ChatGPT解决这个技术问题 Extra ChatGPT

Java 枚举与具有公共静态最终字段的类相比有什么优势?

我非常熟悉 C#,但开始更多地使用 Java。我希望知道 Java 中的枚举基本上等同于 C# 中的枚举,但显然情况并非如此。最初,我很高兴得知 Java 枚举可以包含多条数据,这似乎非常有利 (http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)。但是,从那以后,我发现 C# 中缺少许多微不足道的功能,例如能够轻松地为枚举元素分配某个值,因此无需付出大量努力就可以将整数转换为枚举(即Convert integer value to matching Java Enum)。

所以我的问题是:Java 枚举对具有一堆公共静态最终字段的类有什么好处吗?或者它只是提供更紧凑的语法?

编辑:让我更清楚。 Java 枚举相对于具有一堆相同类型的公共静态最终字段的类有什么好处?例如,在第一个链接的行星示例中,枚举与具有这些公共常量的类相比有什么优势:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

据我所知,卡萨布兰卡的答案是唯一满足这一点的答案。

@Bohemian:它可能不是重复的,因为 OP 只提到了 public static final 字段,这些字段可能是类型值,不一定是 int
@Shahzeb 几乎没有。显然,使用枚举而不是字符串常量是一个非常好的主意,而且值得鼓励。类型安全,不需要静态实用函数,等等.. 绝对没有理由使用字符串来代替。
@Voo 是的,我知道会有分歧。这是 49 秒内的一个。枚举很棒(我喜欢它们并且经常使用它们)但是在声明常量或使用它时需要什么类型的安全性。每次需要为 String literal 声明一个常量时,都创建枚举是一种过度杀伤力。
@Shahzeb如果您只有一个变量,请确保使用字符串,这里不会发生太多事情(单个值作为参数毫无意义)。但请注意,我们讨论的是常量S,所以现在我们讨论的是可能将它们传递给函数等。我们需要类型安全吗?嗯,不,但是大多数人认为“一切都是 void*”的 c 风格类型是好的风格,它可以阻止错误(特别是如果传递多个枚举/字符串参数!)。它还将常量放入它们自己的命名空间等。与此相反,只有普通变量并没有真正的优势。
@Bohemian:我不明白怎么做。使用 int,没有类型安全,因为可以传递任何值。另一方面,类型化对象在类型安全方面与枚举没有什么不同。

u
user207421

类型安全和价值安全。保证单身。能够定义和覆盖方法。无需限定即可在 switch 语句 case 语句中使用值。通过 ordinal() 内置的值顺序化。按名称而不是按值进行序列化,这提供了一定程度的面向未来的能力。 EnumSet 和 EnumMap 类。


说了这么多,每次我将代码放入 Enum 时,我都后悔了。
你为什么后悔?我从来没有……
@glglgl 因为它把特定于应用程序的代码放到了一个我觉得它并不真正属于的地方,那实际上只是定义了一组值。如果让我再做一次,我会将它包含在众多 switch 语句之一中,而这正是使用 Enum 的最初动机。
c
casablanca

从技术上讲,确实可以将枚举视为具有一堆类型常量的类,这实际上是枚举常量在内部实现的方式。但是,使用 enum 会为您提供有用的方法 (Enum javadoc),否则您必须自己实现这些方法,例如 Enum.valueOf


还有 .values() 用于迭代值列表。
这似乎是正确的答案,尽管它不是很令人满意。在我看来,Java 仅仅为了更紧凑的语法和对 Enum 方法的访问而添加对枚举的支持是不值得的。
@Craig您的直觉是正确的-这是一个非常糟糕的答案,因为它完全错过了枚举的目的。有关部分原因,请参阅我在问题下的评论。
@Bohemian:我没有“错过枚举的目的”——我一直在使用它们。请参阅我对您上面评论的回复。
如果调用两次 Enum.valuesOf 方法是否返回相同的对象?
D
Dave Newton

没有人提到在 switch 语句中使用它们的能力;我也会把它扔进去。

这允许以干净的方式使用任意复杂的枚举,而无需使用 instanceof、可能会混淆 if 序列或非字符串/int 切换值。典型的例子是状态机。


无论如何,您没有提到枚举与静态字段的任何好处,您可以在带有静态字段的 switch 语句中使用足够的类型。 Op 需要真正的功能或性能差异
@Genaut好处是枚举比字符串或int具有更多功能-问题是我提供的差异。 OP 已经知道枚举是什么,当我 4.5 年前发布这个时,没有其他人提到 switch 语句,并且至少有几个人发现它提供了新信息¯_(ツ)_/¯
@Genaut 当 Planet 不是 enum 时如何切换 Planet 实例?
J
Jim Garrison

主要优点是类型安全。使用一组常量,可以使用相同内在类型的任何值,从而引入错误。对于枚举,只能使用适用的值。

例如

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

对比

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum

类也是类型安全的......:/(假设静态字段是容器类的类型)
您仍然可以传递一个无效值,但这将是一个更容易发现的编译时错误。
您可以在第二个示例中调用 setSize(null),但它可能会比第一个示例的错误更快地失败。
J
Jeffrey

更少的混乱。以 Font 为例。它有一个构造函数,它采用您想要的 Font 的名称、其大小和样式 (new Font(String, int, int))。直到今天,我都记不起风格或尺寸是第一位的。如果 Font 对其所有不同的样式(PLAINBOLDITALICBOLD_ITALIC)都使用了 enum,则其构造函数看起来像 Font(String, Style, int),以防止任何混淆。不幸的是,在创建 Font 类时,还没有 enum,而且由于 Java 必须保持反向兼容性,所以我们总是会受到这种模棱两可的困扰。

当然,这只是使用 enum 而不是 public static final 常量的一个参数。枚举也非常适合 singletons 并实现默认行为,同时允许以后进行自定义(即 strategy pattern)。后者的一个示例是 java.nio.fileOpenOptionStandardOpenOption:如果开发人员想要创建自己的非标准 OpenOption,他可以。


如果要求两个相同的枚举,您的“字体”案例仍然模棱两可。该问题的典型答案是许多语言所称的 Named Parameters。那个或更好的 IDE 方法签名支持。
@aaaaaa 我还没有看到很多情况,构造函数会采用两个相同的 enum 而不使用 varargsSet 来获取任意数量的它们。
@aaaaaa 命名参数的主要问题依赖于实现细节(参数的名称)。我可以制作一个界面interface Foo { public void bar(String baz); }。有人创建了一个调用 bar: someFoo.bar(baz = "hello"); 的类。我将 Foo::bar 的签名更改为 public void bar(String foobar)。现在调用 someFoo 的人需要修改他们的代码,如果他们仍然希望它工作。
我也不记得见过连续的枚举类型,但认为常见的枚举类型可能是 DAY_OF_WEEK 或类似的东西。关于界面的好点 - 没有想到这一点。就我个人而言,由于未命名的参数引起的普遍模糊性,我会考虑这个问题,因此需要更强大的 IDE 支持。然而,我知道这是一个判断调用,并且需要认真考虑破坏 API 更改。
i
icza

这里有很多很好的答案,但没有人提到专门针对枚举的 Collection API 类/接口的高度优化实现:

枚举集

枚举映射

这些枚举特定类仅接受 Enum 实例(EnumMap 仅接受 Enum 作为键),并且只要有可能,它们在其实现中恢复为紧凑表示和位操作。

这是什么意思?

如果我们的 Enum 类型的元素不超过 64 个(大多数现实生活中的 Enum 示例都符合此条件),则实现将元素存储在单个 long 值中,每个有问题的 Enum 实例将是与这个 64 位长 long 的一点相关联。将元素添加到 EnumSet 只是将正确的位设置为 1,删除它只是将该位设置为 0。测试元素是否在 Set 中只是一个位掩码测试!现在您一定会为此而爱上Enum


我以前知道这两个,但我刚刚从您的 What does this mean? 部分学到了很多东西。我知道在大小 64 的实现上存在分歧,但我实际上并不知道为什么
可能值得一提的是,EnumSet 对包含超过 64 个常量的类型也有相同的优化。它只需要使用 long[] 数组而不是单个 long。虽然将 addremovecontains 操作减少为单位操作非常棒,但真正有趣的操作始于像 addAllremoveAllretainAll 这样的操作,它们不会逐位执行,而是一次处理 64 个元素(换句话说,通常是所有元素)。
P
Pang

例子:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

java 常量的限制

1)没有类型安全:首先它不是类型安全的;您可以将任何有效的 int 值分配给 int,例如 99,尽管没有硬币来表示该值。

2) 没有有意义的打印:打印任何这些常量的值将打印其数值而不是有意义的硬币名称,例如,当您打印 NICKLE 时,它将打印“5”而不是“NICKLE”

3) 没有命名空间:要访问 currencyDenom 常量,我们需要在类名前加上前缀,例如 CurrencyDenom.PENNY 而不仅仅是使用 PENNY,尽管这也可以通过在 JDK 1.5 中使用静态导入来实现

枚举的优势

1) Java 中的枚举是类型安全的,并且有自己的命名空间。这意味着您的枚举将在下面的示例中具有例如“货币”的类型,并且除了枚举常量中指定的值之外,您不能分配任何值。

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2) Java 中的 Enum 是类或接口等引用类型,您可以在 Java Enum 中定义构造函数、方法和变量,这使其比 C 和 C++ 中的 Enum 更强大,如下一个 Java Enum 类型示例所示。

3) 您可以在创建时指定枚举常量的值,如下例所示:public enum Currency {PENNY(1), NICKLE(5), DIME(10), QUARTER(25)};但是要使其工作,您需要定义一个成员变量和一个构造函数,因为 PENNY (1) 实际上正在调用一个接受 int value 的构造函数,请参见下面的示例。

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

    private Currency(int value) {
            this.value = value;
    }
}; 

参考:https://javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html


佚名

正如您已经注意到的,枚举的第一个好处是语法简单。但是枚举的主要目的是提供一组众所周知的常量,默认情况下,它们形成一个范围,并通过类型和值安全检查帮助执行更全面的代码分析。

枚举的这些属性对程序员和编译器都有帮助。例如,假设您看到一个接受整数的函数。该整数可能意味着什么?你可以传递什么样的价值观?你真的不会马上知道。但是如果你看到一个接受枚举的函数,你就会非常清楚你可以传入的所有可能值。

对于编译器,枚举有助于确定值的范围,除非您为枚举成员分配特殊值,否则它们的范围从 0 到更高。这有助于通过类型安全检查等自动跟踪代码中的错误。例如,编译器可能会警告您,您没有在 switch 语句中处理所有可能的枚举值(即,当您没有 default 大小写并且只处理 N 个枚举值中的一个时)。当您将任意整数转换为枚举时,它还会警告您,因为枚举的值范围小于整数,这反过来可能会触发函数中不真正接受整数的错误。此外,当值为 0 及以上时,为开关生成跳转表变得更容易。

这不仅适用于 Java,对于其他具有严格类型检查的语言也是如此。 C、C++、D、C# 就是很好的例子。


P
Peter Lawrey

枚举是隐式最终的,具有私有构造函数,它的所有值都是相同类型或子类型,您可以使用 values() 获取它的所有值,获取它的 name()ordinal() 值,或者您可以查看按数字或名称向上枚举。

您还可以定义子类(即使名义上是最终的,但您不能以任何其他方式执行此操作)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.

要添加到此答案,您还可以对接口中定义的任何方法进行“默认”实现。这样,并不是每个枚举都需要花括号和方法实现。您可以简单地为需要它的枚举值提供内联实现。
N
Nico Haase

枚举好处:

枚举是类型安全的,静态字段不是 值的数量是有限的(不可能传递不存在的枚举值。如果你有静态类字段,你可能会犯这个错误) 每个枚举可以有多个属性(字段/吸气剂)分配 - 封装。还有一些简单的方法: YEAR.toSeconds() 或类似的。比较:Colors.RED.getHex() 与 Colors.toHex(Colors.RED)

“例如能够轻松地为枚举元素分配某个值”

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

“因此能够将整数转换为枚举而无需付出相当大的努力”添加一个将 int 转换为枚举的方法。只需添加包含映射的静态 HashMap

如果您真的想将 ord=VAL_200.ordinal() 转换回 val_200 只需使用: EnumX.values()[ord]


C
Community

使用枚举时,您会在编译时检查有效值。看this question.


P
Premraj

最大的优点是枚举单例易于编写和线程安全:

public enum EasySingleton{
    INSTANCE;
}

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

两者都是相似的,它通过实现自己处理序列化

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

more


P
Pang

另一个重要区别是 java 编译器将 primitive typesStringstatic final 字段视为文字。这意味着这些常量变为内联的。它类似于 C/C++ #define 预处理器。请参阅this SO question。枚举不是这种情况。


B
Basil Bourque

枚举可以是本地的

Java 16 开始,可以在本地(在方法内)定义枚举。除了能够将枚举定义为嵌套或单独的类之外,此范围还可以。

这个新的本地定义范围与新的记录功能一起出现。有关详细信息,请参阅 JEP 395: Records。枚举、接口和记录都可以在 Java 16+ 中本地定义。

相反,public static final 字段始终具有全局范围。


相同更改的一部分是本地类现在可以具有 static final 字段。它们的范围与本地 enum 的常量相同。但是,当您在实例方法中时,普通本地类仍然具有对周围 this 的隐式引用,这会阻止它们像 enum 一样使用。
C
Community

我认为 enum 不能是 final,因为在后台编译器会为每个 enum 条目生成子类。

来自 source 的更多信息


在内部,它们不是最终的,因为——正如你所说——可以在内部被子类化。但是很遗憾,您不能自己对它们进行子类化,例如用自己的值对其进行扩展。
仅当您明确请求时才会生成子类。然后,子类将是 final。否则,enum 类型本身将为 final
B
Bikas Katwal

此处发布的枚举有很多优点,我现在正在按照问题中的要求创建这样的枚举。但我有一个包含 5-6 个字段的枚举。

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

在这种情况下,当您在枚举中有多个字段时,很难理解哪个值属于哪个字段,因为您需要查看构造函数和眼球。

使用 static final 常量进行类并使用 Builder 模式创建此类对象使其更具可读性。但是,如果需要,您将失去使用枚举的所有其他优势。此类类的一个缺点是,您需要手动将 Planet 对象添加到 Planets.list/set

我仍然更喜欢枚举而不是此类,因为 values() 派上用场,而且您永远不知道将来是否需要在 switchEnumSetEnumMap 中使用它们 :)


r
radfast

主要原因:枚举可帮助您编写结构良好的代码,其中参数的语义含义在编译时清晰且强类型 - 出于其他答案给出的所有原因。

Quid pro quo:在开箱即用的 Java 中,Enum 的成员数组是最终的。这通常很好,因为它有助于重视安全性和测试,但在某些情况下,它可能是一个缺点,例如,如果您可能从库中扩展现有的基本代码。相反,如果相同的数据位于具有静态字段的类中,您可以轻松地在运行时添加该类的新实例(您可能还需要编写代码将这些添加到该类的任何 Iterable 中)。但是 Enums 的这种行为可以改变:使用反射,您可以在运行时添加新成员或替换现有成员,尽管这可能只应在别无选择的特殊情况下完成:即它是一个 hacky 解决方案,可能会产生意想不到的问题,在 Can I add and remove elements of enumeration at runtime in Java 上查看我的答案。


z
zettsu

你可以做 :

public enum Size { SMALL(1), MEDIUM(2), LARGE(3) };

private int sizeValue;

Size(sizeValue) {this.sizeValue = value; }

所以有了这个你可以得到像这样的大小值 SMALL.getSizeValue();

如果你想设置大小枚举不适合你,如果你只定义常量和固定值就可以了。

检查此link也许可以帮助您