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 true 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 ============== 1 1 -1 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. 枚举具有内置方法 ,代码更优雅
缺点:
不可继承,无法扩展