可以定义Java接口,以便只有Enums可以扩展它吗?

时间:2019-10-17 23:14:09

标签: java enums

我没有特别的理由要这样做-我只是想知道是否有可能。如果有帮助,可以在一种虚构的情况下使用它:

想象一下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的子类实现-正确吗?

2 个答案:

答案 0 :(得分:10)

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文件中:

com.gotofinal.enuminterface.EnumInterfaceProcessor

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

@EnumInterface
interface TestInterface {}

enum TestEnum implements TestInterface {}

class TestClass implements TestInterface {}

我们将无法编译错误:

  

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

答案 1 :(得分:6)

如果接口的所有实现都扩展了某个类,则该接口的所有实例也都是该类的实例;因此,该接口还必须扩展此类(read more about is-a relationship and subtyping)。

each type in the extends clause of an interface declaration must be an interface type起,您不能使您的界面扩展Enum class ;因此,您不能阻止非枚举类实现您的接口。

enum types must not be declared abstract 起,您甚至无法通过将interface ReadableEnum extends Enum替换为abstract class ReadableEnum extends Enum来实现这一目标。


但是,通过扩展包含所有公共ReadableEnum方法的IEnum接口,您仍然可以为非枚举类实现Enum更加困难:

public interface IEnum<E extends Enum<E>> extends Comparable<E> {
    String name();
    int ordinal();
    Class<E> getDeclaringClass();
}
interface ReadableEnum<E extends Enum<E> & ReadableEnum<E>> extends Readable, IEnum<E> {
    int read(CharBuffer cb) throws IOException;

    default int readAll(CharBuffer cb) throws IOException {
        return ReadableEnumUtils.readAll(getDeclaringClass(), cb);
    }
}

现在,枚举可以仅通过实现ReadableEnum方法来实现read,而其他类则必须实现nameordinalgetDeclaringClass和{{1} }。

相关问题