Можно ли определить интерфейс Java так, чтобы его могли расширять только Enums? - PullRequest
5 голосов
/ 18 октября 2019

Нет особой причины, по которой я хочу это делать - мне просто интересно, возможно ли это. Если это помогает, вот вымышленная ситуация, в которой это можно использовать:

Представьте себе тип 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 ]

10 голосов
/ 30 октября 2019

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

6 голосов
/ 28 октября 2019

Если все реализации интерфейса расширяют определенный класс, то все экземпляры интерфейса также являются экземплярами класса;следовательно, этот интерфейс также должен расширять этот класс ( подробнее о is-a и подтипе ).

Так как каждого типа враздел extends объявления интерфейса должен быть интерфейс тип , вы не можете заставить свой интерфейс расширять Enum класс ;следовательно, вы не можете помешать классам not-enum реализовать ваш интерфейс.

Вы даже не можете достичь этого, заменив interface ReadableEnum extends Enum на abstract class ReadableEnum extends Enum, поскольку enum типы не должны быть объявлены аннотация .


Но вы все равно можете усложнить реализацию ReadableEnum, поскольку неклассы -enum, расширяя интерфейс 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, тогда как другиеклассы должны также реализовывать name, ordinal, getDeclaringClass и compareTo.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...