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

时间:2023-09-05 22:19:38

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

"封装"是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();
} } }

结果为:

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

注:任何类型的域,都可以通过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();
} } }

结果为:

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

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

在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();
} } }

结果为:

控制台输出

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

生成的文件:

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

注:在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);
}
} }

结果为:

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

五、反射进阶之重写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()));
} }

结果为:

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

注:在实际开发当中,一般而言都是需要重写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();
} }

结果为:

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

注:
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.调用对象加载器以及代理处理器中的方法,并以代理实例为结果返回。