Java枚举定义


151

我以为我很了解Java泛型,但是随后在java.lang.Enum中遇到了以下内容:

class Enum<E extends Enum<E>>

有人可以解释如何解释此类型参数吗?奖励积分,用于提供其他示例,说明可以使用类似类型的参数。



这个问题有更好的答案:stackoverflow.com/a/3068001/2413303
EpicPandaForce 2015年

Answers:


105

这意味着枚举的类型实参必须从本身具有相同类型实参的枚举派生。怎么会这样 通过使类型参数成为新类型本身。因此,如果我有一个名为StatusCode的枚举,则它等效于:

public class StatusCode extends Enum<StatusCode>

现在,如果你检查的限制,我们已经得到了Enum<StatusCode>-如此E=StatusCode。让我们检查一下:是否E扩展Enum<StatusCode>?是! 没关系

您可能会问自己这是什么意思:)好吧,这意味着Enum的API可以引用自身-例如,可以说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本身会很​​好。


1
@artsrc:我不记得为什么在构建器和消息中都需要通用它。我敢肯定,如果我不需要的话,我不会走那条路:)
Jon Skeet 2012年

1
@SayemAhmed:是的,它不会阻止类型混合起来的方面。我将为此添加注释。
乔恩·斯基特

1
“我在ProtocolBuffers的C#端口中使用了类似的东西。” 但这是不同的,因为构建器具有返回类型参数类型的实例方法。Enum没有任何返回类型参数类型的实例方法。
newacct

1
@JonSkeet:鉴于枚举类始终是自动生成的,因此我声称class Enum<E>在所有情况下这都是足够的。并且在泛型中,只有在确实需要确保类型安全的情况下才应使用限制性更强的界限。
newacct

1
@JonSkeet:另外,如果Enum子类并非总是自动生成的,那么您需要class Enum<E extends Enum<?>>结束的唯一原因class Enum<E>是可以访问ordinalfor compareTo()。但是,如果您考虑一下,从语言的角度来看,让您通过它们的序数来比较两种不同类型的枚举是没有意义的。因此,仅在自动生成子类的情况下Enum.compareTo(),使用的实现ordinal才有意义Enum。如果您可以手动子类化Enum,则compareTo可能必须这样做abstract
newacct

27

以下是从书中解释的修改版本的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>

Maurice Naftalin(Java泛型和合集)的合著者


1
@newacct好吧,我现在明白了:您希望所有枚举都是Enum <E>的实例,对吗?(因为它们是Enum子类型的实例,所以上面的参数适用。)但是,您将不再具有类型安全的枚举,因此完全失去了拥有枚举的意义。
莫里斯·纳夫塔林2013年

1
@newacct您是否要坚持使用Season工具Comparable<Season>
Maurice Naftalin 2013年

2
@newacct查看Enum的定义。为了比较一个实例与另一个实例,它必须比较它们的序数。因此,该compareTo方法的参数必须已声明Enum子类型,否则编译器将(正确)说它没有序数。
2013年

2
@MauriceNaftalin:如果Java不禁止手动子类化Enum,那么class OneEnum extends Enum<AnotherEnum>{}即使Enum现在声明了它,也可能有。这将没有多大意义,以能够比较一个枚举类型与另一个,所以后来EnumcompareTo就没有意义作为反正声明。边界对此没有任何帮助。
newacct

2
@MauriceNaftalin:如果顺序是原因,那么public class Enum<E extends Enum<?>>就足够了。
newacct

6

这可以通过一个简单的示例和一种可以用来实现子类的链接方法调用的技术来说明。在下面的示例中,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!
    }
}

您的解决方案具有return (CHILD) this;不受检查的强制类型转换:考虑添加getThis()方法:protected CHILD getThis() { return this; } 请参阅:angelikalanger.com/GenericsFAQ/FAQSections/…–
Roland

@Roland感谢您的链接,我从中借来了一个想法。您能解释一下我还是直接看一篇文章解释为什么在这种情况下这是不好的做法?链接中的方法需要更多的输入,这是为什么我要避免这种情况的主要观点。在这种情况下,我从未见过强制转换错误+我知道有一些不可避免的强制转换错误-即,当一个对象将多个类型的对象存储到同一集合中时。因此,如果未经检查的转换不是很关键,并且设计有些复杂(Node<T>不是这种情况),那么我会忽略它们以节省时间。
Andrey Chaschev

除了添加一些语法糖之外,您的编辑与以前没有什么不同,请考虑以下代码将实际编译但会引发运行时错误:`Node <City> node = new Node <City>().setName(“ node”)。 setSquare(1);`如果您查看Java字节码,您会发现由于类型擦除,该语句return (SELF) this;被编译为return this;,因此可以将其省略。
罗兰

@Roland谢谢,这就是我需要的-有空时将更新示例。
Andrey Chaschev 2013年


3

您不是唯一想知道这意味着什么的人。请参阅Chaotic Java博客

“如果一个类扩展了该类,则它应该传递参数E。参数E的范围是针对使用相同参数E扩展该类的类的。”


1

这篇文章向我完全阐明了“递归泛型类型”的问题。我只想添加另一个需要这种特殊结构的情况。

假设在通用图中有通用节点:

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(){...}
}

它仍然允许我创建class Foo extends Node<City>Foo与City不相关的地方。
newacct

1
当然可以,那是错的吗?我不这么认为。仍然遵守Node <City>提供的基本合同,只有Foo子类的用处不大,因为您开始使用Foos,但是将Cities排除在ADT外。可能有一个用例,但是最简单,更有用的是使通用参数与子类相同。但是,无论哪种方式,设计师都可以选择。
mdma

@mdma:我同意。那么,绑定提供了什么用class Node<T>呢?
newacct

1
@nozebacle:您的示例并未证明“此特定结构是必需的”。class Node<T>与您的示例完全一致。
newacct

1

如果您查看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>意思?这意味着类型参数是从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>对象。

一个更清晰的示例是我在这个问题上回答的示例,如果要指定泛型绑定,则无法避免这种构造。


您未显示任何内容,compareTo也不getDeclaringClass需要extends Enum<E>约束。
newacct

0

根据Wikipedia所说,此模式称为好奇重复模板模式。基本上,通过使用CRTP模式,我们可以轻松地引用子类类型而无需类型转换,这意味着通过使用模式,我们可以模仿虚函数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.