目录
推荐学习教程b站韩老师
介绍
定义
形象的比喻
初步认识反射
一个需求引出反射
编辑 反射机制
定义
反射机制可以完成的机制
反射相关的主要类
代码示例
Field
编辑
Constructor
反射优点和缺点
代码示例
反射调用优化-关闭访问检查
Class类
定义
Class类的常用方法
代码示例
获取Class类对象
有Class对象的数据类型
类加载
定义
代码举例
通过了反射获取类的结构信息
反射爆破
通过反射创建对象
公开无参构造
公开有参构造
非公开有参构造
通过反射访问类中的成员
通过反射操作对象中的方法
踩坑
韩顺平老师讲java反射https://www.bilibili.com/video/BV1g84y1F7df/?spm_id_from=333.337.search-card.all.click
反射是Java语言中一种强大的机制,它允许在运行时检测和操作类、接口、字段、方法以及创建对象的能力。通过反射,可以在程序运行时获取类的信息并动态地调用类的方法、访问或修改类的属性,甚至在不具体明确类名的情况下创建对象。
获取Class对象:通过反射,可以使用类的全限定名获取类的Class对象。可以通过以下方式获取Class对象:
获取类的信息:通过Class对象,我们可以获取类的构造函数、方法、字段等信息。通过这些信息,可以了解类的结构和成员。
实例化对象:通过反射,可以通过Class对象创建类的实例。这使得我们可以在运行时动态地创建新对象,而不需要提前知道类的具体名称。
调用方法和访问属性:通过反射,可以动态地调用类的方法和访问属性。可以通过Method对象调用方法,并通过Field对象访问和修改属性的值。
操作数组:反射还提供了一些工具方法,可以创建、操作和处理数组。
尽管反射功能很强大,但它也需要谨慎使用。由于反射可以绕过访问权限,可能会破坏封装性和安全性。此外,反射操作相对较慢,会引入额外的性能开销。因此,建议在真正需要使用反射时使用,而不是滥用。
这就像生活中,虽然对一个事情来说都是对象执行方法,那又何尝没有知道做成事情的方法,然后用这个方法找合适的人或者指定的人去完成这件事情呢。
假设你有一个任务需要完成,但你不直接知道或无法准确找到适合执行该任务的人。在这种情况下,你可以借助中介机构或通过一系列信息,找到合适的人来执行任务。这个过程类似于反射中的操作。在反射中,你可能并不知道类的具体名称,但你可以通过获取类的信息、方法和属性来找到合适的类,并在运行时实例化对象、调用方法或访问属性。
因此,可以将反射理解为一种动态地查找、调用和操作类的机制,就像在生活中委托他人来完成特定任务一样。这种方式提供了更大的灵活性和动态性,使得在编写代码时可以处理不确定的情况,同时也提供了更多自定义和扩展的可能性。
根据配置文件 reflect.peroperties 指定信息 创建Dog对象并调用方法wang
新建Dog对象
public class Dog {
private String name="旺财";
public void wang(){
System.out.println("我一进来就看见来福在打"+name);
}
}
测试传统方式
使用读取文件方式来获取Dog对象
这种反射方式就实现了通过外部文件配置,在不修改源码情况下来控制程序,符合设计模式中的ocp原则(开闭原则)
现在在Dog中再新增一个成员方法
在不修改源码情况下我们来调用这个changwei方法
调整配置文件中方法名为changwei
测试:
反射机制允许程序在执行期借助于ReflectionApi取得任何类的内部信息(比如成员变量,构造器,成员方法等) 并能操作对象的属性及方法 反射在设计模式和框架底层都有大量允许
加载完类之后 在堆中就产生了一个Class类型的对象(一个类只有一个Class对象) 这个对象包含了类的完整结构信息 通过这个对象得到类的结构 这个对象就像一面镜子 透过这个镜子看到类的结构 所以形象的称为:反射
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时得到任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的成员变量和方法
生成动态代理
java.lang.Class:代表一个类 Class对象表示某个类记载后在堆中的对象
java.lang.reflect.Method:代表类的方法 Method对象表示某个类的方法
java.lang.reflect.Field:代表类的成员变量 Field对象表示某个类的成员变量
java.lang.reflect.Constructor:代表类的构造方法 Constructor对象表示构造器
Class和Method前面已经演示过,这里只演示Field和Constructor
发现没有该方法,那是因为gerField不能得到对象的私有属性
再定一个公开属性进行获取
定义有参构造和无参构造
优点: 可以动态的创建和使用对象(框架底层核心) 使用灵活 没有反射机制 框架技术就失去底层支撑
缺点: 使用反射基本上是解释执行,对执行速度有影响
public static void main(String[] args) throws Exception{
d1();
d2();
}
//传统方法
public static void d1() throws Exception{
Dog dog = new Dog();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
dog.wang();
}
long end = System.currentTimeMillis();
System.out.println("传统方法耗时"+(end-start));
}
//反射调用
public static void d2() throws Exception{
Class> cls = Class.forName("com.bjsxt.reflect.eneity.Dog");
Object o = cls.newInstance();
Method method = cls.getMethod("wang");
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
method.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射方法耗时"+(end-start));
}
测试:
可以看到差异还是挺大的,本次的测试例子就有100多倍
Method和Field ConstruCtor对象都有setAccessible()方法
setAccessible作用是启动和禁用访问安全检查的开关
参数值为ture表示反射的对象在使用时取消访问检查 提高反射的效率 参数值为false则表示反射的对象执行访问检查
示例:
Class也是类 因此也继承Object类 类图如下:
Class类对象不是new出来的 而是系统创建的
对于某个类的Class类对象 在内存中只有一份 因为类只加载一次
每个类的实例都会记得自己是由哪个Class实例所生成
通过Class可以完整地得到一个了类的完整结构 通过一系列Api
Class对象是存放在堆内存中的
类的字节码二进制数据 是放在方法区的 有的地方称为类的元数据(包括方法代码,变量,方法名,访问权限等)
public class Dog {
private String name="旺财";
public Integer age=5;
public String color="黑色";
public Dog() {}
public Dog(String name) {
this.name = name;
}
public void wang(){
// System.out.println("我一进来就看见来福在打"+name);
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void cahngwei(){
System.out.println("我一进来就看见常威在打来福和"+name);
}
}
(1) 前提: 已知一个类的全类名 且该类在类路径下 可通过Class类的静态方法forName()获取 可能抛出ClassNotFoundException
应用场景: 多用于配置文件 读取类全路径 加载类
//场景: 读取配置文件等方式获取
Class> cls1 = Class.forName("com.bjsxt.reflect.eneity.Dog");
System.out.println(cls1);
(2) 前提: 若已知具体的类,通过类的class获取 该方式最为安全可靠 程序性能最高
应用场景: 多用于参数传递 比如投过反射得到对应构造器对象
//场景:参数传递 类名.Class
Class cls2 = Dog.class;
System.out.println(cls2);
(3) 前提: 已知某个类的实例 调用该实例的getClass()方法获取Class对象 =
应用场景: 通过创建好的对象 获取Class对象
//场景: 有对象实例 对象.getClass()
Dog dog = new Dog();
Class cls3 = dog.getClass();
System.out.println(cls3);
(4) 类加载器
ClassLoader cl = 对象.getClass().getClassLoader();
Class class = cl.loadClass("类的全类名");
//通过类加载器来获取到类的Class对象 感觉挺鸡肋的
//先得到类加载器
ClassLoader classLoader = dog.getClass().getClassLoader();
//通过类加载器得到Class对象
Class cls4 = classLoader.loadClass("com.bjsxt.reflect.eneity.Dog");
System.out.println(cls4);
(5) 基本数据(int,byte,char,boolean,float,double,long,short)
Class cls = 基本数据类型.class
//基本数据类型
Class intClass = int.class;
Class booleanClass = boolean.class;
(6)基本数据类型对应的包装类 可以通过.TYPE得到Class类对象
Class cls = 包装类.TYPE
//基本数据类型对应包装类 通过.TYPE得到Class对象
Class integerClass = Integer.TYPE;
Class stringClass = Boolean.TYPE;
(1) 外部类 成员内部类 静态内部类 局部内部类 匿名内部类
(2) interface:接口
(3) 数组
(4) enum:枚举
(5) annotation:注解
(6) 基本数据类型
(7) void
反射机制是java实现动态语言的关键 也就是通过反射时效件类动态加载
(1) 静态加载: 编译时加载相关的类 如果没有则报错 依赖性强
(2) 动态加载: 运行时加载需要的类 如果运行时不用该类 则不报错 降低了依赖性
直接在该目录下编译尝试
编译时直接报错找不到静态加载的类Dog
使用反射
可以看到编译通过并生成了class文件
执行文件
Cat类是动态加载 所以没有编写Cat类编译时也没有报错,只有当动态加载到Cat类时才会报错
import java.util.*;
import java.lang.reflect.*;
public class ClassLoader{
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入数字key");
String key = scanner.next();
switch (key){
case "1":
//静态加载
Dog dog = new Dog();
dog.wang();
break;
case "2":
//使用反射 动态加载
Class cls = Class.forName("Cat");
Object obj = cls.newInstance();
Method m = cls.getMethod("miao");
m.invoke(obj);
System.out.println("你好啊");
break;
default:
System.out.println("nothing...");
}
}
}
class Dog{
public void wang(){
System.out.println("汪星人");
}
}
class Cat{
public void miao(){
System.out.println("喵星人");
}
}
class Animal{
public String remark;
private String weight;
public Animal(){}
public void a(){}
private void a2(){}
}
public class Cat extends Animal{
public String name;
private int age;
protected String color;
String type;
public void mi(){}
private void m2(){}
void m3(){}
protected void m4(){}
public Cat(){}
private Cat(String name){}
}
public static void main(String[] args) {
Class cls = Cat.class;
//getName:获取全类名
System.out.println("获取全类名==>"+cls.getName());
//getSimpleName: 获取简单类名
System.out.println("获取简单类名==>"+cls.getSimpleName());
//getFields:获取所有public修饰的属性 包含本类及其父类(超类)
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println("获取所有public修饰的属性==>"+ field.getName());
}
//getDeclaredFields:获取本类中所有属性
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("获取本类中所有属性==>"+ declaredField.getName());
}
//获取本类中所有public修饰的方法 包含本类以及父类
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println("所有public修饰的方法==>"+ method.getName());
}
//获取本类中所有方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("获取本类中所有方法==>"+ declaredMethod.getName());
}
给定student类
public class Student {
private int age = 25;
private String name = "赵云";
public Student(){}
public Student(int age){
this.age = age;
}
//私有有参构造
private Student(int age, String name) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
准备User类
public class User {
public Integer age;
private static String name;
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
age设置成功
接下来设置静态属性name
发现name设置失败
对name属性进行爆破
准备Tom类
public class Tom {
public int age;
private static String name;
public Tom(){}
private static String say(int age,String name,char sex){
return age+" "+name+" "+sex;
}
public void hi(String s){
System.out.println("Hi "+s);
}
}
在测试中有时候使用反射设置私有属性或者方法时没有采用爆破也有成功的情况
这是因为在使用getField
或getDeclaredField
获得一个字段对象后,调用set
方法会忽略该字段的访问权限。通过反射修改字段值时,默认情况下,访问权限检查会被绕过。
需要注意的是,尽管没有设置setAccessible(true)
,在默认情况下,通过反射修改私有字段是可以完成的,但这并不是一种推荐的做法。按照最佳实践,应该设置setAccessible(true)
来确保有权限修改私有字段的值,并且在修改完成后恢复字段的访问权限,以维护封装性和安全性。
因此,在代码中,即使没有使用setAccessible(true)
,通过反射成功设置name属性的值是由于JVM在默认情况下允许访问私有字段并进行修改。这依赖于JVM的具体实现,不同的JVM实现可能会有不同的行为。