1、啥是反射
定义:JAVA 反射机制是在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。
作用:
动态(运行时)获取类的完整结构信息&调用对象的方法。
我们或多或少都听说过设计框架的时候会用到反射,例如 Spring 的 IOC 就用到了工厂模式和反射来创建对象,BeanUtils 的底层也是使用反射来拷贝属性。所以反射无处不在。
尽管我们日常开发几乎用不到反射,但是我们必须要搞懂反射的原理,因为它能帮我们理解框架设计的原理。
2、特点
2.1 优点
灵活性高。反射属于动态编译,只有到运行时才动态创建&获取对象实例。
编译方式说明:
1、静态编译:在编译时确定类型&绑定对象。如常见的使用new关键字创建对象。
2、动态编译:运行时确定类型&绑定对象。动态编译体现了Java的灵活性、多态特性&降低类之间的藕合性。
2.2 缺点
-
执行效率低
因为反射的操作主要通过
JVM
执行,所以时间成本会高于直接执行相同操作
1、因为接口的通用性,Java的invoke方法是传object和object[]数组的。基本类型参数需要装箱和拆箱,产生大量额外的对象和内存开销,频繁促发GC。
2、编译器难以对动态调用的代码提前做优化,比如方法内联。
3、反射需要按名检索类和方法,有一定的时间开销。
-
容易破坏类结构
因为反射操作绕过了源码,容易干扰类原有的内部逻辑
3、JVM加载类
我们写的 Java程序要放到 JVM中运行,所以要学习反射,首先要了解JVM加载类的过程。
- 1、我们写的.java文件叫做源代码
- 2、在一个类中写了一个 main 方法,然后点击 IDEA 的 run 按钮,JVM运行时会触发 jdk 的 javac 指令将源代码编译成 .class 文件,这个文件又叫做字节码文件。
- 3、JVM 的类加载器(可以理解成一个工具)通过一个类的全限定名来获取该类的二进制字节流,然后将该 class 文件加载到 JVM 的方法区中。
- 4、类加载器加载一个 .class 文件到方法区的同时会在堆中生成一个唯一的 Class 对象,这个 Class 包含这个类的成员变量、构造方法以及成员方法。
- 5、这个 Class 对象会创建与该类对应的对象实例。
所以表面上你 new 了一个对象,实际上当 JVM 运行程序的时候,真正帮你创建对象的是该类的 Class 对象。
也就是说反射其实就是 JVM 在运行程序的时候将你创建的所有类都封装成唯一一个 Class 对象。这个 Class 对象包含属性、构造方法和成员方法。你拿到了 Class 对象,也就能获取这三个东西。
你拿到了反射之后(Class)的属性,就能获取对象的属性名、属性类别、属性值,也能给属性设置值。
你拿到了反射之后(Class)的构造方法,就能创建对象。
你拿到了反射之后(Class)的成员方法,就能执行该方法。
知道了 JVM 加载类的过程,相信你应该更加深入的了解了反射的概念。
反射:JVM 运行程序 --> .java 文件 --> .class 文件 --> Class 对象 --> 创建对象实例并操作该实例的属性和方法
接下来我就讲一下反射中的相关类以及常用方法。
4、Class对象
4.1 获取Class对象
先建一个User类:
/**
* @author BaoFeng Huang
* @date 2022/4/10 13:46
*/
public class User {
private String name = "abao";
public String sex = "男";
public User(){
}
public User(String name, String sex) {
this.name = name;
this.sex = sex;
}
public void eat(){
System.out.println("人要吃饭");
}
private void run(){
System.out.println("人要跑步");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
获取 Class 对象的三种方式:
-
1、Class.forName(“全类名”)
全类名:包名+类名
Class userClass = Class.forName("com.test.model.User")
- 2、类名.class
Class userClass1 = User.class;
- 3、对象.getClass()
User user = new User();
Class userClass2 = user.getClass();
尽管有三种方式获取 Class 对象,但是我们一般采用上述第一种方式。
拿到 Class 对象之后,我们就可以操作与它相关的方法了。
4.2 获取类名
1、获取完整类名:包名+类名
getName()
Class userClass = Class.forName("com.test.model.User");
String name = userClass.getName();
System.out.println(name);
2、获取简单类名:不包括包名
getSimpleName()
Class userClass = Class.forName("com.test.model.User");
String simpleName = userClass.getSimpleName();
System.out.println(simpleName);
5、属性
5.1 获取属性
1、获取所有公有属性:public修饰
getFields()
Class userClass = Class.forName("com.test.model.User");
Field[] fields = userClass.getFields();
for(Field field : fields){
System.out.println(field);
}
打印结果:
2、获取单个公有属性
getField("属性名")
Class userClass = Class.forName("com.test.model.User");
Field field = userClass.getField("sex");
System.out.println(field);
3、获取所有属性:公有+私有
getDeclaredFields()
Class userClass = Class.forName("com.test.model.User");
Field[] fields = userClass.getDeclaredFields();
for(Field field : fields){
System.out.println(field);
}
打印结果:
4、获取单个属性:公有或者私有
getDeclaredField("属性名")
Class userClass = Class.forName("com.test.model.User");
Field nameField = userClass.getDeclaredField("name");
Field sexField = userClass.getDeclaredField("sex");
System.out.println(nameField);
System.out.println(sexField);
5.2 操作属性
1、获取属性名称
getName()
Class userClass = Class.forName("com.test.model.User");
Field nameField = userClass.getDeclaredField("name");
System.out.println(nameField.getName());
2、获取属性类型
getType()
Class userClass = Class.forName("com.test.model.User");
Field nameField = userClass.getDeclaredField("name");
System.out.println(nameField.getType());
3、获取属性值
get(object)
Class userClass = Class.forName("com.test.model.User");
Field sexField = userClass.getDeclaredField("sex");
User user = new User();
System.out.println(sexField.get(user));
注:通过反射不能直接获取私有属性的值,但是可以通过修改访问入口来获取私有属性的值。
设置允许访问私有属性:
field.setAccessible(true);
例如:
Class userClass = Class.forName("com.test.model.User");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
User user = new User();
System.out.println(nameField.get(user));
4、设置属性值
set(object, "属性值")
Class userClass = Class.forName("com.test.model.User");
Field nameField = userClass.getDeclaredField("name");
nameField.setAccessible(true);
User user = new User();
nameField.set(user, "吖宝");
System.out.println(nameField.get(user));
6、构造方法
1、获取所有公有构造方法
getConstructors()
Class userClass = Class.forName("com.test.model.User");
Constructor[] constructors = userClass.getConstructors();
for (Constructor constructor : constructors){
System.out.println(constructor);
}
2、获取与参数类型匹配的构造方法
getConstructor(参数类型)
Class userClass = Class.forName("com.test.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
System.out.println(constructor);
7、成员方法
7.1 获取成员方法
1、获取所有公共方法
getMethods()
Class userClass = Class.forName("com.test.model.User");
Method[] methods = userClass.getMethods();
for (Method method : methods){
System.out.println(method);
}
我们发现,打印结果除了自定义的公共方法,还有继承自 Object 类的公共方法。
2、获取某个公共方法
getMethod("方法名", 参数类型)
Class userClass = Class.forName("com.test.model.User");
Method method = userClass.getMethod("setName", String.class);
System.out.println(method);
3、获取所有方法:公有+私有
getDeclaredMethods()
Class userClass = Class.forName("com.test.model.User");
Method[] methods = userClass.getDeclaredMethods();
for (Method method : methods){
System.out.println(method);
}
4、获取某个方法:公有或者私有
getDeclaredMethods()
Class userClass = Class.forName("com.test.model.User");
Method method = userClass.getDeclaredMethod("run");
System.out.println(method);
7.2 执行成员方法
invoke(object, "方法参数")
Class userClass = Class.forName("com.test.model.User");
Method method = userClass.getDeclaredMethod("eat");
User user = new User();
method.invoke(user);
注:通过反射不能直接执行私有成员方法,但是可以设置允许访问。
设置允许执行私有方法:
method.setAccessible(true);
8、注解
1、判断类上或者方法上是否包含某个注解
isAnnotationPresent(注解名.class)
例如:
Class userClass = Class.forName("com.test.model.User");
if(userClass.isAnnotationPresent(Component.class)){
Component annotation = (Component)userClass.getAnnotation(Component.class);
String value = annotation.value();
System.out.println(value);
};
2、获取注解
getAnnotation(注解名.class)
例如:
Class userClass = Class.forName("com.test.model.User");
// 获取类上的注解
Annotation annotation1 = userClass.getAnnotation(Component.class);
Method method = userClass.getMethod("eat");
// 获取方法上的某个注解
Annotation annotation2 = userClass.getAnnotation(Component.class);
9、创建类的实例
1、通过Class实例化对象
Class.newInstance()
Class userClass = Class.forName("com.test.model.User");
User user = (User)userClass.newInstance();
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());
2、通过构造方法实例化对象
constructor.newInstance(参数值)
Class userClass = Class.forName("com.test.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
User user = (User)constructor.newInstance\("李诗情", "女"\);
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());
10、反射案例
设计一个对象的工厂类
原始:
public class ObjectFactory {
public static User getUser() {
User user = null;
try {
Class userClass = Class.forName("com.xxl.model.User");
user = (User) userClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return user;
}
public static UserService getUserService() {
UserService userService = null;
try {
Class userClass = Class.forName("com.xxl.service.impl.UserServiceImpl");
userService = (UserService) userClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return userService;
}
}
该代码存在两个问题:
1.代码存在大量冗余。如果有一万个类,你是不是要写一万个静态方法?
2.代码耦合度太高。如果这些类存放的包路径发生改变,你再用 forName()获取 Class 对象是不是就会有问题?你还要一个个手动改代码,然后再编译、打包、部署。。你不觉得麻烦吗?
优化后:
-
object.properties
user=com.xxl.model.User userService=com.xxl.service.impl.UserServiceImpl
-
ObjectFactory
public class ObjectFactory { private static Properties objectProperty = new Properties(); // 静态方法在类初始化时执行,且只执行一次 static{ try { InputStream inputStream = ObjectFactory.class.getResourceAsStream("/object.properties"); objectProperty.load(inputStream); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } public static Object getObject(String key){ Object object = null; try { Class objectClass = Class.forName(objectProperty.getProperty(key)); object = objectClass.newInstance(); } catch (Exception e) { e.printStackTrace(); } return object; } }
测试:
@Test void testObject() { User user = (User)ObjectFactory.getObject("user"); UserService userService = (UserService)ObjectFactory.getObject("userService"); System.out.println(user); System.out.println(userService); }