Java 反射与动态代理

一,反射

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");
    //方式三:类名.class
    Class 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 potter
    f.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);
        }   
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值