我是靠谱客的博主 俏皮饼干,最近开发中收集的这篇文章主要介绍反射初体验引入反射机制Class应用场景如何提升反射的性能Spring 的反射如何实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

引入

一般情况下,我们都是通过 new 关键字来实例化对象,这是一种正射

实例化一个 HashMap 集合:

Map<Integer, Integer> map = new HashMap<>();

当需要修改集合类型为 LinkedHashMap 时,需要修改代码:

Map<Integer, Integer> map = new LinkedHashMap<>();

每一次我们改变需求的时候,都需要修改代码,然后对代码进行编译、打包、再到 JVM 上重启项目

缺点:效率低下

优化一:动态传参,根据不同情况选择不同的数据结构

public Map<Integer, Integer> getMap(String param) {
Map<Integer, Integer> map = null;
if (param.equals("HashMap")) {
map = new HashMap<>();
} else if (param.equals("LinkedHashMap")) {
map = new LinkedHashMap<>();
} else if (param.equals("WeakHashMap")) {
map = new WeakHashMap<>();
}
return map;
}

弊端:需要尽量考虑多的情况,当漏了某种情况时,还是需要修改代码

优化二:反射,在程序运行过程中动态获取类信息,调用类的方法,构造类实例

public Map<Integer, Integer> getMap(String className) {
Class c = Class.forName(className);
Consructor con = c.getConstructor();
return (Map<Integer, Integer>) con.newInstance();
}

当需要使用的时候,传入对应结构的 全类名路径 到方法中,返回一个对应实例

总结

  • new:在编译器确定对象类型
  • 反射:在运行期确定具体的数据类型

反射机制

  1. 简述反射机制

反射机制是指在运行过程中,对于任意一个类,能获取并调用该类的任意属性和方法,获取类的构造器来构建对象。这种 动态获取程序信息 以及 动态调用对象的功能 称为反射机制(Reflection),反射是 Java 被视为 动态语言 的关键

  1. 反射机制的作用

在程序运行时,构造任意一个类的对象,判定任意一个对象所属的类,判断任意一个类所具有的成员变量和方法,调用任意一个对象的方法,生成动态代理

  1. 反射的优缺点

优点:动态加载类,动态创建对象和编译,当需求变更时,可以灵活地实例化不同对象

缺点:

  • 性能瓶颈。反射是一种解释操作,我们可以告诉 JVM,我们希望做什么并且它满足我们的要求,这类操作总是慢于直接执行相同的操作
  • 破坏类的封装性:通过反射我们可以强制访问 private 修饰的信息

主要用法

  • 获取类的 Class
  • 获取类的实例化对象
  • 获取类的所有信息,包括:变量、方法、构造器、注解、泛型信息
  • 动态代理
  • 获取类的私有信息(存在访问权限的成员,如 protected、private)并修改其作用域

Class

概述

每一个 Java 类都有一个 Class 模板(字节码文件对象),当一个 java 类经 javac 编译后,产生一个 .class 字节码文件,其中包含了类的所有信息,如 属性、构造方法、方法 等

当 .class 被装载进 JVM 执行时,在内存中生成一个 Class 对象,它包含了该类内部的所有信息。通过该对象,我们可以在程序运行时获取类的信息

特点:

  • Class 是一个类,Class 对象只能由系统建立

  • 每个类的 Class 模板是唯一的

  • 每个类的实例都会记得自己是由哪个 Class 模板所生成

Class 是反射的根源,只有先获取 Class 对象,才能对类的信息进行动态获取、加载、运行

获取 Class

  • 类名.class:安全、最高性能
  • 实例.getClass()
  • Class.forName(className):灵活
Class c = Student.class;
Class c = new Student().getClass();
Class c = Class.forName("com.wes.pojo.Student");

基本类型的 Class

基本类型的 Class 通过其包装类来获取

Class c = Integer.TYPE;

获取父类 Class

Class parent = c.getSuperclass();

其他类型的 Class

public static void main(String[] args) {
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = Override.class;
Class c6 = ElementType.class;
Class c7 = Integer.class;
Class c8 = void.class;
Class c9 = Class.class;
}

常用方法

方法说明
getName()Class 对象所代表的实体的名称
ClassLoader getClassLoader()返回该类的类加载器

获取类名

public static void main(String[] args) throws Exception {
Class c = Class.forName("com.yue.pojo.Student");
String typeName = c.getName();//获取类的全限定名
String name = c.getSimpleName(); // 获取类名
}

创建对象

获取 Class 后,通过反射创建对象:

  • 使用 Class 对象的 newInstance 方法(调用类的无参构造器)
  • 获取构造器,调用构造器的 newInstance

public static void main(String[] args) {
Class<Student> c = Student.class;
Student student = null;
try {
student = c.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
// 获取无参构造器
Constructor<Student> c1 = c.getConstructor();
// 调用无参构造
student = c1.newInstance();
// 获取有参构造器
Constructor<Student> c2 = c.getConstructor(String.class, int.class);
// 调用有参构造,传入对应参数类型进行赋值
student = c2.newInstance("zhangsan", 20);
}

获取构造器

方法说明
Constuctor[] getConstructors()获取 public 构造器
Constructor getConstructor(Class…<?> paramTypes)根据参数获取 public 构造器
Constructor[] getDeclaredConstructors()获取所有构造器
Constructor getDeclaredConstructor(class…<?> paramTypes)根据参数获取所有构造器
public static void main(String[] args) throws Exception {
Class<Student> c = Student.class;
// public
Constructor<?>[] publicC = c.getConstructors();
// 无参构造器
Constructor<Student> c1 = c.getConstructor();
// 所有构造器
Constructor<?>[] allC = c.getDeclaredConstructors();
// 指定构造器
Constructor<Student> c2 = c.getDeclaredConstructor(String.class, int.class);
}

通过 getDeclaredConstructor 可以获取到私有的构造器,但却不能直接进行创建对象

  • 使用 setAccessible 取消访问检查
public static void main(String[] args) throws Exception {
Class c = Class.forName("com.yue.pojo.Student");
Constructor con = c.getDeclaredConstructor(String.class); // 获取一个私有的构造器
c.setAccessible(true); // 取消访问检查
Object o = c.newInstance("蓝天"); // 创建对象
}

获取变量

方法说明
Field[] getFields()获取 public 变量
Field getField(String name)根据变量名获取 public 变量
Field[] getDeclaredFields()获取所有变量,无法获取继承的变量
Field getDeclaredField(String name)根据变量名获取变量,无法获取继承的变量
public static void main(String[] args) throws Exception {
Class<Student> c = Student.class;
// 获取所有 pubilc 变量
Field[] fs1 = c.getFields();
Field[] fs2 = c.getDeclaredFields();
// 根据变量名
Field f1 = c.getField("address");
Field f2 = c.getDeclaredField("address");
}

获取变量,创建对象后对变量进行动态赋值

public static void main(String[] args) throws Exception {
Class<Student> c = Student.class;
Constructor<Student> con = c.getConstructor();
Student student = con.newInstance();
Field field = c.getField("address");
field.setAccessible(true);
field.set(student, "北京");
}

获取方法

方法说明
Method[] getMethods()获取 public 方法
Method getMethod(String name, Class…<?> paramTypes)根据名字、参数获取 public 方法
Method[] getDeclaredMethods()获取所有方法,无法获取继承的方法
Method getDeclaredMethod(String name, Class…<?> paramTypes)根据名字、参数获取方法,无法获取继承的方法
public static void main(String[] args) throws Exception {
Class<Student> c = Student.class;
// 获取所有方法
Method[] ms1 = c.getMethods();
Method[] ms2 = c.getDeclaredMethods();
// 获取单个方法
Method m1 = c.getMethod("method");
Method m2 = c.getDeclaredMethod("method");
}

利用 Method 中的 invoke 函数调用方法

Object invoke(Object obj, Object... args);
// obj:调用对象
// args:参数
public static void main(String[] args) throws Exception {
Class<Student> c = Student.class;
Student student = c.getConstructor().newInstance();	// 创建对象
c.getMethod("method").invoke(student); // 调用方法
}

如果调用的是静态方法,invoke 函数的第一个参数只需要传入 null,因为静态方法不与某个对象有关,只与某个类有关

获取注解

在反射中,Field、Constructor 和 Method 类对象都可以获取标注在它们之上的注解

方法说明
Annotation[] getAnnotations()获取对象上所有注解
Annotation getAnnotation(Class annotaionClass)传入注解类型,获取对象上某个注解
Annotation[] getDeclaredAnnotations()获取对象上所有注解(显示标记的),无法获取继承的注解
Annotation getDeclaredAnnotation(Class annotationClass)传入注解类型,获取对象上某个注解(显示),无法获取继承的注解

只有注解的 @Retension 标注为 RUNTIME 时,才能够通过反射获取到该注解

定义一个抽象类、一个抽象的继承

public abstract class Pineapple {
void getInfo();
}
public class SmallPineapple extends Pineapple {
@Transient
@Override
public void getInfo() {
}
}
public static void main(String[] args) throws Exception {
Class<Student> c = Student.class;
// 获取方法 getInfo
Method method = c.getMethod("getInfo");
// 获取方法上的注解,这里获取到 @Transient
Annotation[] annotations = method.getAnnotations();
}

应用场景

  1. 举例说明反射的应用场景
  • 在 JDBC 中,通过反射动态加载了数据库驱动程序
  • Web 服务器利用反射调用 Servlet 的服务方法
  • Spring 利用反射注入属性并调用方法

抽象工厂

传统的工厂模式,如果需要生产新的子类,需要修改工厂类,在工厂类中增加新的分支

public class MapFactory {
public Map<Object, object> produceMap(String name) {
if ("HashMap".equals(name)) {
return new HashMap<>();
} else if ("TreeMap".equals(name)) {
return new TreeMap<>();
} // ···
}
}

结合反射,工厂类不需要进行条件的判定去特定返回特定对象。在运行时,通过参数传入不同子类的全限定名获取到不同的 Class,调用 newInstance 方法返回不同的子类

例如,在运行时才确定使用哪一种 Map 结构,我们可以利用反射传入某个具体 Map 的全限定名,实例化一个特定的子类

public class MapFactory {
public Map<Object, Object> produceMap(String className) {
Class c = Class.forName(className);
Map<Object, Object> map = c.newInstance();
return map;
}
}

className 可以指定为 java.util.HashMap,或者 java.util.TreeMap 等等,根据业务场景来定

JDBC

在导入第三方库时,JVM 不会主动去加载外部导入的类,而是等到真正使用时,才去加载需要的类

正是如此,我们可以在获取数据库连接时传入驱动类的全限定名,交给 JVM 加载该类

public class DBConnectionUtil {
// 指定数据库的驱动类 
private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
public static Connection getConnection() {
Connection conn = null;
// 加载驱动类
Class.forName(DRIVER_CLASS_NAME);
// 获取数据库连接对象
conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root");
return conn;
}
}

读取配置文件

通过反射,使用配置文件的内容来运行指定的类中的指定的方法

以后想要访问什么类的什么方法,只需要修改一下配置文件即可,灵活性很高

现在有一个配置文件 class.txt

className=com.yue.pojo.Student
methodName=study
public static void main(String[] args) throws Exception {
// 首先要加载数据
Properties properties = new Properties();
FileReader fileReader = new FileReader("L:\反射\class.txt");
properties.load(fileReader);
fileReader.close();
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
// 通过反射来使用
Class<?> c = Class.forName(className);
Constructor<?> constructor = c.getConstructor();
Object o = constructor.newInstance();
Method method = c.getMethod(methodName);
method.invoke(o);
}

添加不同数据

向 ArrayList<Integer> 中添加一个字符串数据:通过反射获取到 List 的 add 方法,调用 invoke 添加数据

public static void main(String[] args) throws Exception{
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
// 通过反射拿到 add 方法,然后添加数据
Class<? extends ArrayList> c = list.getClass();
Method add = c.getMethod("add", Object.class);
add.invoke(list, "hello");
add.invoke(list, "world");
System.out.println(list);
}

如何提升反射的性能

Spring 的反射如何实现

最后

以上就是俏皮饼干为你收集整理的反射初体验引入反射机制Class应用场景如何提升反射的性能Spring 的反射如何实现的全部内容,希望文章能够帮你解决反射初体验引入反射机制Class应用场景如何提升反射的性能Spring 的反射如何实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部