Java 8使用枚举的方法

Java 8使用枚举的方法,java,enums,java-8,Java,Enums,Java 8,我想知道在Java8中处理枚举的所有值的最佳方法是什么。特别是当您需要获取所有值并将其添加到某个位置时,例如,假设我们有以下枚举: public enum Letter { A, B, C, D; } 我当然可以做到以下几点: for (Letter l : Letter.values()) { foo(l); } 但是,我也可以将以下方法添加到枚举定义中: public static Stream<Letter> stream() { return Array

我想知道在Java8中处理枚举的所有值的最佳方法是什么。特别是当您需要获取所有值并将其添加到某个位置时,例如,假设我们有以下枚举:

public enum Letter {
 A, B, C, D;
}
我当然可以做到以下几点:

for (Letter l : Letter.values()) {
    foo(l);
}
但是,我也可以将以下方法添加到枚举定义中:

public static Stream<Letter> stream() {
    return Arrays.stream(Letter.values());
}

这种方法是否可行,或者在设计或性能方面是否存在缺陷?此外,为什么枚举没有stream()方法?

我的猜测是,枚举的大小是有限的(即大小不受语言限制,但受用法限制),因此它们不需要本机流api。当您必须操作变换和重新收集流中的元素时,流是非常好的;这些不是枚举的常见用例(通常您迭代枚举值,但很少需要转换、映射和收集它们)

如果只需要对每个元素执行一个操作,那么可能只需要公开一个forEach方法

     public static void forEach(Consumer<Letter> action) {
            Arrays.stream(Letter.values()).forEach(action);
     }

     .... //example of usage
     Letter.forEach(e->System.out.println(e));
publicstaticvoidforeach(消费者行动){
Arrays.stream(Letter.values()).forEach(action);
}
.... //用法示例
Letter.forEach(e->System.out.println(e));

三个问题:三部分答案:

从设计的角度看可以吗? 当然。没什么问题。如果您需要在枚举上进行大量迭代,那么流API是一种干净的方法,将锅炉板隐藏在一个小方法后面就可以了。虽然我认为更好,

从性能的角度看可以吗? 这很可能无关紧要。大多数情况下,枚举没有那么大。因此,在99.9%的情况下,一种或另一种方法的开销可能并不重要

当然,这其中也有0.1%。在这种情况下:使用真实世界的数据和消费者进行适当的测量

如果我不得不打赌,我希望每个循环的
更快,因为它更直接地映射到内存模型,但在谈论性能时不要猜测,在实际需要调优之前不要调优。以一种先正确后易读的方式编写代码,然后才担心代码风格的性能

为什么枚举没有正确地集成到流API中?
如果将Java的流API与许多其他语言中的等效API进行比较,它似乎受到了严重的限制。缺少各种片段(例如,可重用流和可选流)。另一方面,实现流API对API来说无疑是一个巨大的变化。由于某种原因,它被多次推迟。所以我猜Oracle希望将更改限制在最重要的用例上。不管怎么说,枚举并没有使用那么多。当然,每个项目都有几个,但与列表和其他集合的数量相比,它们算不了什么。即使您有一个枚举,在许多情况下您也不会对它进行迭代。另一方面,列表和集合可能几乎每次都被迭代。我认为这就是为什么枚举没有为流世界提供自己的适配器的原因。我们将看看在未来的版本中是否会添加更多。在此之前,您始终可以使用
Arrays.stream

我认为获得枚举常量
stream
的最短代码是
stream.of(Letter.values())
。它不如
Letter.values().stream()好,但这是数组的问题,而不是枚举

此外,为什么枚举没有
stream()
方法

您是对的,最好的调用应该是
Letter.stream()
。不幸的是,一个类不能有两个具有相同签名的方法,因此不可能向每个枚举隐式添加一个静态方法
stream()
(就像每个枚举都隐式添加一个静态方法
values()
)因为这将破坏已经有静态或实例方法的所有现有枚举,这些方法没有名为
stream()
的参数

这个方法行吗

我想是的。缺点是
stream
是一种静态方法,因此无法避免代码重复;它必须分别添加到每个枚举中。

我赞成。由于
forEach()
也在
Iterable
上定义,因此可以避免创建流:

EnumSet.allOf(Letter.class).forEach(x -> foo(x));
或使用方法参考:

EnumSet.allOf(Letter.class).forEach(this::foo);
不过,旧的for loop感觉有点简单:

for (Letter x : Letter.values()) {
    foo(x);
}

为什么不在枚举中添加一个方法
static void forEach(消费者操作)
?然后你可以简单地用
Letter.forEach(l->foo(l))
来称呼它。这对我来说似乎太过分了。因为数组不支持流API,所以我只使用for循环,就可以完成了。看起来你增加复杂性只是为了让自己焕然一新,但这并没有给你带来任何好处。这看起来像是过早的优化。我建议使用JMH或Caliper为您的特定场景和技术堆栈做一个基准测试,并使用最佳方法。C#当您这样做时,人们会感到不安。请注意,调用
Enum.values
会返回
Enum[]
。由于数组是可变的,
enum
每次必须返回数组的不同副本(以防有人对其进行了变异);这意味着对
Enum.values
的任何调用都必须是
O(n)
。这是
enum
设计中的一个缺陷-不可变
是一个更好的选择-但这意味着如果性能是最重要的,那么您应该将
enum.value
缓存在一个不可变
中,而这已经提供了一个
流()
方法。。。“很少需要转换、映射和收集它们”,这取决于您使用它们的方式。现在建议使用
enum
来替代位标志等。这是由@Radiodef实现的。事实上,这似乎很奇怪,因为我们
for (Letter x : Letter.values()) {
    foo(x);
}