当前位置: 首页 > 面试题库 >

为什么在Java中将枚举声明为Enum

公孙国兴
2023-03-14
问题内容

如果语言设计者仅使用Enum 会对语言产生怎样的影响?

现在唯一的区别是有人会写

A扩展Enum <B>
但由于Java中不允许扩展枚举,因此仍然是非法的。我也在考虑为jvm提供一个字节码,该字节码将smth定义为扩展枚举-但是泛型不会受到影响,因为它们都被删除了。

那么,声明的重点是什么?

谢谢!

编辑 为简单起见,让我们来看一个例子:

interface MyComparable<T> {
    int myCompare(T o);
}

class MyEnum<E extends MyEnum> implements MyComparable<E> {
    public int myCompare(E o) { return -1; }
}

class FirstEnum extends MyEnum<FirstEnum> {}

class SecondEnum extends MyEnum<SecondEnum> {}

这个类结构有什么问题?限制MyEnum<E extends MyEnum<E>>可以做什么?


问题答案:

这是一个普遍的问题,可以理解的是。请看一下泛型FAQ的这一部分,以获得答案(实际上,您可以随意阅读整个文档,它做得很好而且内容丰富)。

简短的答案是,它迫使该类对其自身进行参数化。这是超类使用通用参数定义与子类透明(如果需要的话,是“本地”)工作的方法所必需的。

编辑:作为一个(非)示例,请考虑上的clone()方法Object。当前,它被定义为返回type的值Object。由于协变返回类型,特定的子类可以(并且经常)定义它们返回更特定的类,但这不能强制执行,因此不能为任意类推断。

现在,如果Object的定义类似于Enum,Object<T extends Object<T>>则必须将所有的类定义为public class MyFoo<MyFoo>。因此,clone()可以声明其返回的类型,T并且可以确保在编译时返回的值始终与对象本身完全是同一类(即使子类也不匹配参数)。

现在,在这种情况下,Object并没有像这样被参数化,因为当99%的类根本不会使用它时,在所有类上都放这个行李会非常令人讨厌。但是对于某些类层次结构来说,它可能非常有用-我本人之前曾使用过类似的技术来处理具有多个实现的抽象,递归表达式解析器的类型。这种结构使得编写“明显的”代码而不用到处强制转换或复制和粘贴只是为了更改具体的类定义成为可能。

编辑2(实际上要回答您的问题!):

如果将Enum定义为Enum<E extends Enum>,那么正如您正确地说的,某人可以将一个类定义为A extends Enum<B>。这违反了泛型构造的观点,即确保泛型参数始终是所讨论类的确切类型。举一个具体的例子,枚举将其compareTo方法声明为

public final int compareTo(E o)

在这种情况下,由于您定义A了extend Enum,A因此只能将的实例与的实例B(无论B是什么)进行比较,这几乎肯定不是很有用。 使用附加的构造,您知道扩展Enum的任何类都只能与其自身进行比较。因此,您可以在超类中提供在所有子类中仍然有用且特定的方法实现。

(没有这种递归泛型技巧,唯一的选择是将compareTo定义为public final int compareTo(Enum o)。这并不是完全一样的事情,因为这样就可以将ajava.math.RoundingMode与a进行比较,java.lang.Thread.State而不会引起编译器的抱怨,这也不是很有用。)

好的,让我们远离Enum它,因为我们似乎已经挂在上面了。相反,这是一个抽象类:

public abstract class Manipulator<T extends Manipulator<T>>
{
    /**
     * This method actually does the work, whatever that is
     */
    public abstract void manipulate(DomainObject o);

    /**
     * This creates a child that can be used for divide and conquer-y stuff
     */
    public T createChild()
    {
        // Some really useful implementation here based on
        // state contained in this class
    }
}

我们将对此进行一些具体的实现-SaveToDatabaseManipulator,SpellCheckingManipulator等。另外,我们还希望让人们定义自己的类,因为这是一个超级有用的类。;-)

现在-您将注意到我们正在使用递归泛型定义,然后T从该createChild方法返回。这意味着:

1)我们知道并且编译器知道如果我调用:

SpellCheckingManipulator obj = ...; // We have a reference somehow
return obj.createChild();

那么返回的值肯定是a SpellCheckingManipulator,即使它使用的是超类的定义。这里的递归泛型使编译器知道对我们显而易见的内容,因此您不必继续强制转换返回值(例如,您经常需要使用clone(),例如)。

2)请注意,我没有将方法声明为final,因为也许某些特定的子类会希望使用更适合自己的版本来覆盖它。泛型定义意味着无论是由谁创建新类或如何定义新类,我们仍然可以断言from的返回BrandNewSloppilyCodedManipulator.createChild()仍然是的实例BrandNewSloppilyCodedManipulator。如果粗心的开发人员试图将其定义为只返回just Manipulator,则编译器将不允许它们。而且,如果他们尝试将类定义为BrandNewSloppilyCodedManipulator<SpellCheckingManipulator>,那么也不会允许他们。

基本上,结论是,当您想在超类中提供某些功能(以某种方式在子类中变得更加具体)时,此技巧很有用。通过这样声明超类,您可以将任何子类的通用参数锁定为子类本身。这就是为什么您可以在超类中编写泛型compareTocreateChild方法并防止在处理特定子类时它变得过于模糊的原因。



 类似资料:
  • 问题内容: 当我得知该类在Java中被声明为final时,我想知道为什么会这样。那时我没有找到任何答案,但是这篇文章:如何在Java中创建String类的副本?让我想起了我的疑问。 当然,String提供了我所需要的所有功能,而且我从未想过需要扩展String类的任何操作,但是您仍然永远不会知道有人可能需要什么! 那么,有谁知道设计师决定将其定稿时的意图是什么? 问题答案: 将字符串实现为不可变对

  • 问题内容: 我正在使用J2EE Eclipse Indigo,并且有三个这样的类声明: ClassC中的TYPE发生编译错误。它抱怨“枚举无法解析为一种类型”。同时也警告ClassA枚举,它抱怨: 我可以知道导致此代码错误的原因吗? 问题答案: 我有一个类似的问题: 枚举无法解析为类型 Eclipse 改为提供导入。 我去了 首选项-> Java-> Installed_JREs-> Execut

  • 问题内容: 为什么将Java常量声明为static? 在此我了解使用final吗?购买为什么它必须是静态的?为什么它应该是类变量,而不是实例变量? 问题答案: 如果它可以随类的实例而变化,那么显然它不是 常数 。为的每个实例获得不同的pi值意味着什么(甚至不允许构造实例)?还是每个实例的大小写不区分大小写?

  • 问题内容: 为什么不能在Java中将类声明为静态类? 问题答案: 只有嵌套的类可以是静态的。这样,你可以使用嵌套类而无需外部类的实例。

  • 问题内容: 我是一名即将毕业的计算机科学专业的学生,​​在我的整个编码生涯中,我发现很少使用枚举的实例,除了典型的情况(例如代表标准纸牌的面孔)外,还使用了枚举。 您是否知道在日常编码中使用枚举的任何巧妙方法? 为什么枚举如此重要,在什么情况下应该能够确定建立枚举是最佳方法? 问题答案: 这些是主要的论点,以及短的例子。 的情况 从Java 6开始,是一个凌乱类的示例,该类可以从使用中受益匪浅(除

  • 问题内容: 我试图找到为什么不能将类创建为静态类的原因?喜欢: 问题答案: 在Java中,关键字通常将一个方法或字段标记为不存在,而不是每个类实例一次,而是一次。一个类一旦存在就已经存在,因此实际上,所有类都以这种方式是“静态的”,并且所有对象都是该类的实例。 确实对 内部 类具有含义,这是完全不同的:通常,内部类实例可以访问与其绑定的外部类实例的成员,但是如果内部类为,则它没有这样的引用并且可以