Java反射机制

Java反射机制

Java反射机制是在程序运行时,对于任意一个类,都能知道这个类的所有属性和方法,都能够调用它的任意一个方法和属性。这种动态的获取信息和动态调用对象的方法的功能称为Java的反射机制。

反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。

在java中给我们提供了几个这几个类用于描述编译后的各种类:

描述
java.lang.Class描述编译后的class文件的对象
java.lang.reflect.Constructor用于描述构造方法
java.lang.reflect.Field描述字段(成员变量)
java.lang.reflect.Method描述成员方法

如何使用反射:

  1. 使用Class类,获取出被解剖的这个类的class文件对象
  2. 使用Class类方法,获取出类中的所有成员
  3. 将成员获取出来后,交给对应类,对应类中的方法,运行成员

获取class文件对象的方法:

  1. 使用类的对象获取,每个类都使用Object作为父类,Object类方法 getClass()返回这个类的class文件对象
  2. 使用类的静态属性获取,类名.class 返回这个类的class文件对象
  3. 使用Class类的静态方法获取,Class类静态方法 forName(String 类名) 传递字符串类名获取到这个类的class文件对象

在使用第三种方法获取类时,可以不使用import语句导入一个明确的类,而类的名称是采用字符串的形式进行描述的。

使用反射获取类的信息

FatherClass.java

public class FatherClass {
    public String mFatherName;
    public int mFatherAge;
    
    public void printFatherMsg(){}
}

SonClass.java

public class SonClass extends FatherClass{

    private String mSonName;
    protected int mSonAge;
    public String mSonBirthday;

    public void printSonMsg(){
        System.out.println("Son Msg - name : "
                + mSonName + "; age : " + mSonAge);
    }

    private void setSonName(String name){
        mSonName = name;
    }

    private void setSonAge(int age){
        mSonAge = age;
    }

    private int getSonAge(){
        return mSonAge;
    }

    private String getSonName(){
        return mSonName;
    }
}

获取所有信息:

/**
 * 通过反射获取类的所有变量
 */
private static void printFields(){
    //1.获取并输出类的名称
    Class mClass = SonClass.class;
    System.out.println("类的名称:" + mClass.getName());
    
    //2.1 获取所有 public 访问权限的变量
    // 包括本类声明的和从父类继承的
    Field[] fields = mClass.getFields();
    
    //2.2 获取所有本类声明的变量(不问访问权限)
    //Field[] fields = mClass.getDeclaredFields();
    
    //3. 遍历变量并输出变量信息
    for (Field field :
            fields) {
        //获取访问权限并输出
        int modifiers = field.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //输出变量的类型及变量名
        System.out.println(field.getType().getName()
		         + " " + field.getName());
    }
}

调用 getFields() 方法,输出 SonClass 类以及其所继承的父类( 包括 FatherClass 和 Object ) 的 public 方法。

类的名称:obj.SonClass
public java.lang.String mSonBirthday
public java.lang.String mFatherName
public int mFatherAge

调用 getDeclaredFields() , 输出 SonClass 类的所有成员变量,不问访问权限。

类的名称:obj.SonClass
private java.lang.String mSonName
protected int mSonAge
public java.lang.String mSonBirthday

获取类的所有方法信息:

/**
 * 通过反射获取类的所有方法
 */
private static void printMethods(){
    //1.获取并输出类的名称
    Class mClass = SonClass.class;
    System.out.println("类的名称:" + mClass.getName());
    
    //2.1 获取所有 public 访问权限的方法
    //包括自己声明和从父类继承的
    Method[] mMethods = mClass.getMethods();
    
    //2.2 获取所有本类的的方法(不问访问权限)
    //Method[] mMethods = mClass.getDeclaredMethods();
    
    //3.遍历所有方法
    for (Method method :
            mMethods) {
        //获取并输出方法的访问权限(Modifiers:修饰符)
        int modifiers = method.getModifiers();
        System.out.print(Modifier.toString(modifiers) + " ");
        //获取并输出方法的返回值类型
        Class returnType = method.getReturnType();
        System.out.print(returnType.getName() + " "
                + method.getName() + "( ");
        //获取并输出方法的所有参数
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter:
             parameters) {
            System.out.print(parameter.getType().getName()
		            + " " + parameter.getName() + ",");
        }
        //获取并输出方法抛出的异常
        Class[] exceptionTypes = method.getExceptionTypes();
        if (exceptionTypes.length == 0){
            System.out.println(" )");
        }
        else {
            for (Class c : exceptionTypes) {
                System.out.println(" ) throws "
                        + c.getName());
            }
        }
    }
}

调用 getMethods() 方法 获取 SonClass 类所有 public 访问权限的方法,包括从父类继承的:

类的名称:obj.SonClass
public void printSonMsg()
public void printFatherMsg()
public final void wait() throws java.lang.InterruptedException
public final void wait(long arg0,int arg1) throws java.lang.InterruptedException
public final native void wait(long arg0,) throws java.lang.InterruptedException
public boolean equals(java.lang.Object arg0)
public java.lang.String toString()
public native int hashCode()
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()

调用 getDeclaredMethods() 方法,输出的都是 SonClass 类的方法,不问访问权限:

类的名称:obj.SonClass
private int getSonAge(  )
private void setSonAge( int arg0, )
public void printSonMsg(  )
private void setSonName( java.lang.String arg0, )
private java.lang.String getSonName(  )

反射可以做到什么?
访问或操作类的私有变量和方法。
都知道,对象是无法访问或操作类的私有变量和方法的,但是,通过反射,我们就可以做到。
TestClass.java

public class TestClass {

    private String MSG = "Original";

    private void privateMethod(String head , int tail){
        System.out.print(head + tail);
    }

    public String getMsg(){
        return MSG;
    }
}

测试代码:

/**
 * 访问对象的私有方法
 * 为简洁代码,在方法上抛出总的异常,实际开发别这样
 */
private static void getPrivateMethod() throws Exception{
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();
    
    //2. 获取私有方法
    //第一个参数为要获取的私有方法的名称
    //第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
    //方法参数也可这么写 :new Class[]{String.class , int.class}
    Method privateMethod =
            mClass.getDeclaredMethod("privateMethod", String.class, int.class);
            
    //3. 开始操作方法
    if (privateMethod != null) {
        //获取私有方法的访问权
        //只是获取访问权,并不是修改实际权限
        privateMethod.setAccessible(true);
        
        //使用 invoke 反射调用私有方法
        //privateMethod 是获取到的私有方法
        //testClass 要操作的对象
        //后面两个参数传实参
        privateMethod.invoke(testClass, "Java Reflect ", 666);
    }
}

需要注意的是,第3步中的 setAccessible(true) 方法,是获取私有方法的访问权限,如果不加会报异常 IllegalAccessException,因为当前方法访问权限是“private”的,如下:

java.lang.IllegalAccessException: Class MainClass can not access a member of class obj.TestClass with modifiers "private"

正常运行后,打印如下,调用私有方法成功:

Java Reflect 666
利用反射修改私有变量

以修改 TestClass 类中的私有变量 MSG 为例,其初始值为 “Original” ,我们要修改为 “Modified”。

/**
 * 修改对象私有变量的值
 * 为简洁代码,在方法上抛出总的异常
 */
private static void modifyPrivateFiled() throws Exception {
    //1. 获取 Class 类实例
    TestClass testClass = new TestClass();
    Class mClass = testClass.getClass();
    
    //2. 获取私有变量
    Field privateField = mClass.getDeclaredField("MSG");
    
    //3. 操作私有变量
    if (privateField != null) {
        //获取私有变量的访问权
        privateField.setAccessible(true);
        
        //修改私有变量,并输出以测试
        System.out.println("Before Modify:MSG = " + testClass.getMsg());
        
        //调用 set(object , value) 修改变量的值
        //privateField 是获取到的私有变量
        //testClass 要操作的对象
        //"Modified" 为要修改成的值
        privateField.set(testClass, "Modified");
        System.out.println("After Modify:MSG = " + testClass.getMsg());
    }
}

输出:

Before Modify:MSG = Original
After Modify:MSG = Modified

反射的应用
1.解耦
PS: 在任何的开发之中,new是造成耦合的最大元凶。一切的耦合都起源于new。
范例:观察者工厂模式

    interface Fruit{
	void eat();
    }
    class Apple implements Fruit{
	@Override
	public void eat() {
	    System.out.println("* 吃苹果 *");
	}
    }
    class Factory{
	public static Fruit getInstance(String className){
	    if("apple" == className){
		return new Apple();
	    }
	    return null;
	}
    }
    public class TestFactory {
	public static void main(String[] args) {
	    Fruit f = Factory.getInstance("apple");
	    f.eat();
	}
    }

输出结果:"* 吃苹果 *"
代码输出如上,但是此时,如果我们要增加一个Fruit接口子类“orange”,就意味着我们就要修改工厂模式的方法。

    class Orange implements Fruit{
	@Override
	public void eat() {
		System.out.println("* 吃橘子 *");
	}
    }
    class Factory{
	public static Fruit getInstance(String className){
	    if("apple" == className){
		return new Apple();
	    }else if("orange" == className){
		return new Orange();	
            }
	    return null;
	}
    }

由此可见,每增加一个Fruit接口子类,就要修改工厂类,那么如果随时需要增加子类呢?
因为现在工厂类都是new关键字直接实例化的,所以new就造成了所有问题的关键点。要想解决这一问题,就只能依靠反射完成。
修改工厂模式的方法如下:

    package com.jkx.lzh.test;

   interface Fruit{
   void eat();
   }
   class Apple implements Fruit{
   @Override
   public void eat() {
       System.out.println("* 吃苹果 *");
   }
   }
   class Orange implements Fruit{
   @Override
   public void eat() {
       System.out.println("* 吃橘子 *");
   }
   }
   class Factory{
   public static Fruit getInstance(String className){
       Fruit f = null;
       try {
           f = (Fruit) Class.forName(className).newInstance();
       } catch (Exception e) {
   	e.printStackTrace();
       }
       return f;
   }
   }
   public class TestFactory {
   public static void main(String[] args) {
       Fruit f = Factory.getInstance("com.jkx.lzh.test.Apple");
       f.eat();
   }
   }

此时的程序就真正的完成了解耦合的目的,而且可扩展性非常强。

2.动态代理
使用动态代理实现统计系统在线人数的例子:
User.java

public interface User {
   /**
    * 登录
    * @param name 用户名
    * @param pwd 密码
    * @return
    */
   public boolean login(String username, String pwd);

   /**
    * 退出
    */
   public void logout(String username);
}

实现类如下:
UserImpl.java

public class UserImpl implements User{

   @Override
   public boolean login(String username, String pwd) {
       // 简化问题,直接登录成功
       System.out.println(username+" 登录成功.");
       return true;
   }

   @Override
   public void logout(String username) {
       System.out.println(username+" 成功退出.");
   }

}

动态代理类UserDynamicProxy.java

/**
* 继承动态代理接口的代理类
*/
public class UserDynamicProxy implements InvocationHandler{

   // 在线人数
   public static int count = 0;
   // 委托对象
   private Object target; 

   /**
    * 返回代理对象
    * @param target
    * @return
    */
   @SuppressWarnings("unchecked")
   public <T> T getProxyInstance(Object target) {
       // 委托对象,真正的业务对象
       this.target = target;
       // 获取Object类的ClassLoader
       ClassLoader cl = target.getClass().getClassLoader();
       // 获取接口数组
       Class<?>[] cs = target.getClass().getInterfaces();
       // 获取代理对象并返回
       return (T)Proxy.newProxyInstance(cl, cs, this);
   }

   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       Object r = null;
       // 执行之前
       r = method.invoke(target, args);
       // 判断如果是登录方法
       if("login".equals(method.getName())) {
           if((boolean)r == true) {
               // 当前在线人数+1
               count += 1;
           }
       } 
       // 判断如果是退出方法
       else if("logout".equals(method.getName())) {
           // 当前在线人数-1
           count -= 1;
       }
       showCount(); // 输出在线人数
       // 执行之后
       return r;
   }

   /**
    * 输出在线人数
    */
   public void showCount() {
       System.out.println("当前在线人数:"+count+" 人.");
   }

}

使用场景:

public class Main {

   public static void main(String[] args) {
       // 真实角色,委托人
       User user = new UserImpl();    // 可执行真正的登录退出功能

       // 代理类
       UserDynamicProxy proxy = new UserDynamicProxy();

       // 获取委托对象user的代理对象
       User userProxy = proxy.getProxyInstance(user);

       // 系统运行,用户开始登录退出
       userProxy.login("小明", "111");
       userProxy.login("小红", "111");
       userProxy.login("小刚", "111");
       userProxy.logout("小明");
       userProxy.logout("小刚");
   }
}

输出结果:

小明 登录成功.
当前在线人数:1 人.
小红 登录成功.
当前在线人数:2 人.
小刚 登录成功.
当前在线人数:3 人.
小明 成功退出.
当前在线人数:2 人.
小刚 成功退出.
当前在线人数:1 人.

动态代理的原理是利用Java反射在运行阶段动态生成任意类型的动态代理类,这不仅简化了程序员的编程工作,也提高了系统的可扩展性,不管我们的业务接口和委托类如何变,代理类都可以不变化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值