反射
概念
程序运行期间,动态获取/调用类的属性和功能**
理解:
“类”本身也是一种事物,一类对象,各种“类”,都是“类”类的对象。
Class类描述了有原生的、我们创建的各种类对象(class对象)。
class类的属性包括成员方法、成员变量、类名等,所以class类有获取成员方法、成员变量、类名的方法。
Class c1 = Class.forName(“全限定类名”);
Class c2 = 类名.getClass();
方法
1. 获取 构造器对象(间接获取实体类的实例对象)
getConstructors()无参,返回公共的构造器数组
getDeclaredConstructors()无参,返回所有的构造器数组
getConstructor(A,B…)
获取公开的构造函数 返回参数列表为(A,B…)的Constructor对象
参数列表不写则获取无参构造函数
constructor 对象是构造函数对象,有构造新对象的方法:newInstance(A,B...)方法
参数列表需与获取constructor对象的参数列表类型数量完全一致(但class对象获取constructor对象时,参数时各种类型的class类,如String.class,constructor创建对象时,创建的是真正的实体类实例,参数是具体的值)
getDeclaredConstructor(A,B…)
可获取私有化的构造函数 返回参数列表为(A,B…)的Constructor对象
**暴力反射**
参数列表不写则获取无参私有构造函数
由于获取的是私有化构造函数,虽然可以获取,但无法使用。(会报illegalAccessError异常)
需要使用Constructor的setAccessible(true)方法,取消对这个constructor对象的权限检查访问。
破坏了封装性,称为暴力反射
package com.qf.test;
public class User {
private String name;
private int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
private User(String name){
super();
this.name = name;
}
public User() {
}
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 "User [name=" + name + ", age=" + age + "]";
}
}
package com.qf.study;
import java.lang.reflect.Constructor;
public class fanshe {
public static void main(String[] args) throws Exception {
Class u = Class.forName("com.qf.test.User");
for (Constructor c : u.getConstructors()) {
System.out.println(c);
}
//public com.qf.test.User()
//public com.qf.test.User(java.lang.String,int)
for (Constructor c : u.getDeclaredConstructors()) {
System.out.println(c);
}
//public com.qf.test.User()
//private com.qf.test.User(java.lang.String)
//public com.qf.test.User(java.lang.String,int)
Constructor conU = u.getConstructor(String.class,int.class);
Object obj = conU.newInstance("zs",55);
System.out.println(obj);
//User [name=zs, age=55]
Constructor conU2 = u.getConstructor();
Object obj2 = conU2.newInstance();
System.out.println(obj2);
//User [name=null, age=0]
Constructor conU3 = u.getDeclaredConstructor(String.class);
conU3.setAccessible(true);
//如果不setaccessible,出现java.lang.IllegalAccessException
Object obj3 = conU3.newInstance("ls");
System.out.println(obj3);
//User [name=ls, age=0]
}
}
2. 直接获取 实例对象
class对象.newInstance()
前提
class对象的newinstance方法是 无参 的,只能创建无参构造函数;
无法调用私有 构造函数创建对象(class对象没有setAccessible暴力反射的方法)
Object obj4 = u.newInstance();
System.out.println(obj4);
3. 获取成员变量 Field对象
class对象.getDeclaredFiled(String参数即变量名)
返回指定的field对象(无论是否公开)
class对象.getDeclaredFileds(无参)
返回所有的filed对象
class对象.getFiled(String参数即变量名)
返回指定的公共的field对象
class对象.getFields(无参)
返回所有的公共filed对象
//获取指定公共成员变量
Field field = u.getField("id");
System.out.println(field);
//public int com.qf.test.User.id
//获取指定私有成员变量(调用错误方法,获取不到)
Field field2 = u.getField("name");
System.out.println(field2);
//java.lang.NoSuchFieldException,name是私有属性,读不到
//获取指定私有成员变量
Field field3 = u.getDeclaredField("name");
System.out.println(field3);
//private java.lang.String com.qf.test.User.name
//获取所有公共成员变量,打印地址
Field[] fields2 = u.getFields();
System.out.println(fields2);
//[Ljava.lang.reflect.Field;@4e25154f
//对所有公共成员变量遍历获得全限定类名
for (Field f2 : fields2) {
System.out.println(f2);
}
//public int com.qf.test.User.id
//获取所有成员变量并遍历
Field[] fields3 = u.getDeclaredFields();
for (Field f3 : fields3) {
System.out.println(f3);
}
//private java.lang.String com.qf.test.User.name
//private int com.qf.test.User.age
//public int com.qf.test.User.id
field对象.set(实例对象,值)
为公开成员变量赋值
field对象.setAccessible(true)
暴力反射,为私有成员变量设置是否判断访问权限
field对象.set(实例对象,值)
为私有成员变量赋值
public static void main(String[] args) throws Exception {
Class u = Class.forName("com.qf.test.User");
Object obj = u.newInstance();
//获取指定的公共的成员变量并赋值
Field f3 = u.getDeclaredField("id");
f3.set(obj, 234);
System.out.println(obj);
//User [name=null, age=0, id=234]
Field f1 = u.getDeclaredField("name");
Field f2 = u.getDeclaredField("age");
f1.setAccessible(true);
f2.setAccessible(true);
f1.set(obj, "张三");
f2.set(obj, 22);
System.out.println(obj);
//User [name=张三, age=22, id=234]
}
file对象.get(实例对象)
获取属性值
属性是无法使用实例对象直接调出的,因为实例对象使用了多态,是object类型
field对象在创建时已经就是 从实体类的class对象获取的属性对象,所以其所属的类和数据类型已经是固定的。只需确认要获取的是哪个实例的值即可。
//获取属性值1:向下强转(不推荐)
User u1 = (User)obj;
int a = u1.getAge();
System.out.println(a);
//22
//获取属性值2:用field对象直接获取
System.out.println( f2.get(obj));
//22
优点
所有的对象都是object类(多态)
所有属性都是field
如果需要改动对象类型,只需要改动class.forname()里面的全限定类名即可。
4. 获取成员方法 Method对象
class对象.getMethods()
获得所有公共方法,包括从父类继承的(受保护的和私有的和默认的都拿不到)
class对象.getDeclaredMethods()
获取所有本类方法(包括本类私有的但不包括父类非公有的)
class对象.getMethod(“方法名”,参数类型.class…)
获取指定公开方法
class对象.getDeclaredMethod(“方法名”,参数类型.class…)
获取指定方法(公开+非公开)
method对象.invoke(对象名,参数值…)
Class u = Class.forName("com.qf.test.User");
Object obj = u.newInstance();
Method m1 = u.getDeclaredMethod("play",int.class);
m1.setAccessible(true);
m1.invoke(obj, 20);
面向对象
创建类型(实体类),创建方法和属性
创建对象,赋值
调用属性和方法
反射
- 拿到实体类的class对象
- 拿到class对象的method、field、constructor、object实例对象
- 使用method对象/field对象/constructor对象的invoke(对象,参数值…),set/get(对象,参数值…),newInstance(参数值…)方法,对具体某个实例对象进行创建/属性赋值/方法调用
反射+properties
properties文件
className=com.qf.study.Emp
method=work
## properties文件的注释方式:##
##className=com.qf.study.Boss
##method=manage
1.不能不换行!
2.多个键相同会被最后一个覆盖!(map特性)
java代码
package com.qf.study;
public class Emp {
public int work(int days) {
System.out.println("拼命工作"+days+"天");
return days;
}
}
package com.qf.study;
public class Boss {
public int manage() {
System.out.println("老板管人");
return 100;
}
}
package com.qf.study;
import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;
//根据反射和配置文件动态创建对象调用方法
public class RelectTest {
public static void main(String[] args) throws Exception {
//如果写盘符,就写完整绝对路径名
//不写盘符,就从当前项目的根目录(src)往下查找
FileReader fr1 = new FileReader("my.properties");
//创建properties对象
Properties pro = new Properties();
//加载一个输入流来读取properties文件中的键值对
pro.load(fr1);
//流对象加载完了就可以关闭(能走到这一句就证明已经加载完毕了)
fr1.close();
//getProperty方法根据键名获取值
String className = pro.getProperty("className");
String method = pro.getProperty("method");
//根据键值对,利用反射,创建对象并调用方法
//创建配置文件中的class对象
Class c1 = Class.forName(className);
//创建该类的实例对象
Object o1 = c1.newInstance();
//创建该类的指定方法对象
Method oM1 = c1.getMethod(method);
//执行方法并获取返回值
Object i = oM1.invoke(o1,20);
System.out.println(i);
//好处:动态读取配置文件来创建对象
//程序不断运行期间,如需更改对象或方法,无需关程序改源码,改properties即可
}
}
重载方法怎么办(properties文件怎么配置?键不能重复)
方法含参怎么办
动态代理
应用场景:对实体类的某些核心方法X()进行扩展(不改动源码,只在前后拓展)
个人理解:
-
前提
被代理的对象,其被代理的功能X()必须是规范的(定义一个接口中包含X()方法,被代理的对象所属类应继承该接口) -
创建代理器对象
代理器对象如何实现代理(通过反射获取要执行的方法)
-
将代理器对象转型为接口
不能转型为被代理的类 因为代理器对象只代理了接口中定义的功能,不包括对象所属的类的其他特有属性
-
调用转型得到的接口对象的X()(即需要被代理的方法)实现代理执行
因为接口对象是转型来的,所以实际执行的是代理器的对应方法 代理器的对应方法怎么来的: 代理器的三个参数,大致分别用于加载被代理的类,获得接口的class对象数组,**利用反射执行方法**。 第三个参数invocationHandler中的参数:反射的实例对象,参数(要调用的方法全名),参数(调用方法时的参数值)
-
invocationHandler中重写的invoke方法中,可以添加功能扩展,在method这个方法执行前后添加功能
package com.qf.study;
public interface BossInterface {
int manage() ;
int giveSalary(int i) ;
}
package com.qf.study;
public class Boss implements BossInterface{
public int manage() {
System.out.println("老板管人");
return 100;
}
public int giveSalary(int i) {
System.out.println("老板发工资"+i+"元");
return i;
}
}
package com.qf.study;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class RelectTest {
public static void main(String[] args) {
Object obj = Proxy.newProxyInstance(//新建一个代理对象(代理的是对象的某个核心功能)
Boss.class.getClassLoader(),//被代理的类的类加载器
new Class[]{BossInterface.class}, //被代理的对象所继承的接口的class对象的数组(这个接口描述了需要被代理功能,对象的功能来源于此)
new InvocationHandler() {//需求一个invocationHandler对象,用于方法的执行。因为invocationHandler是只有一个抽象函数的函数式接口,适合用匿名内部类方式创建子类对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy:Proxy对象,即代理(者)对象
//后续调用什么方法,method就是什么方法public abstract java.lang.String com.qf.study.EmpInterface.write()
// System.out.println(method);
//后续方法调用时传的什么参数,args的内容就是什么:无参结果是null;有参结果是数组的地址(查看数组内容就是传的参数值)
// System.out.println(Arrays.toString(args));
//代理者可以进行一些特定的拓展(此处是 代理发工资,扣掉100元)
args[0] =(Integer)args[0] - 100;
//代理者的执行,需要执行调用的方法(传递被代理的对象和参数)
Object result =method.invoke(new Boss(), args);
//这个invoke的返回必须与调用的方法的返回类型一致,否则会类型不匹配异常/空指针异常(该返回没有返回)
return result;
}
});
BossInterface boss = (BossInterface)obj;
boss.giveSalary(100);//结果是老板发工资0元
// e实例是由动态代理对象转型来的,调用任何方法都要先创建动态代理对象(obj),调用的方法实际执行的也是右边obj的方法
// obj是从proxy.newinstance创建而来,会从被代理的类中加载类的信息(包括方法)
// 找到要执行的方法后,使用自己的invocationHandler对象的invoke方法,根据执行自己找到的被代理的类方法和传递来的参数执行
// invocationHandler对象的invoke方法是重写的,在执行method的invoke方法前后可以进行代码的增强(适用于不想改动接口或类的时候)
}
}
- 优点
目标类实现接口,代理类无需实现接口;故目标类的功能扩展也不影响代理类 - 缺点
性能一般(因为使用反射)