Java 反射与动态代理

来源:互联网 发布:雷电软件儿 编辑:程序博客网 时间:2024/06/07 20:31

一,反射

1.Class类

  1. 源文件(T.java)经过编译后生成类文件,实例化该类生成对象运行;java运行时系统为会所有的对象维护一个被称为“运行时”的类型标记,跟踪着每一个对象所属的类,保存着对象所属类的信息,而用于保存这些信息的类被称为Class。

  2. 每一个Class对象都是一个保存对象所属的类信息的类文件,每一个类文件的类型是Class。

    • 例:类型为T的对象的类文件是T.class,T.class是一个对象,其类型为Class。(体现了Java“万物皆对象,物(对象)以类聚”的思想:T.class类文件也是一个对象,其类型为Class)
  3. 获取一个对象T的类文件T.class的三种方法:

    //方式一:对象的getClass()方法Class cl = new Date().getClass();//方式二:Class的静态方法forName(String)根据类名加载,若String不是类名或接口名,出现异常Class cl = Class.forName("java.util.Date");//方式三:类名.classClass cl = Date.class;
  4. Class对象的newInstance()方法可以创建Class对象所指类的实例

    //无参对象Class.forName("java.util.Date").newInstance();

    注意:使用newInstance()要求Class对象所表示的类必须有空参数的构造函数,若没有空参构造,则可以获取构造函数的Constructor对象创建带参对象。

        public class UserRef {        public static void main(String[] args) throws Exception {            //使用forName,传入类型名用.分隔包名            Class<User> cl = (Class<User>) Class.forName("com.common.User");            createInstance(cl);        }        //测试利用Class对象创建带参的实例        public static void createInstance(Class<User> clazz) throws Exception{            //获取构造函数,注意参数类型可以使用int.class==Integer.TYPE,但不能使用Integer.class            Constructor<User> cons =  clazz.getConstructor(Integer.TYPE, String.class);            Object[] initargs = new Object[]{20, "hi"};     //初始化参数            User u = cons.newInstance(initargs);            System.out.println(u);        }    }    //User对象    package com.common;    public class User {        private int id;        private String name;        public User(int id, String name) {            super();            this.id = id;            this.name = name;        }        //setters and getters     }
  5. 基础数据类型与其包装类的关系:
    基础类型.class == 包装类.TYPE,但不等于包装类.class

    public void testBasicType(){    sop(Integer.TYPE == int.class);         //true    sop(Integer.class == int.class);        //false    sop(Double.TYPE == double.class);       //true    sop(Double.class == double.class);      //false    sop(Character.TYPE == char.class);      //true    sop(Character.class == char.class);     //false    sop(Boolean.TYPE == boolean.class);     //true    sop(Boolean.class == boolean.class);    //false }

2.利用反射分析类的能力

  1. 利用反射可以获取一个运行对象的所有属性:

    • 类修饰符
    • 属性的修饰符、名称等
    • 构造方法的修饰符、名称、参数类型、返回类型等
    • 通用方法的修饰符、名称、参数类型、返回类型等
  2. 实例:

    //打印该类的所有构造函数public static void printConstructors(Class cl){    //返回cl.class的所有构造函数    Constructor[] constructors = cl.getDeclaredConstructors();    for(Constructor c : constructors){        //获得构造函数类型        String name = c.getName();        //获得构造函数修饰符        String modifiers = Modifier.toString(c.getModifiers());        System.out.print("  " + modifiers + " " +  name + " (");        //获得构造函数所有形参,也是class对象        Class[] types = c.getParameterTypes();        for(int  j = 0; j < types.length; j++){            //列出所有形参            if(j > 0)   System.out.print(" ,");            String tname = types[j].getName();            System.out.print(tname);        }        System.out.println(")");    }}//打印所有方法public static void printMethods(Class cl) {    Method[] methods = cl.getDeclaredMethods();    for(Method m : methods){        //打印方法的修饰符、返回类型、方法名、参数        String mname = m.getName();        Class returntype = m.getReturnType();        String modifiers = Modifier.toString(m.getModifiers());        System.out.print("  "+modifiers+" "+ returntype.getName() + " " + mname + " (");        //参数        Class[] paramTypes = m.getParameterTypes();        for(int  j = 0; j < paramTypes.length; j++){            //列出所有形参            if(j > 0)   System.out.print(" ,");            String tname = paramTypes[j].getName();            System.out.print(tname);        }        System.out.println(")");    }}//打印所有域public static void printFields(Class cl){    Field[] fields = cl.getDeclaredFields();    for(Field f : fields){        //打印域的修饰符、名称        String name = f.getName();        String modifiers = Modifier.toString(cl.getModifiers());        System.out.println("\t"+ modifiers + " "+ name);    }}

3. 运行时利用反射分析对象

  1. 通过反射可以获取类中的域和方法等属性,而且利用反射也可以查看该类对象的域值。

  2. 查看对象域值的关键方法是Field类的get方法;

    Employee harry = new Employee("harry potter", 3500, 10, 1, 1988);        Class cl = harry.getClass();Field f = cl.getDeclaredField("name");f.setAccessible(true);Object v = f.get(harry);        //harry potterf.set(harry, "john wick");System.out.println(v);      
    • f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回obj对象中的f域的值,其类型为Object。
    • 调用f.set(obj, newvalue)可以将obj对象中f域值设为新值newValue。

4. 反射编写泛型数组

  1. Arrays.copyOf(数组变量,新长度)可以动态地扩展已经填满的数组。

    Employee[] e = new Employee[100];   ...e = Arrays.copyOf(e, 200);
  2. 利用反射可以编写一个类似的扩展数组操作

        //传入数组对象和长度    public static Object goodCopyOf(Object a, int newLength){        Class cl = a.getClass();        if(!cl.isArray())   return null;    //非数组返回空        //获取数组元素类型        Class componentType = cl.getComponentType();        //根据长度扩展数组        int length = Array.getLength(a);    //原长度        Object newArray = Array.newInstance(componentType, newLength);        //将原来数组数据复制到新数组数据        System.arraycopy(a, 0, newArray, 0, Math.min(newLength, length));        return newArray;    }    //使用    e = (Employee)goodCopyOf(e, 200);

    注意使用了Array.newInstance(componentType, newLength)创建一个长度为newLength,数组元素类型为componentType的新数组。

4. 调用任意方法

  1. Field对象的get方法可以查看域所在类的对象中的域值,Method也有一个invoke方法,允许调用封装在当前Method所在对象的该方法。签名:
    Object invoke(Object obj, Object… args);
    第一个参数obj需传入method所在对象,若方法是静态的,可以设为null
    第二个参数args需要传入调用该方法所需的参数值,若方法为无参,可以不传。

  2. 实例:

    public class MethodPointerTest {    public static void main(String[] args) throws Exception {        Class cl = MethodPointerTest.class;                     //当前类的类文件对象        Method square = cl.getMethod("square", double.class);   //获取cl类中名称为square,参数类型为double的Method对象        //获取Math类中名称为sqrt,参数类型为double的Method对象        Method sqrt = Math.class.getMethod("sqrt", double.class);        printTableMethod(1, 10, 10, square);        printTableMethod(1, 10, 10, sqrt);    }    public static double square(double x){        return x * x;    }    public static void printTableMethod(double f, double t, int n, Method m) throws Exception{        //调用m方法,从m(f)到m(t)打印n个函数数据        double s = (t - f) / (n - 1);        for(double x = f; x <= t; x += s){            //invoke调用m方法,计算x            double y = (Double)m.invoke(null, x);            System.out.printf("%10.4f | %10.4f%n", x, y);        }    }}

二,动态代理

  1. 当在程序运行时,需要创建实现了某些接口的对象,但不清楚这些接口具体是什么(真正需要调用时才能确定这些接口),此时即可使用动态代理的方式生成实现具体接口的对象。方式:

    Object proxy = Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

    第一个参数:类加载器,使用null作为默认的类加载器
    第二个参数:需要实现的具体接口的Class对象集合
    第三个参数:调用处理器,实现了InvocationHandler接口的对象。

  2. 调用处理器:不仅包装了需要代理的对象(构造方法传入),而且还说明了对接口中的方法的调用方式:一切对接口中的方法的调用都由调用处理器中的invoke方法完成。

  3. 具体流程:一个对象obj需要实现某些接口,但不确定,可以将obj传入调用处理器InvocationHandler,再由Proxy.newProxyInstance生成obj对象的代理对象proxy,对proxy中接口方法的调用都由invoke方法实现。常用于:对已实现了接口的对象完成接口方法的覆盖。

  4. 示例:动态代理生成一个实现了Comparable接口的Integer对象,并对Comparable接口的CompareTo方法覆盖,改写。

    public class ProxyTest {    public static void main(String[] args) {        Object[] elements = new Object[1000];        for(int i = 0; i < elements.length; i++){            //需代理的对象            Integer value = i + 1;            //调用处理器            TraceHandler handler = new TraceHandler(value);            //生成代理对象            Object proxy = Proxy.newProxyInstance(null, new Class[]{Comparable.class}, handler);            //数组存储代理对象            elements[i] = proxy;        }        //随机取一个整数        Integer key = new Random().nextInt(elements.length) + 1;        //二分查找        int result = Arrays.binarySearch(elements, key);        if(result > 0)            System.out.println(elements[result]);    }}public class TraceHandler implements InvocationHandler{    private Object target;      //需代理的对象    public TraceHandler(Object obj) {        //存储包装对象        this.target = obj;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args)            throws Throwable {        // 对接口中方法的具体调用都由invoke实现        System.out.print(target);        System.out.print("."+method.getName()+" (");        //打印参数        if(args !=  null){            for(int i = 0; i < args.length; i++){                System.out.print(args[i]);                if(i < args.length - 1)                    System.out.print(", ");            }        }        System.out.println(")");        //方法正常调用,注意具体实现是由需要代理的对象target完成        return method.invoke(target, args);    }   }
原创粉丝点击