我是Java 枚举的忠实拥护者 。 似乎我们一直在等待获得它,但是当我们最终获得它( J2SE 5 )时,该枚举比C和C ++提供的枚举要好得多,在我看来,这“ 值得等待” 。 与Java enum
一样好,它也不是没有问题。 特别是,Java枚举的方法values()
返回数组的新副本,该副本表示每次调用时可能的值。
Java语言规范阐明了枚举行为。 在Java语言规范Java SE 10 Edition中 , 第8.9节涵盖了枚举。 第8.9.3节 (“枚举成员”)列出了两个“隐式声明的方法”: public static E[] values()
和public static E valueOf(String name)
。 例8.9.3-1 (“使用增强的for
循环遍历枚举常量”)演示了如何调用Enum.values()
遍历枚举。 但是,问题在于Enum.values()
返回一个数组,而Java中的数组是可变的[Java语言规范的10.9节 (“字符数组不是字符串”)提醒我们,当在Java之间进行区分时) string
和Java字符数组。]。 Java枚举是紧密不变的,因此有意义的是,每次调用该枚举以确保不更改与该枚举关联的数组时,该枚举必须返回由values()
方法返回的数组的克隆。
OpenJDK 编译器-开发邮件列表上最近的一篇标题为“ 关于Enum.values()内存分配 ”的文章指出,在紧密循环中调用Enum.values()
会克隆常量值数组时,会分配大量内存。 ” 该消息的发布者补充说,这“可能是出于不变性”,并指出:“我能理解。” 该消息还引用了同一邮件列表上的2012年3月消息和相关主题。
编译器开发邮件列表上的两个线程包括一些有趣的当前可用的解决方法。
- 将
values()
返回的枚举值数组缓存为元素的private static final
数组,该元素的初始private static final
数组初始化为values()
返回的数组。 - 缓存枚举值的固定
List
。 - 创建一个枚举值的
EnumSet
。
Brian Goetz在该线程上的消息开头是“这本质上是API设计错误; 因为values()返回一个数组,并且数组是可变的,所以每次都必须复制该数组。” [Goetz在该消息中还嘲笑了“ 冻结数组 ”(使Java数组变得不可变)的概念。]
这个问题不是新问题。 威廉·希尔兹(William Shields)在2009年12月发布的文章《 Java中的可变性,数组和临时对象的成本 》指出:“所有这些的最大问题是Java数组是可变的。” Shields在写由Enum.values()
提出的特定问题之前,先解释了Java Date类中可变性的古老和众所周知的问题:
Java枚举有一个称为values()
的静态方法,该方法返回该enum
的所有实例的数组 。 在Date
类的课程中,这个特殊的决定令人震惊。 List
本来是更明智的选择。 在内部,这意味着实例数组每次调用时都必须进行防御性复制...
对此问题的其他引用包括“ Enums.values()方法 ”(Guava线程)和“ Java的Enum.values()隐藏分配 ”(显示缓存Enum.values()
返回的数组)。 上面还写了一个JDK错误: JDK-8073381 (“需要API来获取枚举值而不创建新数组”)。
下一篇代码清单中说明了本文中讨论的一些当前可用的变通方法,这是一个简单的Fruit
枚举,演示了以三种不同格式缓存该枚举的值。
具有三个“值”的缓存集的Fruit.java枚举
package dustin.examples.enums;import java.util.EnumSet;
import java.util.List;/*** Fruit enum that demonstrates some currently available* approaches for caching an enum's values so that a new* copy of those values does not need to be instantiated* each time .values() is called.*/
public enum Fruit
{APPLE("Apple"),APRICOT("Apricot"),BANANA("Banana"),BLACKBERRY("Blackberry"),BLUEBERRY("Blueberry"),BOYSENBERRY("Boysenberry"),CANTALOUPE("Cantaloupe"),CHERRY("Cherry"),CRANBERRY("Cranberry"),GRAPE("Grape"),GRAPEFRUIT("Grapefruit"),GUAVA("Guava"),HONEYDEW("Honeydew"),KIWI("Kiwi"),KUMQUAT("Kumquat"),LEMON("Lemon"),LIME("Lime"),MANGO("Mango"),ORANGE("Orange"),PAPAYA("Papaya"),PEACH("Peach"),PEAR("Pear"),PLUM("Plum"),RASPBERRY("Raspberry"),STRAWBERRY("Strawberry"),TANGERINE("Tangerine"),WATERMELON("Watermelon");private String fruitName;Fruit(final String newFruitName){fruitName = newFruitName;}/** Cached fruits in immutable list. */private static final List<Fruit> cachedFruitsList = List.of(Fruit.values());/** Cached fruits in EnumSet. */private static final EnumSet<Fruit> cachedFruitsEnumSet = EnumSet.allOf(Fruit.class);/** Cached fruits in original array form. */private static final Fruit[] cachedFruits = Fruit.values();public static List<Fruit> cachedListValues(){return cachedFruitsList;}public static EnumSet<Fruit> cachedEnumSetValues(){return cachedFruitsEnumSet;}public static Fruit[] cachedArrayValues(){return cachedFruits;}
}
在许多情况下,每次调用Enum.values()
必须克隆其数组的事实实际上并不重要。 也就是说,不难想象在“紧缩循环”中反复调用Enum.values()
会很有用,然后每次将枚举值复制到数组中都会对内存产生明显影响的情况并不难使用以及与更大内存使用相关的问题。 如果有一种标准的方法来以更有效的内存方式访问枚举的值,那就太好了。 前面提到的两个线程讨论了一些潜在实现此功能的想法。
翻译自: https://www.javacodegeeks.com/2018/08/memory-hogging-enum-values-method.html