"理想者最可能疯狂。"
作者:Mylvzi
文章主要内容:Java反射,枚举讲解
"
作者:Mylvzi
文章主要内容:数据结构之Map/Set讲解+硬核源码剖析
Java的反射机制(reflection)是指在Java程序在运行状态过程中,能够拿到任意类的任意属性和任意方法,从而能够动态的调用,修改属性方法,这种机制就叫做"反射"
说明:
1.为什么强调"运行状态":在下面会讲到,.java程序经过编译,再到JVM上运行,最终每个.java文件都会生成一个类Class,我们的所有反射操作都是根据生成的Class类完成的
2.拿到任意类的任意属性和方法,包括他的私有属性和私有方法,这看起来和封装冲突了,因为我们之前讲过私有属性和私有方法只能在类内使用,类外无法使用,其实通过反射机制可以实现在类外使用私有属性和私有方法
Java文件被编译后会生成一个.class文件,这个.class文件会被JVM解析为一个类,这个类就是java.lang.Class,当程序运行时,对应的Java文件就是Class类的一个实例化对象,我们通过反射机制就能动态的调用这个对象的所有属性和方法
通过Class类对象的静态方法forName
Class.forName("反射对象的全路径");
代码验证:
Class c1;
try {
// forName这个方法会抛出异常 要去解决异常
c1 = Class.forName("reflectDemo.Student");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
一定要记得处理异常
// 2.使用.class方法
Class c2 = Student.class;
先实例化出一个需要反射类的实例化对象,再通过此对象的getClass方法获取
// 3.使用getClass方法 需要先实例化一个需要反射的对象
Student student = new Student();
Class c3 = student.getClass();
以上三种方式都能获取到Class对象,那有人发现,我分别定义了三个变量来接受Class对象,分别是c1,c2,c3,那他们三个一定是不同的对象吧?实际上,这三个变量是同一个对象,因为Class对象只有一个,因为一个.java文件对应一个Class对象
// 都输出true
System.out.println(c1.equals(c2));
System.out.println(c1.equals(c3));
System.out.println(c2.equals(c3));
创建对象 即实例化一个反射获得对象,通过newInstance方法使用
newInstance() 创建类的实例
// 创建对象 即实例化反射得到的Class对象
public static void reflectNewInstance(){
try {
// 先获取一个Class对象
Class> c1 = Class.forName("reflectDemo.Student");
// 此处要进行类型转换
Student student = (Student) c1.newInstance();
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
说明:
获取到反射对象的私有构造方法
- getConstructor(Class... parameterTypes) 获得该类中与参数类型匹配的公有构造方法
- getConstructors() 获得该类的所有公有构造方法
- getDeclaredConstructor(Class... parameterTypes) 获得该类中与参数类型匹配的构造方法
- getDeclaredConstructors() 获得该类所有构造方法
public static void reflectPrivateConstructor() {
// 先获取一个Class对象
try {
// 1.先获取一个Class对象
Class> c1 = Class.forName("reflectDemo.Student");
// 2.获取私有的构造方法
Constructor con = (Constructor) c1.getDeclaredConstructor(String.class,int.class);
// 3.使用私有的构造方法需要添加"保险"
con.setAccessible(true);
// 4.创建一个反射的对象
Student student = con.newInstance("张三",100);
// 5.打印
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateField() {
try {
Class c1 = Class.forName("reflectDemo.Student");
// 使用Field类接受一个私有属性 此处存放你要获取的私有属性的名称
Field field = c1.getDeclaredField("name");
// 私有的都需要添加保险
field.setAccessible(true);
// 创建一个类的实例
Student student = (Student) c1.newInstance();
// 改变私有属性的值
// 第一个参数是你要修改的对象 第二个参数是你修改的值
field.set(student,"zhizi");
System.out.println(student);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void reflectPrivateMethod() {
try {
// 1.先创建一个对象
Class c1 = Class.forName("reflectDemo.Student");
// 获取私有的方法
Method method = c1.getDeclaredMethod("function",String.class);
// 私有的都需要添加保险
method.setAccessible(true);
// 创建一个类的实例
Student student = (Student) c1.newInstance();
// 修改方法
method.invoke(student,"我是修改的私有构造方法");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
注意:方法的修改是通过method.invoke,属性的修改是field.set
优点:
- 对于任意一个类,都能获取其所有属性和方法
- 增加程序的灵活性,降低耦合性,提高自适应能力
- 开发框架
缺点:
- 使用反射会降低效率
- 反射技术绕过了源代码技术,带来维护相关的问题,同时也增加代码的复杂性
枚举是在JDK1.5以后引入的。和之前C语言中学习过的枚举常量类似,都是将一组常量有序组织起来的集合
在这之前定义常量的方式
public static final int RED = 1;
public static final int GREEN = 2;
public static final int BLACK = 3;
使用枚举常量
public enum Color{
Black,Red,Blue;
}
本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了 这个类。
public enum Color{
Black,Red,Blue;
}
public static void main(String[] args) {
Color color = Color.Black;
switch (color) {
case Red:
System.out.println("Red");
break;
case Blue:
System.out.println("Blue");
break;
case Black:
System.out.println("Black");
break;
default:
break;
}
}
直接将枚举类型定义为一个类
public enum TestEnum {
RED,BLACK,GREEN,WHITE;
public static void main(String[] args) {
//拿到枚举实例BLACK
TestEnum testEnum = TestEnum.BLACK;
//拿到枚举实例RED
TestEnum testEnum21 = TestEnum.RED;
}
}
- values() 以数组形式返回枚举类型的所有成员
- ordinal() 获取枚举成员的索引位置
- valueOf() 将普通字符串转换为枚举实例
- compareTo() 比较两个枚举成员在定义时的顺序
public enum Color{
Black,Red,Blue;
}
public static void main(String[] args) {
// 1.values方法 以数组形式返回枚举类型的所有成员
Color[] colors = Color.values();
for (Color color : colors) {
System.out.println(color);
}
// 2.ordinal获取枚举成员的索引位置
int index = Color.Red.ordinal();
System.out.println(index);// 输出1 索引是从 0开始
// 3.valueOf 将普通字符串转换为枚举实例
Color color = Color.valueOf("Red");
System.out.println(color);// 输出red
// 4.compareTo
// 计算:Red.ordinal - Black.ordinal
System.out.println(Color.Red.compareTo(Color.Black));// 输出1 先定义Black
}
注意:在Java的Enum的源码中,并不包好values方法,但却可以使用,这是为什么呢?
是因为values()
方法是由编译器在编译时自动添加到每个枚举类中的。
这个方法会返回一个包含枚举类型所有值的数组。虽然在 enum
类的源代码中看不到 values()
方法的定义,但编译器在生成字节码时会自动添加这个方法,因此你可以在代码中直接调用 YourEnumType.values()
来获取该枚举类型中所有的枚举常量值。
枚举的构造方法是私有的
public enum TestEnum {
RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
private String name;
private int key;
/**
* 1、当枚举对象有参数后,需要提供相应的构造函数
* 2、枚举的构造函数默认是私有的 这个一定要记住
* @param name
* @param key
*/
private TestEnum (String name,int key) {
this.name = name;
this.key = key;
}
}
优点
- 枚举常量跟简单,更安全
- 枚举内部有内置方法,代码更优雅
缺点
- 不可继承,无法扩展
反射可以拿到任意类的任意属性和任意方法,对于枚举来说,其构造方法是私有的。是否也能通过反射拿到其构造方法呢?
public enum TestEnum {
RED("red",1),BLACK("black",2),WHITE("white",3),GREEN("green",4);
private String name;
private int key;
private TestEnum (String name,int key) {
this.name = name;
this.key = key;
}
public static void refletct() throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class c1 = Class.forName("TestEnum");
Constructor con = c1.getDeclaredConstructor(String.class,int.class);
con.setAccessible(true);
// 通过构造类con实例化一个对象
TestEnum testEnum = (TestEnum) con.newInstance("绿色",666);
System.out.println(testEnum);
}
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
refletct();
}
}
运行结果:
java.lang.NoSuchMethodException指没有对应的构造方法
为什么会报这个错误呢?为什么说没有指定的构造方法呢?问题出现在哪里?我们之前说过。所有的枚举类都是继承于java.lang.Enum,既然是继承,肯定继承了除父类构造方法之外的所有的属性和方法,而且子类要先帮助父类进行构造,按理说我们自己写的enum类中应该有类似于super()这样的方法进行父类的构造,而实际上我们并没有写,是违反了继承的规则么?不是的,枚举类比较特殊,虽然我们写的是两个参数,但实际上他还默认添加了两个参数,请看源码:
虽然我们写了两个参数String和int,但实际上他还有两个默认的参数String和int,也就是构造方法中要有四个参数,下面修改一下构造方法
//注意传入对应的参数,获得对应的构造方法来构造对象,当前枚举类是提供了两个参数分别是String和int。
Constructor> declaredConstructorStudent =
classStudent.getDeclaredConstructor(String.class,int.class,String.class,int.class)
运行结果:
此处又出现了类型转换异常,定位到错误代码出,发现报错的代码竟然是
让我们再去看下newInstance的源码