1.什么是反射
关于反射,我用自己的话总结下:java语言中,在运行状态中,通过某种方式或手段获取任何一个类的属性,方法和修饰符等,这种方式或手段是通过Class对象来实现的,也可以这么说,java虚拟机在运行的时候,都会为每个类创建一个与之相对应Class类型的实例,可以通过这个Class类的实例创建与之对应的对象,并且可以访问,修改这个类的实例的信息。
2.学习反射有哪些好处
1.反射机制很强大,可以通过反射对程序本身的做一个审查。
2.反射是构建框架技术的基础,更有利于我们主流框架技术的理解。
3.反射能让我们写出更加灵活的程序,比如利用动态代理
3.熟悉Class类型
java是面向对象语言,在java中一切都是对象,类是java.lang.Class类的实例对象,当我们创建一个类的时候,你可以通过“类名.class”来创建一个Class类的对象,。举个例子说下:
public class Reflect{
public static void main(String []args){
Class bjkc = BestJiKe.class
}
class BestJiKe{
public void print(String str,int i){
System.out.println(str+i);
System.out.println("Hello Best 极客");
}
}
}
以上说明,BestJiKe这个类是java.lang.Class的实例对象,因为每一类都有一个class属性,任何一个类都是java.lang.Class的实例对象,bjkc表示是BestJiKe类的类类型(class type)。
4.Class实例对象使用
4.1 获取class对象的三种方法(以上面的例子为例)
public class Reflect{
public static void main(String []args){
//1.第一种方式
Class cla1 = BestJiKe.class
//2.第二种方式
BestJiKe bestJiKe = new BestJiKe();
Class cla2 = bestJiKe.getClass();
//3.第三种方式
Class cla3 = null;
try{
//必须是全类名,包括包名
cla3 = Class.forName("com.bestjike.reflect.BestJiKe");
}catch(ClassNotFoundException e){
}
}
class BestJiKe{}
}
4.2 动态地获取类的对象
运行时加载类是动态加载,编译时加载类是静态加载
//必须是全类名,包括包名,同时父类中要有无参构造方法
cla3 = Class.forName("com.bestjike.reflect.BestJiKe");
bestJiKe = (BestJiKe)cla3.newInstance();
4.3 Class类实例对象的一些方法
cla3.getName() 获取父类的类名。
cla3.getMethods() 返回一个Method类型的数组,获取类的public修饰的成员方法,包括子类继承父类成员方法的信息。
cla3.getDeclaredMethods() 返回一个Method类型的数组,获取该类自己定义的成员方法,不受访问权限控制。
cla3.getFields() 返回一个Field类型的数组,获取类的public修饰的成员变量。
cla3.getDeclaredFields() 返回一个Field类型的数组,获取该类自己定义的成员变量。修改私有变量需要修改field.setAccessible(true)设置允许访问。
cla3.getConstructors() 返回一个Constructor类型的数组,获取类的public修饰的构造方法信息。
cla3.getDeclaredConstructors() 返回一个Constructor类型的数组,获取该类自己定义的构造方法。
以上是class对象的常用方法,了解更多的方法需要去查看官方文档。
4.4 方法在反射中的调用
//获取
cla4 = Class.forName("com.bestjike.reflect.BestJiKe");
Method m = cla4.getDeclaredMethod("print",new Class[]{String.class,int.class});
m.invoke(cla4.newInstance(),"hello",0);
说明:先得到BestJiKe类类型cla4,然后通过cla4获取Method对象m,最后m对象调用invoke方法。其中获取Method对象的方法getDeclaredMethod中有两个参数,一个是方法名称,另一个是Class对象数组或Class对象列表。invoke方法中传一个对象和对应的参数。注静态方法不需要newInStance(),如m.invoke(cla4,”hello”,0);
4.5泛型在反射中的实质
首先,反射的应用是编译之后的,编译之后的集合是去泛型化的,java的泛型为了防止错误输入,只在编译时有效,举个例子说下
ArrayList list1 = new ArrayList();
ArrayList<String> list2 = new ArrayList()<String>;
list2.add(10); //出错
list2.add("Bestjike");
Class c1 = list1.getClass();
Class c2 = list2.getClass();
//输出的结果为true
System.out.println(c1 == c2);
//当然我们可以绕过编译,去证实下
Method d = c2.getMethod("add",Object.class);
m.invoke(list2,10);
//输出结果为2,说明可以添加
System.out.println(list2.size());
5.动态代理
5.1 继承与聚合
在了解动态代理之前,我有必要提要下实现代理的两种方式:继承和聚合,举个例子说下:
//定义一个接口,形状
interface Shape{
//计算面积
int getArea(int width,int height);
}
//定义一个矩形 求矩形的面积
class Rectangle implements Shape{
//求面积
@Overrivide
public int getArea(int width,int height){
return width*height;
}
}
对求矩形面积的方法前后打印日志,当然这样的例子没任何意义,目的是说清楚代理是怎么一回事,代码当然不能运行
//继承的方式,实现代理
class ExtendProxy extend Rectangle{
//求矩形面积
@Overrivide
public int getArea(int width,int height){
System.out.println("before");
super.getArea(width,height);
System.out.println("after");
}
}
//聚合的方式,实现代理
class ConvergeProxy implements Shape{
public ConvergeProxy(){
s = new Rectangle();
}
Shape s;
//求矩形面积
@Overrivide
public int getArea(int width,int height){
System.out.println("before");
s.getArea(width,height);
System.out.println("after");
}
}
从上面的例子很明显就可以看出,继承表明的类的关系是:ExtendProxy继承Rectangle类;聚合表明类的关系是:Rectangle类的对象是ConvergeProxy成员变量。利用聚合的方式比继承的方式更加灵活,当需求有变更或者不仅仅去添加日志的时候而是功能叠加的时候,你会发现聚合更灵活,我们只需要多次去传实现shape接口的代理类的对象,就可以实现功能的叠加,而不像继承,创建继承一个上一个功能类的类的对象完成功能的叠加。
5.2 动态代理
由于聚合和继承都需要创建很多的代理类去实现不一样的功能,类的增多会导致项目难以管理,而动态代理就很好了解决这样的问题,我们只需要创建一个代理对象。动态代理个人理解:在虚拟机运行的时候动态地去创建一个代理类去执行被代理的对象的方法。动态代理在jdk的真实情况就是“反射”和“动态生成代理类字节码即.class文件”的结合
动态代理实现的步骤:
1. 创建被代理的类,被代理的类必须实现了某个接口
2. 实现InvocationHandler接口,这是负责连接代理类和被代理类的中间类实现的接口。
3. 通过Proxy类新建代理类的对象
代码如下:
class BestJikeInvocationHandler implements InvocationHandler{
private Object obj;
public BestJikeInvocationHandler(Object obj){
this.obj = obj
}
@override
public Object invoke(Object proxy,Method method,Object[] args){
//to do
}
}
InvocationHandler handler = new BestJikeInvocationHandler(new Rectangle());
Shape s = (Shape)Proxy.newProxyInstance(
Shape.class.getClassLoader(),new Class[]{Shape.class},
handler);
s.getArea(3,4);
说明:BestJikeInvocationHandler类实现了InvocationHandler接口,同时重写了invoke方法,其中invoke有三个参数,proxy表示通过newProxyInstance生成的代理类对象,method表示动态代理实现的接口的被调用的方法,args是被调用方法的参数。newProxyInstance()方法创建代理对象,方法中同样有三个参数,第一个参数是类加载器用来将动态代理类加载到内存,第二各参数代理类要实现接口的数组,第三个参数实现了InvocationHandler接口的类的对象目的是将方法的调用转到代理上。
总结下:实现了Shape接口的类的对象new Rectangle()作为BestJikeInvocationHandler类的构造函数的参数创建对象handler ,然后调用了Proxy类的newProxyInstance()函数创建代理对象,当然这个代理对象是动态生成的,代理类的对象会调用handler的invoke方法,而invoke方法会调用被代理类对象的相应的方法。动态代理和反射的关系已经很明显了,BestJikeInvocationHandler通过反射调用,动态代理是基于Java的反射机制实现。调用的方式可以用图表示:
动态代理虽然能帮我们很好的解决聚合和继承类增多的问题,但它的缺点是就是不能对类进行代理,如果类没有实现接口,就不能用代理,每一个类至少继承一个接口,不能说是缺点,这样的接口编程却能带来更好的灵活性。