Java 接口是否可以定义为只有 Enum 可以扩展它?

2023-12-31

我想这样做并没有什么特别的原因——我只是想知道这是否可能。如果有帮助的话,这是一个可以使用它的虚构情况:

想象一下一种类型Enum它用作只读数据源,这样每个值Enum包含不同的内容。这Enum实施Readable。现在,假设我们想要一个读取所有值的方法Enum到单个缓冲区中。这可以作为辅助类中的静态实用方法来实现(见下文)。

public class ReadableEnumUtils {
    /** reads data from all enum values into the charbuffer */
    public static <T extends Enum<T> & Readable> int readAll(Class<T> clazz, CharBuffer cb) throws IOException {
        int total = 0;
        for (T e : clazz.getEnumConstants()) {
            int intermediate = e.read(cb);
            if (intermediate < 0) {
                throw new IllegalArgumentException("The enum value \'" + e.name() + "\' had no data to read.");
            }
            total += intermediate;
        }
        return total;
    }
}

最好在接口中声明该方法,但这可能会造成混淆,因为非 Enum 类不应实现此类方法并不是立即显而易见的。理想情况下,接口的定义方式应使编译器确保它仅由接口的子类实现Enum。以下是该界面的示例:

interface ReadableEnum extends Readable {
    int read(CharBuffer cb) throws IOException;

    int readAll(CharBuffer cb) throws IOException;
}

我认为不可能让编译器确保ReadableEnum仅由子类实现Enum- 那是对的吗?


Java 默认情况下不支持类似的功能,您会问为什么不提供规范链接,但没有特殊原因,只是没有人决定添加此类功能,您可以自己提出 - 但随后您可能会了解到它们不认为这是需要的,并且不会将其添加到语言中。

但是java提供了非常强大的选项来自行实现这一点:注释处理。
我创建了带有注释的简单 java 8 maven 项目:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface EnumInterface {}

并配有特殊处理器

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.util.*;

@SupportedAnnotationTypes("com.gotofinal.enuminterface.EnumInterface")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EnumInterfaceProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
        Types typeUtils = processingEnv.getTypeUtils();

        // first we scan for all interfaces marked with this annotation
        List<TypeElement> enumOnlyInterfaces = new ArrayList<>();
        for (Element rootElement : roundEnv.getRootElements()) { // getRootElements should return all types being compiled
            if (! (rootElement instanceof TypeElement)) {
                continue;
            }
            TypeMirror typeMirror = rootElement.asType();
            // we check if this class have our annotation, we could also here check if this is an interface (by checking if it does not extend Object directly) and throw error otherwise
            if (rootElement.getAnnotation(EnumInterface.class) != null) {
                enumOnlyInterfaces.add((TypeElement) rootElement);
            }
        }

        // and now we scan for any non enum types that implement this interface
        for (Element rootElement : roundEnv.getRootElements()) {
            if (! (rootElement instanceof TypeElement)) {
                continue;
            }
            TypeElement type = findImplementedInterface(rootElement.asType(), enumOnlyInterfaces, typeUtils);
            if (type == null) {
                continue;
            }
            if (! (rootElement.asType() instanceof DeclaredType)) {
                continue;
            }

            // it's fine if it is an enum
            if (this.isEnum(rootElement.asType(), typeUtils)) {
                continue;
            }

            // and we print error to compiler
            messager.printMessage(Diagnostic.Kind.ERROR, "Interface " + type.getQualifiedName()
                                                                 + " can't be used on non enum class: " + ((TypeElement) rootElement).getQualifiedName());
        }
        return false;
    }

    public TypeElement findImplementedInterface(TypeMirror type, List<TypeElement> interfaces, Types types) {
        for (TypeElement anInterface : interfaces) {
            // types.isSubtype(typeA, typeA) would return true, so we need to add this equals check
            if (!anInterface.asType().equals(type) && types.isSubtype(type, anInterface.asType())) {
                return anInterface;
            }
        }
        return null;
    }

    // maybe there is better way to do this... but I just scan recursively for a subtype with java.lang.Enum name, so it's not perfect but should be enough.
    public boolean isEnum(TypeMirror type, Types types) {
        for (TypeMirror directSupertype : types.directSupertypes(type)) {
            TypeElement element = (TypeElement) ((DeclaredType) directSupertype).asElement();
            if (element.getQualifiedName().contentEquals("java.lang.Enum")) {
                return true;
            }
            if (isEnum(directSupertype, types)) {
                return true;
            }
        }
        return false;
    }
}

并将其注册在META-INF/services/javax.annotation.processing.Processor file:

com.gotofinal.enuminterface.EnumInterfaceProcessor

这段代码可能可以改进很多,我以前从未编写过任何注释处理器。但是当我们创建另一个 Maven 项目并将其声明为依赖项并编写如下代码时:

@EnumInterface
interface TestInterface {}

enum TestEnum implements TestInterface {}

class TestClass implements TestInterface {}

我们将无法编译它并出现错误:

接口 com.gotofinal.enuminterface.TestInterface 不能用于非枚举类:com.gotofinal.enuminterface.TestClass

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 接口是否可以定义为只有 Enum 可以扩展它? 的相关文章

随机推荐