引入
一般情况下,我们都是通过 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:在编译器确定对象类型
- 反射:在运行期确定具体的数据类型
反射机制
- 简述反射机制
反射机制是指在运行过程中,对于任意一个类,能获取并调用该类的任意属性和方法,获取类的构造器来构建对象。这种 动态获取程序信息 以及 动态调用对象的功能 称为反射机制(Reflection),反射是 Java 被视为 动态语言 的关键
- 反射机制的作用
在程序运行时,构造任意一个类的对象,判定任意一个对象所属的类,判断任意一个类所具有的成员变量和方法,调用任意一个对象的方法,生成动态代理
- 反射的优缺点
优点:动态加载类,动态创建对象和编译,当需求变更时,可以灵活地实例化不同对象
缺点:
- 性能瓶颈。反射是一种解释操作,我们可以告诉 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();
}
应用场景
- 举例说明反射的应用场景
- 在 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);
}