Java反射机制
一、反射的概念
Java反射是基于正射而言的,所谓正射就是通过new的形式创建对象,也就是我们常说的new()方法。我们知道,代码编译后.java的文件被编译成.class的二进制文件,在运行过程中只有用到了这个类JVM类加载器才会将.class文件加载到内存中。
Java反射是指Java在运行时具有自观察的能力,能对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象都能调用它的方法和属性;这种动态获取信息以及动态调用该对象的方法的功能称为Java的反射机制。
二、为什么需要Java反射机制
以Java的工厂模式举例:
产品接口类
public interface Shape {
void produce();
}
产品实现类
public class Square implements Shape {
@Override
public void produce() {
System.out.println("produce Square!");
}
}
public class Rectangle implements Shape {
@Override
public void produce() {
System.out.println("produce Rectangle!");
}
}
public class Circle implements Shape {
@Override
public void produce() {
System.out.println("produce Circle!");
}
}
工厂类
public class ShapeFactory {
//使用 getShape 方法获取形状类型的对象
public Shape getShape(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
}
测试类
@SpringBootTest
public class FactoryTest {
@Test
public void test1(){
ShapeFactory shapeFactory = new ShapeFactory();
//获取 Circle 的对象,并调用它的 draw 方法
Shape shape1 = shapeFactory.getShape1("CIRCLE");
//调用 Circle 的 draw 方法
shape1.produce();
//获取 Rectangle 的对象,并调用它的 draw 方法
Shape shape2 = shapeFactory.getShape1("RECTANGLE");
//调用 Rectangle 的 draw 方法
shape2.produce();
//获取 Square 的对象,并调用它的 draw 方法
Shape shape3 = shapeFactory.getShape1("SQUARE");
//调用 Square 的 draw 方法
shape3.produce();
}
}
测试结果
produce Circle!
produce Rectangle!
produce Square!
在工厂模式下,如果需要new什么对象就在shapeFactory.getShape()中传入需要的图形,看似很方便其实潜在这一个问题。当如果需求变了,需求不在是CIRCLE、RECTANGLE、SQUARE这三种图形,而是第四种图形,就需要修改工厂类的getShape()方法,增加一个if判断,这种方式需要修改源代码的方式及其不优雅。因此,引出了我们接下来要讨论的问题,如何通过Java反射的形式动态的获取对象。
三、通过Java反射的方式动态的获取对象
修改上面工厂模式的工厂类
public class ShapeFactory {
//使用 getShape 方法获取形状类型的对象
public Shape getShape1(String shapeType){
if(shapeType == null){
return null;
}
if(shapeType.equalsIgnoreCase("CIRCLE")){
return new Circle();
} else if(shapeType.equalsIgnoreCase("RECTANGLE")){
return new Rectangle();
} else if(shapeType.equalsIgnoreCase("SQUARE")){
return new Square();
}
return null;
}
public Shape getShape2(String className){
try {
Class clazz = Class.forName(className);
Constructor con = clazz.getConstructor();
return (Shape) con.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}
添加新产品类
public class Triangle implements Shape {
@Override
public void produce() {
System.out.println("produce Triangle!");
}
}
测试类修改
@SpringBootTest
public class FactoryTest {
@Test
public void test2(){
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.getShape2("com.franky.modules.factory.product.impl.Square");
shape1.produce();
Shape shape2 = shapeFactory.getShape2("com.franky.modules.factory.product.impl.Rectangle");
shape2.produce();
Shape shape3 = shapeFactory.getShape2("com.franky.modules.factory.product.impl.Circle");
shape3.produce();
Shape shape4 = shapeFactory.getShape2("com.franky.modules.factory.product.impl.Triangle");
shape4.produce();
}
}
控制台打印结果
produce Square!
produce Rectangle!
produce Circle!
produce Triangle!
改造后的工厂类,当需要增加新的产品后则不需要再进行修改工厂类。在程序运行过程种,需要调用哪个类就加载哪个类。这就是Java反射的便利之处。
四、Java反射优劣势
通过上面得例子,我们已经体会到了Java反射在某些方便的便利之处,Java反射除了优势肯定还有劣势,这里我们详细的分析下Java反射的优劣势。
1、优势
- 增加程序的灵活性,避免将程序写死到代码里
- 代码简洁,提高代码复用率
- 破坏类的封装,可以访问类的私有方法、属性;
2、劣势
- 相比于直接new的方式,Java反射操作的效率会比非反射操作的效率低很多,因为中间需要多个检查和解析步骤,JVM无法进行优化;
- 反射提高了代码的复杂性,维护代码较为困难;
- 反射机制使得类的私有属性和方法暴露出来,可能导致其他意外副作用,降低了可移植性。
- 反射需要运行时权限,在安全管理器下运行时可能不存在。对于在受限安全上下文运行的代码,例如Applet中,这是一个重要的考虑因素;
五、Java反射的使用
以下是测试使用的类
@Data
@TableName("student")
@ApiModel(value="学生实体")
public class Student {
@TableId(type = IdType.UUID)
@ApiModelProperty(value="id")
private String id;
@ApiModelProperty(value="姓名")
private String name;
@ApiModelProperty(value="年龄")
public String age;
@ApiModelProperty(value="年级")
private String grade;
@ApiModelProperty(value="创建人")
private String createBy;
@TableField(fill = FieldFill.INSERT)
@ApiModelProperty(value="创建时间")
private Date createTime;
@ApiModelProperty(value="更新人")
private String updateBy;
@TableField(fill = FieldFill.UPDATE)
@ApiModelProperty(value="更新时间")
private Date updateTime;
@ApiModelProperty(value="所属租户")
public String corpCode;
@TableLogic
@ApiModelProperty(value="删除状态 0正常 1已删除")
public String delFlag;
public Student() {
}
public Student(String id, String name) {
this.id = id;
this.name = name;
}
private Student(String id, String name, String age, String grade) {
this.id = id;
this.name = name;
this.age = age;
this.grade = grade;
}
private void sayHello(String name) {
System.out.println("hello: " + name);
}
public void sayHi(String name) {
System.out.println("hi: " + name);
}
}
1、获取Class对象
获取Class对象的方式有三种,不管哪种方式,都是获取同一个对象。
- 类明.class 方式获取Class对象
Class clazz1 = Student.class;
- 实例.getClass() 方式获取Class对象
Student student = new Student();
Class clazz2 = student.getClass();
- Class.forName(className) 方式获取Class对象
try {
Class clazz3 = Class.forName("com.franky.modules.student.entity.Student");
System.out.println("clazz3: " + clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
测试类
@SpringBootTest
public class ReflectTest {
@Test
public void test(){
// 类明.class 方式获取Class对象
Class clazz1 = Student.class;
System.out.println("clazz1: " + clazz1);
// 实例.getClass() 方式获取Class对象
Student student = new Student();
Class clazz2 = student.getClass();
System.out.println("clazz2: " + clazz2);
// Class.forName(className) 方式获取Class对象
try {
Class clazz3 = Class.forName("com.franky.modules.student.entity.Student");
System.out.println("clazz3: " + clazz3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
控制台输出结果
clazz1: class com.franky.modules.student.entity.Student
clazz2: class com.franky.modules.student.entity.Student
clazz3: class com.franky.modules.student.entity.Student
2、通过获取的Class类获取实例对象
- 通过Class对象调用newInstance()方法,此方法有一定的局限性,只能调用无参构造方法;
Class clazz1 = Student.class;
System.out.println("clazz1: " + clazz1);
try {
// 通过Class对象调用newInstance()方法
Student student1 = (Student)clazz1.newInstance();
System.out.println(student1.toString());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
- 通过Constructor构造器调用newInstance()方法,此方法可以调用任意的构造方法;
Class clazz1 = Student.class;
try {
Constructor constructor = clazz1.getConstructor(String.class, String.class);
try {
Student student2 = (Student) constructor.newInstance("123", "小米");
System.out.println(student2.toString());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
3、获取类中的变量Field
- Field[] getFields():获取类中所有被public修饰的所有属性;
Class clazz = Student.class;
Field[] fields = clazz.getFields();
- Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰;
Class clazz = Student.class;
Field field2 = clazz.getField("age");
- Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量;
Class clazz = Student.class;
Field[] fields2 = clazz.getDeclaredFields();
- Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量;
Class clazz = Student.class;
Field field1 = clazz.getDeclaredField("name");
4、获取类中的方法Method
- Method[] getMethods():获取类中被public修饰的所有方法;
- Method getMethod(String name, Class…<?> paramTypes):根据方法名和参数类型获取对应方法,该方法必须被public修饰;
Class clazz = Student.class;
Student student = (Student)clazz.newInstance();
Method method1 = clazz.getMethod("sayHi", String.class);
method1.invoke(student,"maomao");
- Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法;
- Method getDeclaredMethod(String name, Class…<?> paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法,在调用私有方法前,需要先获取权限setAccessible(true);
Class clazz = Student.class;
Student student = (Student)clazz.newInstance();
Method method2 = clazz.getDeclaredMethod("sayHello", String.class);
method2.setAccessible(true);
method2.invoke(student,"caiying");
5、获取类中的构造器Constructor
构造方法归根结底也是方法,除了使用获取Method方法获取构造器外,与此类似Java还提供了获取构造器的独立方法。
- Constuctor[] getConstructors():获取类中所有被public修饰的构造器;
- Constructor getConstructor(Class…<?> paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰;
Class clazz = Student.class;
Constructor constructor = clazz.getConstructor(String.class, String.class);
Student student = (Student)constructor.newInstance("123", "maomao");
- Constructor[] getDeclaredConstructors():获取类中所有构造器,私有构造方法使用前需要先获取权限setAccessible(true);
- Constructor getDeclaredConstructor(class…<?> paramTypes):根据参数类型获取对应的构造器,私有构造方法使用前需要先获取权限setAccessible(true);
Class clazz = Student.class;
Constructor constructor2 = clazz.getDeclaredConstructor(String.class, String.class, String.class, String.class);
constructor2.setAccessible(true);
Student student2 = (Student)constructor2.newInstance("456", "caiying", "28", "6");
System.out.println(student2.toString());