【小家java】java5新特性(简述十大新特性) 重要一跃
【小家java】java6新特性(简述十大新特性) 鸡肋升级
【小家java】java7新特性(简述八大新特性) 不温不火
【小家java】java8新特性(简述十大新特性) 饱受赞誉
【小家java】java9新特性(简述十大新特性) 褒贬不一
【小家java】java10新特性(简述十大新特性) 小步迭代
【小家java】java11新特性(简述八大新特性) 首个重磅LTS版本
这次当我入职一家新公司的时候,编写代码发现,里面还在大量的使用public static final…这种语句来神马一些状态常量。
很多时候,虽然都能暂时
完成一样的功能,但武功高低,一看便知。因此我加入之后,迅速全面引入枚举类型,并且指定枚举的使用规范、统一实现的接口。。。
枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。
这段代码,是在enum没引入之前:
public class DayDemo {
public static final int MONDAY =1;
public static final int TUESDAY=2;
public static final int WEDNESDAY=3;
public static final int THURSDAY=4;
public static final int FRIDAY=5;
public static final int SATURDAY=6;
public static final int SUNDAY=7;
}
上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足:
//枚举类型,使用关键字enum
public enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
相当简洁,使用起来也是异常的方便(下面会着重讲解它的用法)。
需要看真身,首先我们得看看编译后的.class文件。
/**
* @author [email protected]
* @description
* @date 2018-11-03 16:49
*/
public enum DayEnum {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
查看.class文件如下:
我们发现它和普通的class文件一样,还是会生成一个同名的.class文件。现在我们反编译看看.class文件的内容:
public final class DayEnum extends Enum<DayEnum> {
//编译器为我们添加的静态的values()方法
public static DayEnum[] values() {
return (DayEnum[])$VALUES.clone();
}
//编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
public static DayEnum valueOf(String s) {
return (DayEnum)Enum.valueOf(com/fsx/run/enums/DayEnum, s);
}
//私有构造函数 只能由编译器来调用
private DayEnum(String enumName, int index) {
super(enumName, index);
}
//前面定义的7种枚举实例
public static final DayEnum MONDAY;
public static final DayEnum TUESDAY;
public static final DayEnum WEDNESDAY;
public static final DayEnum THURSDAY;
public static final DayEnum FRIDAY;
public static final DayEnum SATURDAY;
public static final DayEnum SUNDAY;
//装载所有实例的一个数组
private static final DayEnum $VALUES[];
//通过静态代码快实例这些多例
static {
MONDAY = new DayEnum("MONDAY", 0);
TUESDAY = new DayEnum("TUESDAY", 1);
WEDNESDAY = new DayEnum("WEDNESDAY", 2);
THURSDAY = new DayEnum("THURSDAY", 3);
FRIDAY = new DayEnum("FRIDAY", 4);
SATURDAY = new DayEnum("SATURDAY", 5);
SUNDAY = new DayEnum("SUNDAY", 6);
//都装载进去
$VALUES = (new DayEnum[] {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
});
}
}
看到了enum类型编译后的真身,很多结果都一目了然了有木有。
我们注意到,DayEnum类是final类型的,将无法被继承。而且该类继承自java.lang.Enum类(它是一个抽象类,所有的enum类型的类都是它的子类,提供很多方法和定义)
这里提醒大家一点,Enum类内部会有一个构造函数,该构造函数只能有编译器调用,我们是无法手动操作的
需求:我们需要一次性获取到所有的枚举值对象:
public static void main(String[] args) {
DayEnum[] values = DayEnum.values();
DayEnum[] enumConstants = DayEnum.class.getEnumConstants();
//判断某个class是否为枚举类型
System.out.println(MONDAY.getClass().isEnum()); //true
}
与常规抽象类一样,enum类允许我们为其定义抽象方法,然后使每个枚举实例都实现该方法,以便产生不同的行为方式,注意abstract关键字对于枚举类来说并不是必须的如下:
public enum EnumDemo3 {
FIRST{
@Override
public String getInfo() {
return "FIRST TIME";
}
},
SECOND{
@Override
public String getInfo() {
return "SECOND TIME";
}
}
;
/**
* 定义抽象方法
* @return
*/
public abstract String getInfo();
//测试
public static void main(String[] args){
System.out.println("F:"+EnumDemo3.FIRST.getInfo());
System.out.println("S:"+EnumDemo3.SECOND.getInfo());
/**
输出结果:
F:FIRST TIME
S:SECOND TIME
*/
}
}
在JDK1.5 之前,我们定义常量都是: public static final… 。现在好了,有了枚举,可以把相关的常量分组到一个枚举类型里,而且枚举提供了比常量更多的方法。
public enum Color {
// RED, GREEN, BLANK, YELLOW //若后续没有代码了,此;可以省略。否则不行
RED, GREEN, BLANK, YELLOW;
}
JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例。
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通静态方法方法 可通过枚举类直接调用
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get方法(set方法是没有必要给出的,因为并不希望改变值)
public String getName() {
return name;
}
public int getIndex() {
return index;
}
}
下面给出一个toString()方法覆盖的例子。
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
}
所有的枚举都继承自java.lang.Enum类。由于Java 不支持多继承,所以枚举对象不能再继承其他类。
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
这个思想是很好的。如果你的一个模块需要有多个枚举,建议可以放在接口内,来统一组织。这样方便管理,也方便做一些多态的使用
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型,而value则可以是任意类型
public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
implements java.io.Serializable, Cloneable
先思考这样一个问题,现在我们有一堆size大小相同而颜色不同的数据,需要统计出每种颜色的数量是多少以便将数据录入仓库,定义如下枚举用于表示颜色Color:
enum Color {
GREEN,RED,BLUE,YELLOW
}
显然这个如果用Map来做,估计谁都会做。但是本文采用更方便,更加高效的EnumMap来处理:
//方案2:使用EnumMap
// 使用color作为key,总数Integer作为值即可方便统计
Map<Color,Integer> enumMap=new EnumMap<>(Color.class);
for (Clothes clothes:list){
Color color=clothes.getColor();
Integer count = enumMap.get(color);
if(count!=null){
enumMap.put(color,count+1);
}else {
enumMap.put(color,1);
}
}
System.out.println(enumMap.toString());
EnumMap作为枚举的专属的集合,我们没有理由再去使用HashMap,毕竟EnumMap要求其Key必须为Enum类型,因而使用Color枚举实例作为key是最恰当不过了,也避免了获取name的步骤。
更重要的是EnumMap效率更高,因为其内部是通过数组实现的(具体源码分析,本文不做过多的分析)。注意EnumMap的key值不能为null
,虽说是枚举专属集合,但其操作与一般的Map差不多,概括性来说EnumMap是专门为枚举类型量身定做的Map实现,虽然使用其它的Map(如HashMap)也能完成相同的功能,但是使用EnumMap会更加高效.
它只能接收同一枚举类型的实例作为键值且不能为null,由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值,毕竟数组是一段连续的内存空间,根据程序局部性原理,效率会相当高。
它有三个构造函数:
//创建一个具有指定键类型的空枚举映射。
EnumMap(Class<K> keyType)
//创建一个其键类型与指定枚举映射相同的枚举映射,最初包含相同的映射关系(如果有的话)。
EnumMap(EnumMap<K,? extends V> m)
//创建一个枚举映射,从指定映射对其初始化。
EnumMap(Map<K,? extends V> m)
使用实例:
//使用第一种构造
Map<Color,Integer> enumMap=new EnumMap<>(Color.class);
//使用第二种构造
Map<Color,Integer> enumMap2=new EnumMap<>(enumMap);
//使用第三种构造
Map<Color,Integer> hashMap = new HashMap<>();
hashMap.put(Color.GREEN, 2);
hashMap.put(Color.BLUE, 3);
Map<Color, Integer> enumMap = new EnumMap<>(hashMap);
public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>
implements Cloneable, java.io.Serializable
EnumSet 中所有元素都必须是枚举类型。
与其他Set接口的实现类HashSet/TreeSet(内部都是用对应的HashMap/TreeMap实现的)不同的是,EnumSet在内部实现是位向量(稍后分析),它是一种极为高效的位运算操作。
由于直接存储和操作都是bit,因此EnumSet空间和时间性能都十分可观,足以媲美传统上基于 int 的“位标志”的运算,重要的是我们可像操作set集合一般来操作位运算,这样使用代码更简单易懂同时又具备类型安全的优势。
创建EnumSet并不能使用new关键字,因为它是个抽象类,而应该使用其提供的静态工厂方法,EnumSet的静态工厂方法比较多,如下:
// 创建一个具有指定元素类型的空EnumSet。
EnumSet<E> noneOf(Class<E> elementType)
//创建一个指定元素类型并包含所有枚举值的EnumSet
<E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)
// 创建一个包括枚举值中指定范围元素的EnumSet
<E extends Enum<E>> EnumSet<E> range(E from, E to)
// 初始集合包括指定集合的补集
<E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)
// 创建一个包括参数中所有元素的EnumSet
<E extends Enum<E>> EnumSet<E> of(E e)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4)
<E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4, E e5)
<E extends Enum<E>> EnumSet<E> of(E first, E... rest)
//创建一个包含参数容器中的所有元素的EnumSet
<E extends Enum<E>> EnumSet<E> copyOf(EnumSet<E> s)
<E extends Enum<E>> EnumSet<E> copyOf(Collection<E> c)
使用示例:
enum Color {
GREEN , RED , BLUE , BLACK , YELLOW
}
public static void main(String[] args){
//空集合
EnumSet<Color> enumSet= EnumSet.noneOf(Color.class);
System.out.println("添加前:"+enumSet.toString());
enumSet.add(Color.GREEN);
enumSet.add(Color.RED);
enumSet.add(Color.BLACK);
enumSet.add(Color.BLUE);
enumSet.add(Color.YELLOW);
System.out.println("添加后:"+enumSet.toString());
System.out.println("-----------------------------------");
//使用allOf创建包含所有枚举类型的enumSet,其内部根据Class对象初始化了所有枚举实例
EnumSet<Color> enumSet1= EnumSet.allOf(Color.class);
System.out.println("allOf直接填充:"+enumSet1.toString());
System.out.println("-----------------------------------");
//初始集合包括枚举值中指定范围的元素
EnumSet<Color> enumSet2= EnumSet.range(Color.BLACK,Color.YELLOW);
System.out.println("指定初始化范围:"+enumSet2.toString());
System.out.println("-----------------------------------");
//指定补集,也就是从全部枚举类型中去除参数集合中的元素,如下去掉上述enumSet2的元素
EnumSet<Color> enumSet3= EnumSet.complementOf(enumSet2);
System.out.println("指定补集:"+enumSet3.toString());
System.out.println("-----------------------------------");
//初始化时直接指定元素
EnumSet<Color> enumSet4= EnumSet.of(Color.BLACK);
System.out.println("指定Color.BLACK元素:"+enumSet4.toString());
EnumSet<Color> enumSet5= EnumSet.of(Color.BLACK,Color.GREEN);
System.out.println("指定Color.BLACK和Color.GREEN元素:"+enumSet5.toString());
System.out.println("-----------------------------------");
//复制enumSet5容器的数据作为初始化数据
EnumSet<Color> enumSet6= EnumSet.copyOf(enumSet5);
System.out.println("enumSet6:"+enumSet6.toString());
System.out.println("-----------------------------------");
List<Color> list = new ArrayList<Color>();
list.add(Color.BLACK);
list.add(Color.BLACK);//重复元素
list.add(Color.RED);
list.add(Color.BLUE);
System.out.println("list:"+list.toString());
//使用copyOf(Collection c)
EnumSet enumSet7=EnumSet.copyOf(list);
System.out.println("enumSet7:"+enumSet7.toString());
/**
输出结果:
添加前:[]
添加后:[GREEN, RED, BLUE, BLACK, YELLOW]
-----------------------------------
allOf直接填充:[GREEN, RED, BLUE, BLACK, YELLOW]
-----------------------------------
指定初始化范围:[BLACK, YELLOW]
-----------------------------------
指定补集:[GREEN, RED, BLUE]
-----------------------------------
指定Color.BLACK元素:[BLACK]
指定Color.BLACK和Color.GREEN元素:[GREEN, BLACK]
-----------------------------------
enumSet6:[GREEN, BLACK]
-----------------------------------
list:[BLACK, BLACK, RED, BLUE] //普通List没有去重
enumSet7:[RED, BLUE, BLACK] //运用此set可以方便的去重枚举
*/
}
其实博主认为EnumSet最有价值的是其内部实现原理,采用的是位向量,它体现出来的是一种高效的数据处理方式,这点很值得我们去学习它。
因此EnumSet的内部实现原理还是值得好好学习的。但本文不做过多的讨论了。
enum这个关键字,可以理解为跟class差不多,这也个单独的类。
一般的class可以自己new对象,想几个就几个,而这个enum关键字,他就不行,他的实例对象,只能在这个enum里面体现。也就是说,他对应的实例是有限的。这也就是枚举的好处了,限制了某些东西的范围,举个栗子:
一年四季,只能有春夏秋冬,你要是字符串表示的话,那就海了去了,但是,要用枚举类型的话,你在enum的大括号里面把所有的选项,全列出来,那么这个季节的属性,对应的值,只能在里面挑。不能有其他的。
强制规范
:所有的枚举类型成员必须要有注释,说明每个字段的用途。(一般可以使用接口进行强制规范)枚举类型对象之间的值比较,是可以使用==,直接来比较值,是否相等的,不是必须使用equals方法的哟。 并且,强烈建议使用==,效率更高