JavaSE 反射机制(下)
四、获取类的方法
-
获取方法函数:
-
public Method[] getDeclaredMethods()
返回此Class对象所表示的类或接口的全部方法。
-
public Method[] getMethods()
返回此Class对象所表示的类或接口的public的方法。
-
-
常用方法:(在获取方法过程中)
方法名 功能 Method[] getMethods() 获取类的所有公有方法 Method[] getDeclaredMethods() 获取类的所有方法(公有+私有) getName() 获取方法名称 getReturnType() 获取返回值类型 getModifiers() 获取修饰符(public–>1; private–>2) getParameterTypes() 获取方法的参数类型,返回Class[], 数组元素个数为参数个数 -
上述方法案例:
package com.reflection; //父类 public class Person { public String name; int age; }
package com.reflection; //子类 public class Student extends Person{ String school; private String privateField; //无参构造 public Student(){ System.out.println("调用的是public Student()"); } //有参构造 public Student(String school){ System.out.println("调用的是public Student(String school)"); this.school = school; } //私有有参构造 private Student(String name, int age){ System.out.println("调用的是private Student(String name, int age)"); this.name = name; this.age = age; } public void moveType() { System.out.println("骑自行车上学"); } public void studyInfo() { System.out.println("学习中学知识"); } public String getSchool(){ return this.school; } private void test(String name){ } }
package com.reflection; import java.lang.reflect.Method; public class Test3 { public static void main(String[] args) { try { Class clazz3 = Class.forName("com.reflection.Student"); //获取到类的所有公有方法 Method[] ms = clazz3.getMethods(); for (Method m : ms) { System.out.println("方法名: "+ m.getName()); System.out.println("返回值类型: "+ m.getReturnType()); System.out.println("修饰符: "+ m.getModifiers()); Class[] pcs = m.getParameterTypes();//获取方法的参数类型,返回Class数组,方法参数个数等于数组元素个数 if (pcs.length>0){ for (Class pc : pcs) { System.out.println("参数类型: " + pc.getName()); } } System.out.println("-----------------------------------------------"); } System.out.println("\n========分==================割===================线===========\n"); //获取类的所有方法,包含公有私有 Method[] ams = clazz3.getDeclaredMethods(); for (Method m : ams) { System.out.println("方法名: "+ m.getName()); System.out.println("返回值类型: "+ m.getReturnType()); System.out.println("修饰符: "+ m.getModifiers()); Class[] pcs = m.getParameterTypes();//获取方法的参数类型,返回Class数组,方法参数个数等于数组元素个数 if (pcs.length>0){ for (Class pc : pcs) { System.out.println("参数类型: " + pc.getName()); } } System.out.println("-----------------------------------------------"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /*运行结果: 方法名: moveType 返回值类型: void 修饰符: 1 ----------------------------------------------- 方法名: studyInfo 返回值类型: void 修饰符: 1 ----------------------------------------------- 方法名: getSchool 返回值类型: class java.lang.String 修饰符: 1 ----------------------------------------------- ========分==================割===================线=========== 方法名: test 返回值类型: void 修饰符: 2 参数类型: java.lang.String ----------------------------------------------- 方法名: moveType 返回值类型: void 修饰符: 1 ----------------------------------------------- 方法名: studyInfo 返回值类型: void 修饰符: 1 ----------------------------------------------- 方法名: getSchool 返回值类型: class java.lang.String 修饰符: 1 ----------------------------------------------- */
五、获取类的属性和包
-
获取属性函数:
-
public Field[] getDeclaredFields()
返回此Class对象所表示的类或接口的全部属性。
-
public Field[] getFields()
返回此Class对象所表示的类或接口的public的属性。
注意:1. getDeclaredFields()仅获取本类中所有属性,不包含父类。
2. getFields()获取本类及其父类中的public修饰属性。
-
-
获取包函数:
-
Package getPackage()
返回此Class对象所在的包名称。
-
-
常用方法:(在获取属性和包中)
方法名 功能 getFields() 获取类(本类及父类)的公有属性 getDeclaredFields() 获取本类中所有的属性(公有+私有) getModifiers() 获取属性修饰符 getType() 获取属性的类型 getName() 获取属性名称 Package getPackage() 获取该类所属包名 -
以上方法案例展示:
注:父类Person()、子类Student()同(四)中案例。
package com.reflection; import java.lang.reflect.Field; public class Test5 { public static void main(String[] args) { try { Class clazz4 = Class.forName("com.reflection.Student"); Field[] fs = clazz4.getFields(); //获取类中所有公有属性,同时包含父类的属性 for (Field f : fs) { System.out.println("修饰符: " + f.getModifiers()); System.out.println("属性类型: " + f.getType()); System.out.println("属性名称: " + f.getName()); System.out.println("-----------------------------------"); } System.out.println("\n==========分===========割===========线===========\n"); Field[] afs = clazz4.getDeclaredFields(); //获取类中所有属性(私有+公有),不包含父类的属性。 for (Field af : afs) { System.out.println("修饰符: " + af.getModifiers()); System.out.println("属性类型: "+af.getType()); System.out.println("属性名称: " + af.getName()); System.out.println("-----------------------------------"); } System.out.println("\n==========分===========割===========线===========\n"); Package p = clazz4.getPackage(); System.out.println(p.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } /*运行结果: 修饰符: 1 属性类型: class java.lang.String 属性名称: school ----------------------------------- 修饰符: 1 属性类型: class java.lang.String 属性名称: name ----------------------------------- ==========分===========割===========线=========== 修饰符: 1 属性类型: class java.lang.String 属性名称: school ----------------------------------- 修饰符: 2 属性类型: class java.lang.String 属性名称: privateField ----------------------------------- ==========分===========割===========线=========== com.reflection */
六、调用类中指定方法、指定属性
– 调用指定方法
注:方法调用前需要通过构造器创建obj对象,为之后的方法调用做准备!
实际调用分以下四种情况讨论:
-
调用公有方法
Method m = clazz.getMethod("调用方法名","参数.class"...);
m.invoke(obj,"args"...);
//此处的invoke英文意思有“唤起”的意思,此处表示向对象obj中注入参数。 -
调用私有方法
需要额外注明
m.setAccessible(true)
,该语句表示解除私有封装,强行调用。其他语句同上。
-
调用重载方法
只需注意填入参数的不同即可。
-
调用有返回值的方法
需要注意对返回值进行接收,接受过程中可能需要对对象进行强转。
以上情况案例展示:
注:该案例引入Teacher类,其父类Person同上。
package com.reflection;
//Teacher类
public class Teacher extends Person{
public String school;
private String privateField;
//私有方法
private void test(String name){
this.name = name;
System.out.println("这里调用private void test(String name)方法");
}
//公有方法
public void setInfo(String name, String school){
this.name = name;
this.school = school;
System.out.println("这里调用public void setInfo(String name, String school)方法");
}
//重载方法
public void SetInfo(String school){
this.school = school;
System.out.println("这里调用public void setInfo(String school)方法");
}
//有返回值方法
public String getSchool(){
System.out.println("这里调用public String getSchool()方法");
return school;
}
}
package com.reflection;
//调用指定方法
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class Test6 {
public static void main(String[] args) {
try {
/*
以下不论是反射调用setInfo还是test方法
都调用的obj对象的方法,obj对象实际上就是Teacher对象
*/
Class clazz5 = Class.forName("com.reflection.Teacher");
Constructor con = clazz5.getConstructor(); //获取无参构造
Object obj = con.newInstance(); //使用无参构造创建对象
//调用公有方法
Method m = clazz5.getMethod("setInfo", String.class, String.class); //得到名称为setInfo,参数是(String,String)的方法
m.invoke(obj,"卡莉","第一中学");//参数1为需要实例化的对象,参数2为调用当前方法的实际参数
//调用私有方法(需要解封)
Method m1 = clazz5.getDeclaredMethod("test", String.class);
m1.setAccessible(true); //解除私有封装,则可调用私有方法
m1.invoke(obj,"苏瞻");
//调用重载方法,只需注意填入参数不同即可
Method m2 = clazz5.getMethod("SetInfo", String.class);
m2.invoke(obj,"第二中学");
//调用有返回值的方法,需要对返回值进行接收
Method m3 = clazz5.getMethod("getSchool");
String school = (String)m3.invoke(obj);//调用有返回值但是没有参数的方法
System.out.println(school);
} catch (Exception e) {
e.printStackTrace();
}
}
}
– 调用指定属性
注:方法调用前需要通过构造器创建obj对象,此处案例强转为Teacher对象,为之后调用属性准备!
实际调用分两种情况讨论:
-
调用公有属性
Field f = clazz.getField("属性名称");
f.set(对象名,“传入参数”);
//设置对应属性值f.get(对象名);
//获取对象的对应属性值 -
调用私有属性
Field f = clazz.getDeclaredField("私有属性名称");
f.setAccessible(true)
//非常重要!!!进行解除封装f.set(对象名,“传入参数”)
//设置对应属性值f.get(对象名);
//获取对象的对应属性值
以上情况案例展示:
注:Teacher类同上
package com.reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test7 {
public static void main(String[] args) {
try {
Class clazz6 = Class.forName("com.reflection.Teacher");
//反射创建一个对象
Constructor con = clazz6.getConstructor();
Teacher tea = (Teacher)con.newInstance();
//调用公有属性
Field f = clazz6.getField("school");
f.set(tea,"第三中学");//对tea对象的school属性设置值 "第三中学"
String school = (String)f.get(tea); //获取tea对象的school属性值
System.out.println(school);
System.out.println("----------------");
//调用私有属性:使用getDeclareField()方法,并解除封装
Field f1 = clazz6.getDeclaredField("privateField");
f1.setAccessible(true);
f1.set(tea,"测试私有属性");
String field = (String)f1.get(tea);
System.out.println(field);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*运行结果:
第三中学
----------------
测试私有属性
*/
七、动态代理
什么是动态代理?
例:一个Java项目,其中有100个java类,每个java有10个方法,总共1000个方法。现在有一个需求,需要在每个java方法上加上2句话,在方法执行前输出“这个方法开始执行”,在方法执行后输出“这个方法已经完成”。此时就需要使用到动态代理!
Proxy :专门完成代理的操作类,是所有动态代理类的父类。通过此类为一个或多个接口动态地生成实现类。
动态代理步骤:
-
创建需要被代理的类以及接口。
-
创建一个实现接口
InvocationHandler
的类,它必须实现invoke方法,以完成代理的具体操作。(ProxyDemo为实现接口的类,test为被代理对象),则使用如下语句生成代理对象handler。InvocationHandler handler = new ProxyDemo(test);
-
通过Proxy的静态方法,返回一个Subject接口代理。
newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
-
通过Subject代理调用RealSubject实现类的方法。
注意:如果一个对象想要通过Proxy.newProxyInstance方法被代理,那么这个对象的类一定要有相应的接口。
案例展示:
package com.proxy;
//被代理的接口
public interface ITestDemo {
void test1();
void test2();
}
package com.proxy;
//实现被代理接口的类
public class TestDemoImpl implements ITestDemo{
@Override
public void test1() {
System.out.println("执行test1()方法");
}
@Override
public void test2() {
System.out.println("执行test2()方法");
}
}
package com.proxy;
//实现了InvocationHandler接口的动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 动态代理类
*/
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; //返回Object对象
}
}
注意:其中invoke()方法有三个参数:
-
动态代理类的引用,通常情况下不需要它。但可以使用getClass()方法,得到proxy的Class类从而取得实例的类信息,如方法列表,annotation等。
-
方法对象的引用,代表被动态代理类调用的方法。从中可得到方法名,参数类型,返回类型等等
-
args对象数组,代表被调用方法的参数。注意基本类型(int,long)会被装箱成对象类型(Interger, Long)
package com.proxy;
//测试类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test1 {
public static void main(String[] args) {
//如果一个对象想要通过Proxy.newProxyInstance方法被代理
//那么这个对象的类一定要有相应的接口
//就像本类中ITestDemo接口和实现类TestDemoImpl
ITestDemo test = new TestDemoImpl();
test.test1();
test.test2();
System.out.println("====================================");
/**
* 需求:
* 在执行test1和test2方法时,需要加入一些东西
* 在执行方法前打印test1/test2开始执行
* 在执行方法后打印test1/test2执行完成
* 打印的方法名要和当时调用的方法保存一致
*/
InvocationHandler handler = new ProxyDemo(test); //handler为代理对象,代理test
//参数1为代理对象的类加载器
//参数2为被代理的对象的接口
//参数3为代理对象
//返回的值是成功被代理后的对象,返回Object类型,需要根据当时的情况去转换类型
ITestDemo t = (ITestDemo) Proxy.newProxyInstance(handler.getClass().getClassLoader(),test.getClass().getInterfaces(),handler);
t.test1();
System.out.println("====================================");
t.test2();
}
}
/*测试结果:
执行test1()方法
执行test2()方法
====================================
test1方法开始执行
执行test1()方法
test1方法执行完成
====================================
test2方法开始执行
执行test2()方法
test2方法执行完成
*/
注意:Proxy.newProxyInstance()方法有三个参数:
-
代理对象类加载器(Class Loader)
-
被代理对象的接口
-
InvocationHandler接口实例对象(也称代理对象)。所有动态代理类的方法调用,都会交由InvocationHandler接口实现类里的invoke()方法去处理。这是动态代理的关键所在。
写在最后
我以后会经常抱你。
To Dottie!