java笔记--反射进阶之总结与详解

一.反射进阶之动态设置类的私有域

 

"封装"是Java的三大特性之一,为了能更好保证其封装性,我们往往需要将域设置成私有的,

然后通过提供相对应的set和get方法来操作这个域。但是我们仍然可以用java的反射机制来

修改类的私有域,由于修改类的私有域会破坏Java"封装"的特性,故请慎重操作。

主要技术:
    Field类提供有关类或接口的单个字段的信息,以及对它的动态访问权限。
    访问的字段可能是一个类(静态)字段或实例字段。
   
        常用方法:
        set(Object obj,Object value)-----: 将指定对象变量上此Field对象表示的字段设置为指定的新值
        setBoolean(Object obj,boolean z)-: 将字段使得设置为指定对象上的一个boolean值
        setDouble(Object obj,double d)---: 将字段的值设置为指定对象上的一个double值
        setInt(Object obj,int i)---------: 将字段的值设置为指定对象上的一个int值
        setAccessible(boolean flag)------: 将此对象的accessible标志设置为指定的布尔值
       
       注:对于私有域,外部类调用的时候一定要使用setAccessible()方法将其可见性设置为true才能设置新值,
            否则将会抛出异。

--支持知识共享,转载请标注地址"http://www.cnblogs.com/XHJT/p/3922160.html "——和佑博客园,谢谢~~-- 

实例代码:

package com.xhj.reflection_excetion;

import java.lang.reflect.Field;

/**
 * 动态修改类的私有域
 * 
 * @author XIEHEJUN
 * 
 */
public class DynamicChangePrivate {
    private String userName;
    private int userAge;
    private String userAddress;
    private boolean userGender;

    public DynamicChangePrivate(String userName, int userAge,
            String userAddress, boolean userGender) {
        super();
        this.userName = userName;
        this.userAge = userAge;
        this.userAddress = userAddress;
        this.userGender = userGender;
    }

    public DynamicChangePrivate() {
        super();
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public int getUserAge() {
        return userAge;
    }

    public void setUserAge(int userAge) {
        this.userAge = userAge;
    }

    public String getUserAddress() {
        return userAddress;
    }

    public void setUserAddress(String userAddress) {
        this.userAddress = userAddress;
    }

    public boolean isUserGender() {
        return userGender;
    }

    public void setUserGender(boolean userGender) {
        this.userGender = userGender;
    }

    /**
     * 根据获得的性别的真假,获取其String值: true--为男 false--为女
     * 
     * @param userGender
     * @return
     */
    public String getGender(boolean userGender) {
        if (userGender) {
            return "男";
        } else {
            return "女";
        }
    }

    public static void main(String[] args) {
        DynamicChangePrivate user = new DynamicChangePrivate("小黑", 21,
                "北京路华西小区3单元446", true);
        Class<?> clazz = user.getClass();
        System.out.println("通过反射取得的对象全称为:" + clazz.getName());
        try {

            // 获取要修改的类的字段名称
            Field userName = clazz.getDeclaredField("userName");
            Field userAge = clazz.getDeclaredField("userAge");
            Field userAddress = clazz.getDeclaredField("userAddress");
            Field userGender = clazz.getDeclaredField("userGender");

            // 修改并输出新旧名称
            System.out.print("原名称为:" + user.getUserName());
            userName.set(user, "晓晓");
            System.out.println("\t\t\t修改后的名称为:" + user.getUserName());

            // 修改并输出新旧年龄
            System.out.print("原年龄为:" + user.getUserAge());
            userAge.set(user, 24);
            System.out.println("\t\t\t修改后的年龄为:" + user.getUserAge());

            // 修改并输出新旧地址
            System.out.print("原地址为:" + user.getUserAddress());
            userAddress.set(user, "石景山八角南里3单元506");
            System.out.println("\t修改后的地址为:" + user.getUserAddress());

            // 修改并输出新旧性别
            System.out.print("原性别为:" + user.getGender(user.isUserGender()));
            userGender.set(user, false);
            System.out.println("\t\t\t修改后的性别为:"
                    + user.getGender(user.isUserGender()));

        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

}

结果为:

image

 

 

注:任何类型的域,都可以通过set(Object obj, Object value) 方法将指定对象变量上此 Field 对象表示的字段设置为指定的新值。
但是Field本身也提供了其他类型相对应的set方法,如 setBoolean(Object obj, boolean z),setDouble(Object obj, double d)

等。另外,通过Field也可以设置public.protected域,但一般情况下很少这么设置,尤其是public域。在这里特别要注意的是:一定

要明确修改的含义,不要轻易的通过反射来修改类的私有域,因为这破坏了java面向对象"封装"的特性。

 

二、反射进阶之动态调用类的方法

 

我们知道Java是一种面向对象的语言,对他而言,一切都是对象,因此要调用类的方法,只能通过建立类的对象来调用。当然如果是
静态的方法,那就可以直接通过类本身来调用,而不需要建立类的对象。那么还有没有其他可以调用类方法的方式呢??

 

在Java的反射的机制中,提供了比较另类的调用方式,既可以根据需要指定要调用的方法,而不必在编程时确定。调用的方法不仅权限于public
的,还可以是private的。

 

Method类提供类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。它允许在匹配
要调用的实参与底层方法的形参时进行扩展转换,但是如果要进行收缩转换,则会抛出"非法参数异常"--IllegalArgumentExcetion。使用invoke()
可以实现动态调用方法:
   public Object invoke(Object obj,Object...args)throws IllegalArgumentException,IllegalAccessException,InvocationTargetExcetion
    obj--要调用的方法的类对象
    args--方法调用的参数

   
   注:对于私有域,外部类调用的时候一定要使用setAccessible,并且设置为true
   
实例代码:

package com.xhj.reflection_excetion;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 实现动态调用类的方法
 * 
 * @author XIEHEJUN
 * 
 */
public class DynamicCallMethod {

    public static void main(String[] args) {
        try {
            // 运行时动态调用Math的abs()静态方法
            System.out.println("运行时动态调用Math的abs()静态方法");
            Method meth = Math.class.getDeclaredMethod("abs", Integer.TYPE);
            Integer a = (Integer) meth.invoke(null, new Integer(-1));
            System.out.println("-1的绝对值为:" + a);

            // 运行时动态调用String的非静态方法contains()
            System.out.println("运行时动态调用String的非静态方法contains()");
            Method strMeth = String.class.getDeclaredMethod("contains",
                    CharSequence.class);
            boolean str = (Boolean) strMeth.invoke(new String("xhjit"), "xhj");
            System.out.println("xhjit中是否包含有xhj——" + str);

        } catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

}

结果为:

image

 

三、反射进阶之动态实例化类

 

在Java中,要实例化一个类即创建一个类的对象,需要通过构造方法来实现。但是在Java中还可以使用另外一种方式来实现,

那就是通过反射来实例化一个类。
   
    Constructor是Java中提供类的单个构造方法的信息以及访问权限的封装类。
    它允许在将实参与带有底层构造方法的形参的newInstance()匹配时进行扩展转换,
    但是如果发生收缩转换,则抛出IllegalArgumentExcetion。newInstance()方法可以
    使用指定的参数来创建对象:
   public T newInstance(Object...initargs)throws InstantiationException,IllegalAccessException,

                            IllegalArgumentException,InvocationTargetException
       
    initargs: 将作为变量传递给构造方法调用的对象数组。
   
    注:对于私有域,外部类调用的时候一定要使用setAccessible,并且设置为true。
   
代码实例为:

package com.xhj.reflection_excetion;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 动态实例化类
 * 
 * @author XIEHEJUN
 * 
 */
public class DynamiCreateClass {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        try {
            // 动态建立一个String对象,
            Constructor<?> construtor = String.class
                    .getConstructor(String.class);
            String str = (String) construtor.newInstance("000123");
            System.out.println(Integer.parseInt(str));

            // 动态建立一个txt文件,并将上面初始化后的String值写入文件的当中
            construtor = File.class.getConstructor(String.class);
            String url = "C:\\Users\\XIEHEJUN\\Desktop\\XHj.txt";
            File file = (File) construtor
                    .newInstance(url);
            file.createNewFile();
            if (file.exists()) {
                str += "---文件创建成功";
                System.out.println(str);
                OutputStream os = new FileOutputStream(url);
                os.write(str.getBytes());
                os.close();
            } else {
                System.out.println("创建文件失败");
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

结果为:

控制台输出

image

生成的文件:

image

注:在java中有两种不用new就可以建立类对象的方法,即Class.newInstance()Constructor.newInstance();
       区别在于:

                  前者只能调用无参构造方法,而后者却能调用有参构造方法;
                  且前者需要被调用的构造方法可见,后者则在特定情况下运行调用不可见的构造方法

 

四、反射进阶之自定义可变数组工具类 

 

在Java中,要创建可变数组可通过ArraryList类来实现。除此之外,我们也可以用自定义的方法来实现可变数组。
这里,我们将使用Java的反射机制实现一个工具类,通过这个工具类,我们就能实现可变数组的创建。

 

主要技术:


    Array类提供了动态创建和访问Java数组的方法。它允许在执行get,set操作期间进行扩展转换,但若发生收缩
    转换将抛出IllegalArgumentExcetion。为了创建新的数组对象,需要使用newInstance()方法,它可以根据指定
    的元素类型和长度创建新的数组:


   public static Object newInstance(Class<?> componentType,int length)throws NegativeArraySizeException


        componentType: 表示新数组的组件类型的Class对象
        length: 新数组的长度

 

     注:Java 中的数组不管是几维的,都是Object类型的s

 

代码实例:
可变数组工具类的实现:

package com.xhj.reflection_excetion;

import java.lang.reflect.Array;

/**
 * 用反射机制实现可变数组工具类
 * 
 * @author XIEHEJUN
 * 
 */
public class VariableArrayUtil {
    /**
     * 增加的数组长度值
     */
    private int addLength;
    /**
     * 需要增加长度的原数组
     */
    private Object array;

    public int getaddLength() {
        return addLength;
    }

    public Object getArray() {
        return array;
    }

    /**
     * 可变数组初始化
     * @param addLength 需要增加的数组的长度
     * @param array 需要增加长度的原数组
     */
    public VariableArrayUtil(int addLength, Object array) {
        super();
        this.addLength = addLength;
        this.array = array;
    }

    /**
     * 可变数组的实现
     * 
     * @return
     */
    public Object newArrary() {
        Class<?> clazz = array.getClass();
        if (clazz.isArray()) {
            Class<?> type = clazz.getComponentType();
            int length = Array.getLength(array);
            Object new_Array = Array.newInstance(type, length + addLength);
            System.arraycopy(array, 0, new_Array, 0, length);
            return new_Array;
        }
        return null;
    }

}

测试类:

package com.xhj.test;

import com.xhj.reflection_excetion.VariableArrayUtil;

/**
 * 可变数组工具类的测试类
 * 
 * @author XIEHEJUN
 * 
 */
public class Test {

    public static void main(String[] args) {
        int[] a = new int[10];
        System.out.println("原数组为:");
        for (int i = 0; i < 10; i++) {
            a[i] = i;
            System.out.print("  " + a[i]);
        }
        System.out.println("\n数组长度为:" + a.length);
        VariableArrayUtil util = new VariableArrayUtil(5, (Object) a);
        int[] b = (int[]) util.newArrary();
        System.out
                .println("==================================================\n"
                        + "更改后的数组长度为:" + b.length);
        for (int i = 10; i < 15; i++) {
            b[i] = i;
        }
        System.out.println("更改后的数组为:");
        for (int i : b) {
            System.out.print("  " + i);
        }
    }
    
}

结果为:

image

 

 

五、反射进阶之重写toString方法


为了方便输出对象,在Object中定义了toString的方法,其默认值由类名和哈希码组成。但是很多时候,为了能更好的满足我们的需求,

我们都是需要重写这个方法的。下面我们将利用Java的反射机制,重写这个方法,并输出类的相关信息。

 

代码实例:

package com.xhj.reflection_excetion;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 * 利用反射重写Object的toString方法
 * 
 * @author XIEHEJUN
 * 
 */
public class RewriteToString {
    public String toString(Object obj) {
        Class<?> clazz = obj.getClass();
        // 建立一个容器用来存储类的信息
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append("以下是类的信息:");
        // 类名
        String className = clazz.getSimpleName();
        // 包名
        Package packageName = clazz.getPackage();
        // 公共构造方法
        Constructor<?>[] constructors = clazz.getConstructors();
        // 公共域
        Field[] fields = clazz.getFields();
        // 接口
        Type[] interfaces = clazz.getInterfaces();
        // 公共方法
        Method[] methods = clazz.getMethods();

        strBuilder.append("\n此类的简单名称为--" + className);

        strBuilder.append("\n此类的包名为--" + packageName);

        strBuilder.append("\n此类的公共构造方法有:");
        if (constructors.length > 0) {
            for (Constructor<?> constructor : constructors) {
                strBuilder.append("\n\t" + constructor);
            }
        } else {
            strBuilder.append("空");
        }

        strBuilder.append("\n此类的公共域有:");
        if (fields.length > 0) {
            for (Field field : fields) {
                strBuilder.append("\n\t" + field);
            }
        } else {
            strBuilder.append("空");
        }

        strBuilder.append("\n此类的接口有:");
        if (fields.length > 0) {
            for (Type type : interfaces) {
                strBuilder.append("\n\t" + type);
            }
        } else {
            strBuilder.append("空");
        }

        strBuilder.append("\n此类的公共方法有:");
        if (methods.length > 0) {
            for (Method method : methods) {
                strBuilder.append("\n\t" + method);
            }
        } else {
            strBuilder.append("空");
        }
        return strBuilder.toString();
    }

    public static void main(String[] args) {
        RewriteToString rts = new RewriteToString();
        System.out.println(rts.toString(new StringBuilder()));
    }

}

结果为:

image

 

注:在实际开发当中,一般而言都是需要重写toString方法的,为了更好的开发,可使用Commons Lang 组件提供的工具类来重写此方法。

 

六、反射进阶之动态代理


   代理是Java中很重要的一个特性,使用代理可以在程序运行时创建一个实现指定接口的新类。一般而言,我们只有在程序编译时

    无法确定需要使用哪些接口时才会使用代理机制。代理更多的是用在系统的开发上,它可以为工具类提供更加灵活的特性。

 

    InvocationHandle 接口是代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的调用处理程序。

   对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的invoke()方法:


    Object invoke(Object proxy,Method method,Object[]args)throws Throwable
    proxy:代理类
    method: 代理实例上要被调用的方法
    args: 代理实例上方法调用的参数数组

 

    Proxy接口提供了用于创建动态代理类和实例的静态方法,它是所有动态代理类的父类。


    获得一个指定接口的代理类实例:
   public static Object newProxyInstance(ClassLoader loader,Class<?> [] interfaces,InvocationHandle h)throws IllegalArgumentException
    loader:定义代理类的类加载器
    interfaces:代理类要实现的接口列表
    h:指派方法调用的代理处理程序

 

代码实例:

接口:

package com.xhj.reflection_excetion.dynamicProxy.bean;

/**
 * 代理类和被代理类的共同接口
 * 
 * @author XIEHEJUN
 * 
 */
public interface DoBusiness {
    /**
     * 商品交易方式
     */
    public void trading();
}

被代理类:

package com.xhj.reflection_excetion.dynamicProxy.service;

import com.xhj.reflection_excetion.dynamicProxy.bean.DoBusiness;

/**
 * 被代理类--厂家
 * 
 * @author XIEHEJUN
 * 
 */
public class Product implements DoBusiness {

    @Override
    public void trading() {
        System.out.println("厂家直销");
    }

}

代理处理器:

package com.xhj.reflection_excetion.dynamicProxy.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 代理处理器--商家代理
 * 
 * @author XIEHEJUN
 * 
 */
public class DynamicProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println("商家代理定点销售");
        return null;
    }
}

测试类:

package com.xhj.reflection_excetion.dynamicProxy;

import java.lang.reflect.Proxy;

import com.xhj.reflection_excetion.dynamicProxy.bean.DoBusiness;
import com.xhj.reflection_excetion.dynamicProxy.proxy.DynamicProxy;
import com.xhj.reflection_excetion.dynamicProxy.service.Product;

public class Test {

    public static void main(String[] args) {
        DoBusiness p = new Product();
        System.out.print("没有启用代理模式---");
        p.trading();
        ClassLoader loader = p.getClass().getClassLoader();
        p = (DoBusiness) Proxy.newProxyInstance(loader,
                Product.class.getInterfaces(), new DynamicProxy());
        System.out.print("启用动态代理模式---");
        p.trading();
    }

}

结果为:

image

 

注:
java动态代理类位于java.lang.reflect包下,一般主要涉及到以下两个类:

Interface InvocationHandler:该接口中仅定义了一个方法Object:invoke(Object obj,Method method, Object[] args)。
                                            在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的trading(),

                                            args为该方法的参数数组。这个抽象方法在代理类(代理处理类)中动态实现。
Proxy:该类即为动态代理类。
    Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):
        返回代理类的一个实例,返回后的代理类可以当作被代理类使用 (可使用被代理类的在接口中声明过的方法)。

 

 

总结:
  动态代理类是一个实现在创建类并运行时指定接口列表的类,
    1.代理接口是代理类所实现的一个接口。
    2.代理实例是代理类的一个实例。
    3.每一个代理实例都有一个关联的调用处理程序对象,它可以实现接口InvocationHandler。
    4.通过调用代理处理器中的Invoke方法实现代理,并传递代理实例,识别调用方法以及方法上的参数数组。
    5.调用对象加载器以及代理处理器中的方法,并以代理实例为结果返回。

 

posted @ 2014-08-19 15:09  Liape  阅读(3572)  评论(0编辑  收藏  举报