Вопрос

Я думал, что довольно хорошо разбираюсь в Java generics, но потом я наткнулся на следующее в java.lang.Перечисление:

class Enum<E extends Enum<E>>

Может ли кто-нибудь объяснить, как интерпретировать параметр этого типа?Бонусные баллы за предоставление других примеров того, где можно было бы использовать параметр аналогичного типа.

Это было полезно?

Решение

Это означает, что аргумент типа для enum должен быть производным от enum, который сам имеет аргумент того же типа.Как это могло произойти?Сделав аргумент type самим новым типом.Итак, если у меня есть перечисление с именем StatusCode, это было бы эквивалентно:

public class StatusCode extends Enum<StatusCode>

Теперь, если вы проверите ограничения, мы получим Enum<StatusCode> - итак E=StatusCode.Давайте проверим:делает E расширять Enum<StatusCode>?Да!У нас все в порядке.

Вы вполне можете спросить себя, какой в этом смысл :) Ну, это означает, что API для Enum может ссылаться на самого себя - например, будучи в состоянии сказать, что Enum<E> орудия труда Comparable<E>.Базовый класс способен выполнять сравнения (в случае перечислений), но он может убедиться, что сравнивает друг с другом только правильный тип перечислений.(РЕДАКТИРОВАТЬ:Ну, почти - смотрите правку внизу.)

Я использовал нечто подобное в своем порту ProtocolBuffers на C #.Существуют "сообщения" (неизменяемые) и "конструкторы" (изменяемые, используемые для создания сообщения) - и они представлены в виде пар типов.Задействованные интерфейсы следующие:

public interface IBuilder<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

public interface IMessage<TMessage, TBuilder>
  where TMessage : IMessage<TMessage, TBuilder> 
  where TBuilder : IBuilder<TMessage, TBuilder>

Это означает, что из сообщения вы можете получить соответствующий конструктор (напримерчтобы скопировать сообщение и изменить некоторые биты), а из конструктора вы можете получить соответствующее сообщение, когда закончите его создание.Это хорошая работа, хотя пользователям API на самом деле не нужно заботиться об этом - это ужасно сложно, и потребовалось несколько итераций, чтобы добраться до того, что есть.

Редактировать:Обратите внимание, что это не мешает вам создавать нечетные типы, которые используют аргумент типа, который сам по себе подходит, но который не является тем же типом.Цель состоит в том, чтобы предоставить преимущества в правильно случае, а не защищать вас от неправильно кейс.

Так что , если Enum в любом случае, они не были обработаны "специально" в Java, вы могли бы (как отмечено в комментариях) создать следующие типы:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Second осуществил бы Comparable<First> вместо того , чтобы Comparable<Second>...но First само по себе было бы прекрасно.

Другие советы

Ниже приводится измененная версия объяснения из книги Дженерики и коллекции Java:У нас есть Enum объявленный

enum Season { WINTER, SPRING, SUMMER, FALL }

который будет расширен до класса

final class Season extends ...

где ... должен быть каким-то образом параметризованным базовым классом для перечислений.Давайте разберемся , каким это должно быть.Что ж, одно из требований к Season заключается в том, что он должен реализовать Comparable<Season>.Итак, нам понадобится

Season extends ... implements Comparable<Season>

Для чего вы могли бы использовать ... что позволило бы этому сработать?Учитывая, что это должна быть параметризация Enum, единственный выбор - это Enum<Season>, чтобы вы могли иметь:

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

Итак Enum параметризуется для таких типов, как Season.Абстрагироваться от Season и вы получаете, что параметр Enum есть ли какой-либо тип, который удовлетворяет

 E extends Enum<E>

Морис Нафталин (соавтор, Java Generics and Collections)

Это можно проиллюстрировать простым примером и техникой, которая может быть использована для реализации цепных вызовов методов для подклассов.В приведенном ниже примере setName возвращает Node таким образом, цепочка не будет работать для City:

class Node {
    String name;

    Node setName(String name) {
        this.name = name;
        return this;
    }
}

class City extends Node {
    int square;

    City setSquare(int square) {
        this.square = square;
        return this;
    }
}

public static void main(String[] args) {
    City city = new City()
        .setName("LA")
        .setSquare(100);    // won't compile, setName() returns Node
}

Таким образом, мы могли бы ссылаться на подкласс в общем объявлении, так что City теперь возвращает правильный тип:

abstract class Node<SELF extends Node<SELF>>{
    String name;

    SELF setName(String name) {
        this.name = name;
        return self();
    }

    protected abstract SELF self();
}

class City extends Node<City> {
    int square;

    City setSquare(int square) {
        this.square = square;
        return self();
    }

    @Override
    protected City self() {
        return this;
    }

    public static void main(String[] args) {
       City city = new City()
            .setName("LA")
            .setSquare(100);                 // ok!
    }
}

Вы не единственный, кто задается вопросом, что это значит;видишь Хаотичный Java-блог.

“Если класс расширяет этот класс, он должен передать параметр E.Границы параметра E относятся к классу, который расширяет этот класс с тем же параметром E ”.

Этот пост полностью прояснил для меня проблему "рекурсивных универсальных типов".Я просто хотел добавить еще один случай, когда необходима именно эта структура.

Предположим, у вас есть общие узлы в общем графе:

public abstract class Node<T extends Node<T>>
{
    public void addNeighbor(T);

    public void addNeighbors(Collection<? extends T> nodes);

    public Collection<T> getNeighbor();
}

Тогда у вас могут быть графики специализированных типов:

public class City extends Node<City>
{
    public void addNeighbor(City){...}

    public void addNeighbors(Collection<? extends City> nodes){...}

    public Collection<City> getNeighbor(){...}
}

Если вы посмотрите на Enum исходный код, он имеет следующее:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    } 
}

Перво-наперво, что делает E extends Enum<E> в смысле?Это означает, что параметр type является чем-то, что расширяется из Enum и не параметризуется с помощью необработанного типа (он параметризуется сам по себе).

Это актуально, если у вас есть перечисление

public enum MyEnum {
    THING1,
    THING2;
}

который, если я правильно знаю, переводится как

public final class MyEnum extends Enum<MyEnum> {
    public static final MyEnum THING1 = new MyEnum();
    public static final MyEnum THING2 = new MyEnum();
}

Таким образом, это означает, что MyEnum получает следующие методы:

public final int compareTo(MyEnum o) {
    Enum<?> other = (Enum<?>)o;
    Enum<MyEnum> self = this;
    if (self.getClass() != other.getClass() && // optimization
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

И что еще более важно,

    @SuppressWarnings("unchecked")
    public final Class<MyEnum> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
    }

Это делает getDeclaringClass() приведите к нужному Class<T> объект.

Более наглядным примером является тот, на который я ответил этот вопрос где вы не можете избежать этой конструкции, если хотите указать общую границу.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top