java枚举新特性_Java5.0新特性---枚举enum

前言:

写这篇随笔的时间已经是2020年8月份了,偷偷的去看了一下Oracle网站,发现java版本已经更新迭代到java14了,不禁感叹我对java5的相关知识还没有很好的掌握。java是一门非常活跃的语言,目前已经迭代到了java14版本,其中java5和java8被认为是java最具有里程碑的两个版本,java5中引入了泛型、自动拆装箱、增强for循环、可变参数、枚举等众多新特性,本篇随笔就简单写一下我理解的枚举。

一、枚举类的前世今生

java5之前,没有枚举enum关键字,我们如何实现枚举的功能?

public classPerson {//定义两个成员变量,不对外提供set方法

privateString name;privateString description;/*** 有枚举意义的类,必须提供给外部有限个已经确定的对象,那么该类的构造器必须置为private

* 让外部无法通过new的方式创建该对象,否则如果能通过new创建多个该对象,就不是枚举意义的类。*/privatePerson(String name,String description){this.name =name;this.description =description;

}

@OverridepublicString toString() {return "Person{" +

"name='" + name + '\'' +

", description='" + description + '\'' +

'}';

}/*** 自身提供两个枚举对象

* 1.final,不可被改变的,不允许外部对这两个对象进行更改操作

* 2.static,外部可以通过Person.MAN(类.静态变量)的方式得到该对象*/

public final static Person MAN = new Person("男人","我是个男人");public final static Person WOMAN = new Person("女人","我是个女人");/*** main测试*/

public static voidmain(String[] args) {

Person person1=Person.MAN;

Person person2=Person.WOMAN;

System.out.println(person1);//Person{name='男人', description='我是个男人'}

System.out.println(person2);//Person{name='女人', description='我是个女人'}

}

}

java5有了关键字enum如何定义枚举类?很多技术的发展与迭代跟人懒离不开关系,上面这个具有枚举意义的类,在有了enum关键字后,一些必要且重复的成分就可以省去了。

public enumPerson {/*** 提供两个枚举对象

* 1.final,不可被改变的,不允许外部对这两个对象进行更改操作

* 2.static,外部可以通过Person.MAN(类.静态变量)的方式得到该对象*/MAN("男人","我是个男人"), //省去了 “private final static Person = new Person”

WOMAN("女人","我是个女人");//定义两个成员变量,不对外提供set方法

privateString name;privateString description;/*** 有枚举意义的类,必须提供给外部有限个已经确定的对象,那么该类的构造器必须置为private

* 让外部无法通过new的方式创建该对象,否则如果能通过new创建多个该对象,就不是枚举意义的类。*/Person(String name,String description){//省去了private关键字

this.name =name;this.description =description;

}/*** main测试*/

public static voidmain(String[] args) {

Person person1=Person.MAN;

Person person2=Person.WOMAN;

System.out.println(person1.toString());//MAN

System.out.println(person2.toString());//WOMAN

}

}

有了enum,创建一个枚举类是不是更简单了呢?

二、Enum类

上面使用enum关键字创建枚举类的代码中,我特意删除了toString()方法,而在main测试的结果中,person1、person2分别打印出了“MAN”、“WOMAN”字符串。

我们都知道如果一个类被创建的时候,即使没有显示地指定继承父类Object类时,实际上也会将Object作为父类,拥有Object类中的toString()、equals()、hashcode()等方法。

就拿toString()方法来说,如果子类中没有去重写该方法,那么子类对象调用toString()方法时,打印出的应该会是一个地址值,类似于Person@1b6d3586,然而却打印出了“MAN”、“WOMAN”。

这说明了什么?要么被enum修饰的类,默认进行了隐式的toString()方法重写,又或者被enum修饰的类的父类,另有其“类”,不是直接继承于Object,在该父类中对toString()方法进行了重写。

到底真相是什么?

/*** main测试*/

public static voidmain(String[] args) {

Person person1=Person.MAN;

Person person2=Person.WOMAN;

System.out.println(person1.toString());//MAN

System.out.println(person2.toString());//WOMAN

/*** 通过反射,验证了枚举类Person的直接父类不是Object而是java.lang.Enum*/Class> superclass =person1.getClass().getSuperclass();

System.out.println(superclass);//class java.lang.Enum

}

哦,原来枚举类都会默认继承java.lang.Enum这个类,那来看看这个类提供了哪些子类可以调用的方法?

/*** main测试*/

public static voidmain(String[] args) {//获取Person枚举类定义的所有对象

Person[] values =Person.values();for(Person person : values){

System.out.println(person);

}//根据枚举对象名获取枚举对象,注意:如果获取不到,将会抛出异常,而不是返回null值。

Person person1 = Person.valueOf("MAN");

System.out.println(person1);//使用父类Enum提供的方法,获取指定类型的枚举对象

Person person2 = Enum.valueOf(Person.class, "WOMAN");

System.out.println(person2);

//......

}

看了上面代码,细心的同学,可能又会发现了,java.lang.Enum类中并没有 values()、valueOf(String name)的两个静态方法,而Person这个枚举类中,也没有定义这两个方法。

那么这两个方法从何而来?反编译一下看看。

60b0e6faa7adfcaa94dab441eecdac48.png

可以看到,在创建该类的时候,编译器自动加上了静态的values()和valueOf()方法。甚至还有意外的发现,反编译后可以看到枚举类是final的,所以枚举类也具有final修饰的类的相关特性。

三、枚举类的应用

1.Java中很多类都使用到了枚举,比如Thread类中用于定义线程状态的public enum State枚举类等。

2.常见的其他应用,单例模式---枚举实现,这个重点说一下。

public enumSingleton {//该类唯一的一个实例

INSTANCE;/*** 供外部访问实例的方法*/

public staticSingleton getInstance(){returnINSTANCE;

}

}

优点一:有效地避免了反射攻击

提到单例模式,可能我们都会想到饿汉模式(天生线程安全),懒汉模式(volatile+ 双重检测),这两种方式虽然都私有化了构造器,“希望”外部能根据公有方法获取该类的唯一实例。

但是,在有反射攻击的情况下,也只是希望了。先看一个暴力反射的例子

classAnimal {//私有化构造方法

privateAnimal(){

}

}classTest{public static void main(String[] args) throwsException {

Class clazz = Animal.class;//暴力反射,获取到Animal私有的无参构造方法

Constructor constructor =clazz.getDeclaredConstructor();

constructor.setAccessible(true);

Animal animal=constructor.newInstance();

System.out.println(animal);//Animal@677327b6

}

}

可以看到,即使Animal类私有化了构造方法,仍然能被反射获得其私有化的构造方法,完成对象的创建。

如果对枚举类使用反射攻击:

1 public enumSingleton {2

3 //该类唯一的一个实例

4 INSTANCE;5

6 /**

7 * 供外部访问实例的方法8 */

9 public staticSingleton getInstance(){10 returnINSTANCE;11 }12

13 }14

15 classTest{16

17 public static void main(String[] args) throwsException {18 Class clazz = Singleton.class;19

20 /**

21 * 暴力反射,获取到Singleton私有的无参构造方法22 * 抛出异常:Exception in thread "main" java.lang.NoSuchMethodException23 * 说明枚举类没有无参构造方法24 */

25 Constructor constructor =clazz.getDeclaredConstructor();26 constructor.setAccessible(true);27

28 //根据构造器创建对象

29 Singleton singleton =constructor.newInstance();30 System.out.println(singleton);31

32 }33 }

在上面25行位置抛出了java.lang.NoSuchMethodException,说明了枚举类没有提供无参的构造方法,这也是一种保护。

但是,枚举类父类Enum类中还存在一个protected Enum(String name, int ordinal)有参构造方法,使用该方法,看看能否通过反射获取对象。

1 public enumSingleton {2

3 //该类唯一的一个实例

4 INSTANCE;5

6 /**

7 * 供外部访问实例的方法8 */

9 public staticSingleton getInstance(){10 returnINSTANCE;11 }12

13 }14

15 classTest{16

17 public static void main(String[] args) throwsException {18 Class clazz = Singleton.class;19

20 /**

21 * 暴力反射,获取到Singleton父类Enum的有参构造方法22 */

23 Constructor constructor = clazz.getDeclaredConstructor(String.class,int.class);24 constructor.setAccessible(true);25

26 /**

27 * 根据构造器创建对象28 * java.lang.IllegalArgumentException: Cannot reflectively create enum objects29 */

30 Singleton singleton = constructor.newInstance("INSTANCE",1);31 System.out.println(singleton);32

33 }34 }

这次虽然构造方法找到了,但是在30行代码创建实例的时候,抛出了不能反射创建对象的异常。为什么不能反射?原因在于:

1 publicT newInstance(Object ... initargs)2 throwsInstantiationException, IllegalAccessException,3 IllegalArgumentException, InvocationTargetException4 {5 if (!override) {6 if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {7 Class> caller =Reflection.getCallerClass();8 checkAccess(caller, clazz, null, modifiers);9 }10 }11 //此处说明了原因,如果构造函数是一个枚举类型,抛出异常。

12 if ((clazz.getModifiers() & Modifier.ENUM) != 0)13 throw new IllegalArgumentException("Cannot reflectively create enum objects");14 ConstructorAccessor ca = constructorAccessor; //read volatile

15 if (ca == null) {16 ca =acquireConstructorAccessor();17 }18 @SuppressWarnings("unchecked")19 T inst =(T) ca.newInstance(initargs);20 returninst;21 }

由此可见,使用枚举可以避免反射攻击。

优点二:是阻止反序列化时重新创建对象的一个有效方式(阻止序列化攻击)

先看一个序列化攻击案例:

1 public class Animal implementsSerializable {2

3 private static final long serialVersionUID = -7252563037661450268L;4

5 private static Animal animal = newAnimal();6

7 privateAnimal() { }8

9 public staticAnimal getInstance() {10 returnanimal;11 }12 }13

14 classTest {15

16 public static void main(String[] args) throwsException {17 Animal instance =Animal.getInstance();18

19 //序列化对象

20 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serializeFile"));21 oos.writeObject(instance);22

23 //再从序列化文件中反序列化出对象

24 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serializeFile"));25 Animal instance1 =(Animal) ois.readObject();26

27 //比较两个对象是否相同

28 System.out.println(instance == instance1);//false

29 }30 }

在上面第28行代码中,可以看到,通过序列化和反序列化后,得到了两个不同的对象,就违背了单例的初衷。

原因在于:“任何一个 readObject 方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例”,关于这句话的详细介绍,请百度查看。

那么如果反序列化枚举类对象,会发生什么呢?

1 public enumSingleton {2

3 //该类唯一的一个实例

4 INSTANCE;5

6 /**

7 * 供外部访问实例的方法8 */

9 public staticSingleton getInstance() {10 returnINSTANCE;11 }12

13 }14

15 classTest {16

17 public static void main(String[] args) throwsException {18 Singleton instance =Singleton.getInstance();19

20 //序列化对象

21 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serializeFile"));22 oos.writeObject(instance);23

24 //再从序列化文件中取出该对象

25 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serializeFile"));26 Singleton instance1 =(Singleton) ois.readObject();27

28 //比较两个对象是否相同

29 System.out.println(instance == instance1);//true

30 }31 }

可以看到在29行,打印的结果显示反序列化枚举类对象后,得到的对象与序列化前的对象是相同的。

正如“对于实例控制,枚举类型优先于readResolve”所说一样,虽然重写readResolve方法也可以控制实例,但是枚举不香吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值