第十三章 反射
反射的思路
- 看到形象的图片想到了抽象的名字
- 看到抽象的名字,在记忆中寻找匹配的形象的图片
- 看到一个类总结类名
- 看到一个类名总结类
1 Java Reflection (反射) 概述
2 理解Class类并实例化Class类对象
- 创建Class对象的四种方法
//=================创建Class对象的四种方法==============
Class c0 = Person.class;//通过类名.class创建指定类的Class实例
Clazz c1 = p.getClass();//通过一个类的实例对象.getClass()方法,获取对应 实例对象的类的Class实例
try{
//通过Class的静态方法forName(String className)来获取一个类的Class实例
//forName(String className)方法中的参数是你要获取的Class实例的类的全路径(包名.类名)
//最常用的
Class c2 = Class.forName("chapter13.Person");//这个是获取Class实例的常用方式
}catch(Exception e){
e.pritnStackTrace();
}
//第四种不常用不介绍
3 运行时创建类对象,通过反射调用类的完整结构
Class clazz = Class.forName("chapter13.Student");
//通过包名.类名的字符串,调用Class.forName方法获取指定类的Class实例
Package p = clazz.getPackage();
//获取类所在的包
System.out.println("获取类所在的包"+p.getName());
//Package类也有getName()方法
- 创建Person类作为父类,创建Student类继承父类并实现接口Move,Study
- 在Student类中,创建不同权限的构造器、方法、属性
//Move接口
public interface Move {
void moveTy();
}
//Study接口
public interface Study {
void studyInfo();
}
//Person类
public class Person {
String name;
int age;
}
//Student类
public class Student extends Person implements Move,Study{
public Student(){
System.out.println("调用的是 public Student()");
}
public Student(int age){
this.age = age;
System.out.println("调用的是 public Student(int age)");
}
protected Student(String school){
this.school = school;
System.out.println("调用的是 protected Student(String school)");
}
Student(int id, int age){
this.id = id;
}
private Student(String name, int age){
this.name = name;
this.age = age;
System.out.println("调用的是 private Student(String name, int age)");
}
/**
* 四种访问修饰符的范围复习:
* 类内部 同一个包的类 子类 任何地方
* private ✔ ✘ ✘ ✘
* 缺省 ✔ ✔ ✘ ✘
* protected ✔ ✔ ✔ ✘
* public ✔ ✔ ✔ ✔
*/
public String school;
private String privateField;
int id;
protected char sex;
@Override
public void moveTy() {
System.out.println("骑自行车上学");
}
@Override
public void studyInfo() {
System.out.println("学习的是中学的知识");
}
public void showInfo(){
System.out.println("学校是:"+this.school);
}
private void test(String name){
System.out.println("这是私有方法private void test(String name)");
}
public String getSchool(){
System.out.println("这是public String getSchool()方法");
return this.school;
}
public void setInfo(String name, int age, int id){
this.name = name;
this.age = age;
this.id= id;
System.out.println("这是public void setInfo(String name, int age, int id)方法");
}
public void setInfo(String school){
this.school = school;
System.out.println("这个是public void setInfo(String school)方法");
}
}
✔ 复习4种访问权限修饰符
不同的修饰符及其对应符号:private: 2,缺省default: 0,protected: 4,public: 1
✔ Declared:不含Declared的方法只能返回public修饰的方法或属性(包含基类的),含Declared的方法可以返回所有访问权限的方法或属性(不包含基类)
- 例如:得到所有构造器
Constructor c1 = clazz.getConstructor(int.class);
//参数类型为Class,返回public的Constructor类对象
Constructor c2 = clazz.getDeclaredConstructor(String.class);
//参数类型为Class,返回Constructor类对象
-
得到所有方法
Method[] ms = clazz.getMethods();
//获取到类的所有的公有public方法
Method[] ms = clazz.getDeclaredMethods();
//获取类的所有方法,包括公有和私有 -
- getMethods() 获取的是类的所有共有方法,这就包括自身的 所有public 方法,和从基类继承的、从接口实现的所有public方法。
-
- getDeclaredMethods()获取的是类的所有方法包含4种不同访问权限包括private私有类。但不包含基类(即父类,Object类等继承过的类)的方法。
-
得到所有属性
Field[] fs = clazz.getFields();
//获取类的公有的属性,包括从父类继承的
Field[] fs = clazz.getDeclaredFields();
//获取类的所有属性包括私有,不包括从父类继承的
3.1 获取当前对象类的父类、所有接口(Class)
注意:此处获得的父类Person,子类Student,类实现的接口Move,Study都是作为Class类的对象返回得到的
- 实例化Class对象,并获取所继承的父类,获取当前类的所有接口
Class superClass = clazz.getSuperclass();
//获取父类
Class[] interfaces = clazz.getInterfaces();
//获取当前类的所有接口
c.getName()
//获取单个接口名
public static void main(String[] args) {
try{
Class clazz = Class.forName("chapter13.Student");//通过包名.类名的字符串,调用Class.forName方法获取指定类的Class实例
Class superClass = clazz.getSuperclass();//获取父类
System.out.println("父类:"+superClass.getName());
Class[] interfaces = clazz.getInterfaces();//获取当前类的所有接口
for(Class c: interfaces){
System.out.println("接口:"+c.getName());
}
}catch(Exception e){
e.printStackTrace();
}
}
3.2 通过反射获取类的全部构造器及修饰符、构造方法名、参数类型。
注意:
1.此处获得的构造器(构造方法)都是作为Constructor类的对象返回得到的,而获取的构造器的参数们为Class类型对象
2.获得的方法为Method类
3.获得的属性为Field类
- 获取类的全部构造器及修饰符、构造方法名、参数类型
public static void main(String[] args) {
try{
//---------------------------------获取当前类public的构造器-------------------------------------------------
Constructor[] pcons = clazz.getConstructors();//返回的是一个数组,public的构造方法的数组;
for(Constructor c: pcons){
System.out.println("------------------------");
//输出所有构造方法的名称;直接c输出整个方法名包含修饰符、返回值、参数类型
System.out.println("构造方法名称:"+c.getName());//getName获取方法名称
System.out.println("构造方法:"+c.getName()+"的修饰符是:"+c.getModifiers());//getModifiers获取方法修饰符
//getModifiers取得方法的修饰,返回数字1代表public,返回数字2代表private,缺省是0,protected是4
}
//-------------------------获取当前类所有的构造器-----通过构造方法Constructor类返回该构造器的参数数组--------------------------------------
System.out.println();
System.out.println("获取当前类所有的构造器-----通过构造方法Constructor类返回参数数组");
Constructor[] cons = clazz.getDeclaredConstructors();//返回此Class对象类声明的所有构造方法
for(Constructor c: cons){
System.out.println("------------------------");
//输出所有构造方法的名称;直接c输出整个方法名包含修饰符、返回值、参数类型
System.out.println("构造方法名称:"+c.getName());//getName获取方法名称
System.out.println("构造方法:"+c.getName()+"的修饰符是:"+c.getModifiers());//getModifiers获取方法修饰符
Class[] parmClazz = c.getParameterTypes();//获取该构造方法的参数类型,有几个参数,数组的元素就有几个
for(Class pc: parmClazz){
System.out.println("构造方法"+c.getName()+"的参数类型:"+pc.getName());
}
}
}catch(Exception e){
e.printStackTrace();
}
- 运行结果
- 对应4个构造器
- 不同的修饰符及其对应符号:private2,缺省default0,protected4,public1
3.3 获取类的全部方法及方法名、返回值类型、修饰符、方法的参数类型们。获取类的全部属性及属性名、属性类型、修饰符。
注意:返回的类的方法都为Method类对象,返回的类的属性都为Field类对象
- 获取类的全部方法及方法名、返回值类型、修饰符、方法的参数类型们
//--------------------------------------获取类的所有方法Methods------------------------------------------------
try{
//Method[] ms = clazz.getMethods();//获取到类的所有的公有public方法
Method[] ms = clazz.getDeclaredMethods();//获取类的所有方法,包括公有和私有
for (Method m : ms) {//输出每一个方法
System.out.println("方法名:" + m.getName());
System.out.println("返回值类型:" + m.getReturnType());
System.out.println("修饰符:" + m.getModifiers());
Class[] pcs = m.getParameterTypes();//获取方法的参数类型,是一个数组,方法有几个参数,数据就有几个元素
//输出方法的每个参数
if (pcs != null && pcs.length > 0) {//没有参数的情况,懒得打印
for (Class p : pcs) {
System.out.println(p.getName());
}
}
System.out.println("---------------------------");
}
}catch(Exception e){
e.printStackTrace();
}
- 运行结果
-
- getMethods() 获取的是类的所有共有方法,这就包括自身的 所有public 方法,和从基类继承的、从接口实现的所有public方法。
-
- getDeclaredMethods()获取的是类的所有方法包含4种不同访问权限包括private私有类。但不包含基类(即父类,Object类等继承过的类)的方法。
getMethods()得到很多基类public方法
getDeclaredMethods得到的方法里没有test方法,因为test方法为private
- 获取类的全部属性及属性名、属性类型、修饰符
//--------------------------------------获取类的所有属性Field-----------------------------------------------
try{
/*
* 加了Declared就不能输出基类的属性,但是可以输出所有类型的
*/
Field[] fs = clazz.getFields();//获取类的公有的属性,包括从父类继承的
// Field[] fs = clazz.getDeclaredFields();//获取类的所有属性包括私有,不包括从父类继承的
for (Field f:fs){
System.out.println("-----------------------------");
System.out.println("修饰符:"+f.getModifiers());
System.out.println("属性类型:"+f.getType());
System.out.println("属性名称"+f.getName());
}
}catch(Exception e){
e.printStackTrace();
}
- 运行结果
由图可见,2private,0缺省default,4protected,1public
4 通过反射创建对象 -> Class对象.newInstance();Constructor对象.newInstance(参数.class);
- 通过反射创建对象,调用四种不同的构造器
-
- 注意:只有public的无参构造是直接用该
Class类对象.newInstance()
创建,其他都是先得到构造器(根据参数类型.class
转为Class类,作为调构造器的参数,来区分调哪个构造器)再构造器.newInstance(参数)创建该类对象
- 注意:只有public的无参构造是直接用该
-
- 如果是调用私有private构造器创建对象,Declared强制获得构造器后进行,
构造器.setAccessible(true)
来解除封装,再构造器.newInstance(参数);
- 如果是调用私有private构造器创建对象,Declared强制获得构造器后进行,
-
- 获得protected和default类型的构造器无需解除封装,但需Declared
例如:
Student obj = (Student)clazz.newInstance();
//调无参构造直接构造对象
Constructor c1 = clazz.getConstructor(int.class);
//参数类型为Class,返回Constructor类对象
Student stu2 = (Student)c1.newInstance(36);
//填入该构造器参数
Constructor c3 = clazz.getDeclaredConstructor(String.class,int.class);
//指定获取有两个参数(String,int)的构造方法
c3.setAccessible(true);
//解除私有的封装,下面就可以对这个私有方法强制调用
Student stu4 = (Student)c3.newInstance("张三",24);
//填入构造器对应的参数
Class clazz = Class.forName("chapter13.Student");//通过包名.类名的字符串,调用Class.forName方法获取指定类的Class实例
try{
//-------------------用Class对象.newInstance();调用无参构造器返回对应类的Object------------------------------
//相当于调用Student类的无参公有的构造方法,public Student()
Object obj = clazz.newInstance();//无参,返回Object对象
//得到一个Student对象,需要强转到原本类型
Student stu1 =(Student)obj;
System.out.println("该public无参构造的Student对象学校为:"+stu1.school);//无参构造,school无值
//------------------用Constructor对象.newInstance(参数.class);调用public有参构造器返回对应类的Object------------------------------------
Constructor c1 = clazz.getConstructor(int.class);//参数类型为Class,返回Constructor类对象
Student stu2 = (Student)c1.newInstance(36);//通过newInstance实例化对象,相当于调用protected Student(String school)
System.out.println("该public有参构造的Student对象年龄为:"+stu2.age);
//------------------用Constructor对象.newInstance(参数.class);调用procected有参构造器返回对应类的Object------------------------------------
//指定获取有一个参数并且为String类型的protected的构造方法
Constructor c2 = clazz.getDeclaredConstructor(String.class);//参数类型为Class,返回Constructor类对象
Student stu3 = (Student)c2.newInstance("第一中学");//通过newInstance实例化对象,相当于调用protected Student(String school)
System.out.println("该protected有参构造的Student对象学校为:"+stu3.school);//该有参构造的Student对象学校为:第一中学
//-------------------用Constructor对象.newInstance(参数.class);调用default有参构造返回对应类的Object---------------------------------------
Constructor c3 = clazz.getDeclaredConstructor(int.class, int.class);
Student stu4 = (Student)c3.newInstance(202856,34);
System.out.println("该default有参构造的Student对象学号为:"+stu4.id+"年龄为:"+stu4.age);
//------------------用Constructor对象.newInstance(参数.class);调用private有参构造器返回对应类的Object------------------------------------
//通过反射机制,可以强制的调用私有的构造方法
Constructor c3 = clazz.getDeclaredConstructor(String.class,int.class);//指定获取有两个参数(String,int)的构造方法
//需要加上这一句,解除封装
c3.setAccessible(true);//解除私有的封装,下面就可以对这个私有方法强制调用
//直接写报错java.lang.IllegalAccessException: Class chapter13.Test1 can not access a member of class chapter13.Student with modifiers "private"
//因为是私有构造
Student stu4 = (Student)c3.newInstance("张三",24);
System.out.println("该private有参构造的Student对象姓名为为:"+stu4.name+",年龄为:"+stu4.age);//该有参构造的Student对象学校为:第一中学
}catch(Exception e){
e.printStackTrace();
}
- 运行结果
5 通过反射调用类的指定方法、指定属性
如果是私有属性
Field f1 = clazz.getDeclaredField("privateField");
//获取该class实例对应类中对应名称的对象
f1.setAccessible(true);
//解除私有的封装,下面就可以强制的调用这个属性
//=============================通过反射调用类的指定方法、指定属性============================
try{
System.out.println("=============================通过反射调用类的指定方法============================");
//----------------------------------通过反射调用指定方法---------------------------------------------
/**
* 注意:下面不论是反射调用setInfo还是test方法
* 都调用的obj对象的方法,obj对象实际上就是Student对象
*/
Constructor con = clazz.getConstructor();//获取无参构造
Object obj = con.newInstance();//使用无参构造创建对象
Method m = clazz.getMethod("setInfo",String.class,int.class,int.class);//得到名称叫setInfo,参数是String,int,int的方法
m.invoke(obj,"张三",23,20165678);//参数1是需要实例化的对象,后面的参数是调用当前的方法实际参数
//如果想要调用一个私有方法
Method m1 = clazz.getDeclaredMethod("test",String.class);//获取方法名为test,参数为1个String类型的方法
m1.setAccessible(true);//解除私有封装,下面可以强制调用私有的方法
m1.invoke(obj,"李四");//私有的
//调用一个重载方法
Method m2= clazz.getMethod("setInfo",String.class);
m2.invoke(obj,"第一中学");
//有返回值的方法
Method m3 = clazz.getMethod("getSchool");//这是获取方法名为getSchool并且没有参数的方法
String school= (String)m3.invoke(obj);//调用有返回值但是没有参数的方法;返回的是Object类型的,强转一下
System.out.println(school);
//----------------------------------通过反射调用指定方法属性---------------------------------------------
System.out.println("=============================通过反射调用类的指定属性============================");
//反射创建一个对象
Constructor con2 = clazz.getConstructor();//利用无参构造创建一个对象
Student stu = (Student)con2.newInstance();//通过构造方法创建对象
Field f = clazz.getField("school");//获取名称为school的属性
f.set(stu,"第三中学");
String school2 = (String)f.get(stu);//获取stu对象的school属性的值
System.out.println(school2);
//如果是私有属性
Field f1 = clazz.getDeclaredField("privateField");
f1.setAccessible(true);//解除私有的封装,下面就可以强制的调用这个属性
f1.set(stu,"测试私有属性");
System.out.println(f1.get(stu));
}catch(Exception e){
e.printStackTrace();
}
6 动态代理
如果有100个java类,每个类有10个方法,总共1000个方法,都需要在方法前后增加打印开始和结束的输出。
这里是两个类
- 接口ITestDemo
public interface ITestDemo {
void test1();
void test2();
}
- 类TestDemoImp1实现接口ITestDemo
public class TestDemoImp1 implements ITestDemo {
@Override
public void test1() {
System.out.println("执行test()方法");
}
@Override
public void test2() {
System.out.println("执行test2()方法");
}
}
- 类TestDemoImp2实现接口ITestDemo
public class TestDemoImp2 implements ITestDemo{
@Override
public void test1() {
System.out.println("TestDemoImp2类的test1()");
}
@Override
public void test2() {
System.out.println("TestDemoImp2类的test2()");
}
}
- 动态代理类
-
- 需要实现InvocationHandler接口和public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 方法
-
- 其中有参构造,参数为被代理的类Object,属性Object类型的obj在invoke方法中作为参数调用原类的方法
* @Description: 动态代理类
*/
public class ProxyDemo implements InvocationHandler {
Object obj;//被代理的对象
public ProxyDemo(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法开始执行");
Object result = method.invoke(this.obj, args);//执行的是指定对象的指定的方法;传入参数调用该对象的该方法
System.out.println(method.getName()+"方法执行完毕");
return result;
}
}
- 动态代理,前后对比
public static void main(String[] args) {
//接口中定义了两个方法,实例化类对象
ITestDemo demoImp = new TestDemoImp1();//继承接口的类对象
/**
* 注意:如果一个对象想要通过Proxy.newProxyInstance方法被代理,
* 那么这个对象的类一定要有相应的接口
* 就像本例中的ITestDemo接口和实现类TestDemoImp1
*/
demoImp.test1();
demoImp.test2();//调用对象的两个方法
/**
* 需求:
* 在执行test1和test2方法时需要加入一些东西
* 在执行方法前打印test1或test2开始执行
* 在执行方法后打印test1或test2执行完毕
* 打印的方法名要和当时调用方法保持一致
*/
InvocationHandler handler = new ProxyDemo(demoImp);//传入要同意修改的类所在的对象为参数作为被代理对象,然后构造代理对象
/**
* Proxy.newProxyInstance(ClassLoader, interfaces, h)
* 参数1是代理对象的类加载器
* 参数2是被代理的对象的接口
* 参数3是代理对象
* 返回的值是成功被代理后的对象
*/
//返回的值就是成功被代理后对象,返回的是Object类型,需要根据具体情况去转换类型
ITestDemo t = (ITestDemo)Proxy.newProxyInstance(handler.getClass().getClassLoader(),demoImp.getClass().getInterfaces(),handler);
t.test1();
t.test2();
//测试:代理前的
ITestDemo t2 = new TestDemoImp2();
t2.test2();
demoImp.test1();
}