1.反射
1.1 定义
Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
1.2 原理
Java程序运行前,首先会通过编译将Java代码生成.class文件;程序运行过程中使用某个类时,如果该类还未被加载到内存中,系统会将该类的.class字节码文件读入内存,同时JVM会产生一个java.lang.Class对象代表该.class字节码文件。
通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类所包括的方法、构造器和属性等,反射的工作原理就是通过这些对象来执行实际的功能,例如调用方法、创建实例等。
类名
|
用途
|
Class类
|
代表类的实体,在运行的Java应用程序中表示类和接口
|
Field类
|
代表类的成员变量/类的属性
|
Method类
|
代表类的方法
|
Constructor类
|
代表类的构造方法
|
1.3 反射对象类中的常用方法(方法的使用在1.4中)
常用获得类相关的方法(以下方法返回值与Class相关)
方法
|
用途
|
getClassLoader()
|
获得类的加载器
|
getDeclaredClasses()
|
返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的)
|
forName(String className)
|
根据类名返回类的对象
|
newInstance()
|
创建类的实例
|
getName()
|
获得类的完整路径名字
|
常用获得类中属性相关的方法(以下方法返回值为Field相关)
方法
|
用途
|
getField(String name)
|
获得某个公有的属性对象(指定参数)
|
getFields()
|
获得所有公有的属性对象
|
getDeclaredField(String name)
|
获得某个属性对象(指定参数)
|
getDeclaredFields()
|
获得所有属性对象
|
获得类中构造器相关的方法(以下方法返回值为Constructor相关)
方法
|
用途
|
getConstructor(Class…<?> parameterTypes)
|
获得该类中与参数类型匹配的公有构造方法
|
getConstructors()
|
获得该类的所有公有构造方法
|
getDeclaredConstructor(Class…<?> parameterTypes)
|
获得该类中与参数类型匹配的构造方法
|
getDeclaredConstructors()
|
获得该类所有构造方法
|
获得类中方法相关的方法(以下方法返回值为Method相关)
方法
|
用途
|
getMethod(String name, Class…<?> parameterTypes)
|
获得该类某个公有的方法
|
getMethods()
|
获得该类所有公有的方法
|
getDeclaredMethod(String name, Class…<?> parameterTypes)
|
获得该类某个方法
|
getDeclaredMethods()
|
获得该类所有方法
|
注意:在方法中声明“Declared”后,就可以获得对象私有的属性和方法,但是必须将该对象的accessible=true,否则抛出IllegalAccessException异常。
1.4 反射示例
首先我们在一个包下写一个类,该类中有私有和公有的各类成员变量和方法,然后我们利用反射来获取该类的方法、构造器或属性等。
package reflect;
public class Person {
//私有属性name
private String name = "张三";
//公有属性age
public int age;
//不带参数的构造方法
public Person() {
System.out.println("Person()");
//带参数的私有构造方法
private Person(String name, int age) {
this.age = age;
this.name = name;
System.out.println("Person(String,name)");
private void eat() {
System.out.println("I am eating");
public void sleep() {
System.out.println("I am sleeping");
private void function(String str) {
System.out.println(str);
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
}
1.4.1 获取反射中的Class对象
想获取类的方法、构造器或属性等,必须先获取该类的Class对象;
且一个类只有一个Class对象,所以代码示例中的打印结果全为true.
//1.使用 Class.forName() 静态方法,需要知道类的全路径名
Class<?> c1=Class.forName("reflect.Person");
//2.使用.class方法
Class<?> c2=Person.class;
//3.使用类对象的getClass()方法
Person person=new Person();
Class<?> c3=person.getClass();
System.out.println(c1==c2);
System.out.println(c1==c3);
System.out.println(c2==c3);
true
1.4.2 通过反射创建类对象
Class<?> c=Person.class;
//1.通过Class对象的newInstance()方法
Person person1=(Person) c.newInstance();
//2.通过 Constructor 对象的 newInstance() 方法
Constructor<?> constructor= c.getConstructor();
Person person2=(Person) constructor.newInstance();
1.4.3 通过反射获取类属性、方法、构造器
Class<?> c=Person.class;
//获取name属性
Field field=c.getDeclaredField("name");
//获取自身和父类的公有属性(不包括私有的)
Field[] fields= c.getFields();
//获取自身的公有和私有属性
Field[] declaredFields = c.getDeclaredFields();
//获取function方法,参数为String
Method method= c.getMethod("function", String.class);
//获取自身和父类的公有方法(不包括私有的)
Method[]methods= c.getMethods();
//获取自身的公有和私有方法
Method[] declaredMethods = c.getDeclaredMethods();
//获取无参的构造方法
Constructor<?> constructor=c.getConstructor();
//获取参数为String和int的构造方法
Constructor<?> constructor1=c.getDeclaredConstructor(String.class,int.class);
//获取自身和父类的构造方法(不包括私有的)
Constructor<?>[]constructors=c.getConstructors();
1.4.4 通过反射获取属性值及执行方法
public static void main(String[] args) {
try {
Class<?> c=Person.class;
//通过构造方法创建对象
Constructor<?> constructor= c.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Person person=(Person) constructor.newInstance("王五",18);
System.out.println(person);
System.out.println("=====================");
//字段属性
Field field=c.getDeclaredField("name");
field.setAccessible(true);
field.set(person,"李四");
System.out.println(person);
System.out.println("=====================");
//方法属性
Method method= c.getDeclaredMethod("function", String.class);
method.setAccessible(true);
method.invoke(person,"我是通过反射给你传参的");//执行方法
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
Person(String,name)
Person{name='王五', age=18}
=====================
Person{name='李四', age=18}
=====================
我是通过反射给你传参的
1.5 反射优点和缺点
优点:
-
反射增加了程序的灵活性。
-
反射可以让程序有很好的拓展性。
-
反射可以在不知道会运行哪一个类的情况下,获取到类的信息,创建对象以及操作对象;我们无需将类型硬编码写死,方便程序拓展,降低耦合度。
缺点:
-
反射更容易出现运行时错误。
-
使用显式的类和接口,编译器能帮我们做类型检查,但使用反射需要运行时才知道类型,此时编译器也爱莫能助。
-
-
反射性能不高。
-
反射是一种解释操作,在访问字段和调用方法前,需要查找到对应的 Field 和 Method,性能要低一些。
-
因此反射机制主要应用在对灵活性和拓展性要求较高的系统框架上。
-
-
反射会带来安全性问题。
-
反射可以随意修改私有属性和访问私有方法,破坏了类的封装,可能导致逻辑错误或者存在安全隐患。
2.枚举
2.1 背景及定义
枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来
public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;
、
但是这种常量举例有不好的地方,比如:碰巧有个数字1,但是它可能会误认为是RED,现在我们可以直接用枚举类型来进行组织,这样就有了区分
public enum TestEnum {
RED,BLACK,GREEN;
}
本质:是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示的继承 Enum ,但是其默认继承了
这个类。
2.2 枚举的使用
1.switch语句
public enum TestEnum {
RED,BLACK,GREEN,WHITE;
public static void main(String[] args) {
TestEnum testEnum=TestEnum.BLACK;
switch (testEnum) {
case RED: System.out.println("red");
break;
case BLACK: System.out.println("black");
break;
case WHITE: System.out.println("WHITE");
break;
case GREEN: System.out.println("black");
break;
default:
break;
black
Process finished with exit code 0
2.常用方法
Enum类的常用方法
方法名称
|
描述
|
values()
|
以数组形式返回枚举类型的所有成员
|
ordinal()
|
获取枚举成员的索引位置
|
valueOf()
|
将普通字符串转换为枚举实例
|
compareTo()
|
比较两个枚举成员在定义时的顺序
|
示例:
public enum TestEnum {
RED, BLACK, GREEN, WHITE;
public static void main(String[] args) {
//values()方法
TestEnum[] testEnums = TestEnum.values();
for (int i = 0; i < testEnums.length; i++) {
//ordinal()方法
System.out.println(testEnums[i] + " 序号: " + testEnums[i].ordinal());
System.out.println("==============");
//valueOf()方法
TestEnum testEnum = TestEnum.valueOf("GREEN");
System.out.println(testEnum);
System.out.println("==============");
TestEnum testEnum1 = TestEnum.BLACK;
TestEnum testEnum2 = TestEnum.RED;
//compareTo()方法
System.out.println(testEnum1.compareTo(testEnum2));
System.out.println(BLACK.compareTo(RED));
System.out.println(RED.compareTo(BLACK));
RED 序号: 0
BLACK 序号: 1
GREEN 序号: 2
WHITE 序号: 3
==============
GREEN
==============
Process finished with exit code 0
事实上在Java中,枚举就是一个类,所以我们在定义枚举类时,如果枚举对象有参数,需要提供相应的构造函数。
枚举的构造方法默认是私有的
public enum TestEnum {
RED("red",2), BLACK("black",3),
GREEN("green",4), WHITE("white",5);
public String color;
public int ordinal;
TestEnum(String color, int ordinal) {
this.color = color;
this.ordinal = ordinal;
}
2.3 枚举和反射
我们了解到,任何一个类,哪怕其属性或者方法是私有的,我们都可以通过反射来获取到该实例对象,枚举的构造方法也是私有的,我们可以尝试是否可以通过反射来获取到。
我们还以2.2中最后的枚举类为例:
public enum TestEnum {
RED("red", 2), BLACK("black", 3),
GREEN("green", 4), WHITE("white", 5);
public String color;
public int ordinal;
TestEnum(String color, int ordinal) {
this.color = color;
this.ordinal = ordinal;
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> c = TestEnum.class;
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
TestEnum testEnum = (TestEnum) constructor.newInstance("海雾蓝", 9);
System.out.println(testEnum);
Exception in thread "main" java.lang.NoSuchMethodException: TestEnum.<init>(java.lang.String, int)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at TestEnum.main(TestEnum.java:19)
Process finished with exit code 1
运行后报错 java.lang.NoSuchMethodException: TestEnum.<init>(java.lang.String, int),意思是没有对应的构造方法,但我们所提供的枚举类的构造方法就是两个参数String和int。
这是因为所有的枚举类都默认继承于 java.lang.Enum ,所以在构造子类时,要先帮助父类进行构造,Enum的源码:
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
所以我们需要提供四个参数来完成构造
public enum TestEnum {
RED("red", 2), BLACK("black", 3),
GREEN("green", 4), WHITE("white", 5);
public String color;
public int ordinal;
TestEnum(String color, int ordinal) {
this.color = color;
this.ordinal = ordinal;
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> c = TestEnum.class;
Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class,String.class, int.class);
constructor.setAccessible(true);
TestEnum testEnum = (TestEnum) constructor.newInstance("海雾蓝", 9);
System.out.println(testEnum);
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at TestEnum.main(TestEnum.java:21)
Process finished with exit code 1
此时仍然报错,但报错原因变为了java.lang.IllegalArgumentException: Cannot reflectively create enum objects,我们从源码中查找原因
也就是说,枚举在这里被过滤了,不能通过反射获取枚举类的实例。
2.4 总结
枚举本身就是一个类,其构造方法默认为私有的,且都是默认继承与 java.lang.Enum
枚举可以避免反射和序列化问题
枚举优点缺点
优点:
1.枚举常量更简单安全 。 2. 枚举具有内置方法 ,代码更优雅
缺点:
不可继承,无法扩展