Java反射初探 ——“当类也学会照镜子”

反射的作用

开门见山地说说反射的作用

1.为我们提供了全面的分析类信息的能力

2.动态加载类

我理解的“反射”的意义

(仅个人理解哈)

我理解的java反射机制就是: 提供一套完善而强大的API“反射“类的结构。

打个比方,反射机制就像是一面镜子,而类就像是一个在照着镜子的人。

镜子(反射机制)照出(反射)了人的全貌(类的全方位的信息,例如方法,成员变量和构造器等的相关信息)

为什么要照镜子? 因为不照镜子看不清楚自己的全貌,“镜子”就是为了解决这个问题出现的(为我们提供全面分析类的能力)

好吧,我知道这听起来还是很模糊,让我们一步一步来:

类也是对象

在java里有一句话:万物皆对象, 即使是int等基本类型,虽然本质上不是对象,但行为却也和对象密切相关(基本包装类型和自动装箱)

所以有一个可能完全打破我们常规思维的论断是: 类也是对象

“类”对象和“类”类型

好吧,其实说“ 类也是对象”并不太好,而应该说,java中每个类都有一个与之对应的“类”对象(Class对象),这个“类”对象由jvm生成,并保存了对应类的相关信息。例如,假设我们的java文件涉及三个类:a类,b类和c类,那么编译的时候就会对应生成a类的“类”对象,a类的“类”对象,a类的“类”对象,分别用于保存和a,b,c类对应的信息

我们的思维是这样的: 一个对象必然有一个与之对应的类,因为只有类才能实例化对象啊

那么,“类对象”的“上面”,应该还有一个类才对!这个“类之上的类”,就是java.lang.Class,它是所有“类”对象的类(这样说可能听起来很拗口)

我们这样声明一个“类”对象,假设这个类对象(Class对象)是a——

Class a

我们称a属于“类”类型(Class类型)

所以我们可以其实可以将java中的对象分为两种:

1. 实例对象

2. Class对象

所以我们今天要讲的第一个内容是:  有别于平时使用的实例对象的——Class对象

取得Class对象的三种方式

我们假设有这么一个类叫MyClass:

public class MyClass {  }

那么取得该类对应Class对象的方法有三种:

一. 通过“类名.class”的方式取得

Class classInstance= MyClass.class;

二. 通过类创建的实例对象的getClass方法取得

MyClass myClass = new MyClass();
Class classInstance = myClass.getClass();

三.通过Class类的静态方法forName方法取得(参数是带包名的完整的类名)

Class classInstance = Class.forName("mypackage.MyClass");

【注意】

1.运行forName时候可能会因为找不到包名而抛出已检查异常ClassNotFoundException,所以我们需要将其包裹在try-catch语句中:

try {
  Class classInstance = Class.forName("mypackage.MyClass");
} catch (ClassNotFoundException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

2.上面三种方法取得的对象都是相同的,所以效果上等价

利用反射API全面分析类的信息——方法,成员变量,构造器

我们上面提到,反射的一大作用是用于分析类的结构,或者说用于分析和这个类有关的所有信息。而这些信息就是类的基本的组成: 方法,成员变量和构造器

事实上,和我们上面所介绍的Class类和Class对象相似的是,一个类中的方法,成员变量和构造器也分别对应着一个对象

1.每个方法都对应有一个保存和该方法有关信息的Method对象, 这个对象所属的类是java.lang.reflect.Method;

2.每个成员变量都对应有一个保存和该变量有关信息的Field对象,这个对象所属的类是 java.lang.reflect.Field

3. 每个构造器都对应有一个保存和该构造器有关信息的Constructor对象,这个对象所属的类是java.lang.reflect.Constructor

方法,成员变量和构造器是附属于某一个类的,正因如此,我们应该先取得某一个类对应的Class对象,其次才考虑如何取得 Method/Field/Constructor对象

我们可以通过一系列的方法,从一个类的Class对象中取得对应的Method对象,Field对象和Constructor对象

假设c是一个类的Class对象:

通过 c.getDeclaredMethods()可取得这个类中所有声明方法对应的Method对象组成的数组

通过 c.getDeclaredFields()可取得这个类中所有声明的成员变量对应的Field对象组成的数组

通过 c.getConstructors(); 可取得这个类中所有构造函数所对应的Constructor对象所组成的数组

在下面的示例中,我们将遍历某一个类中方法,成员变量和构造器的名称:

MyClass.java:

public class MyClass {
  private int value; //成员变量
  public MyClass (int value) { this.value = value;   } //构造函数
  public int getValue() {  return value; } //方法1
  public void setValue(int value) {  this.value = value;  } //方法2
}

Test.java:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Test {
  public static void printClassMessage (Object obj) {
    Class c = obj.getClass(); // 获取obj所属类的Class对象
    Method [] methods = c.getDeclaredMethods(); // 获取方法对象列表    System.out.println("遍历MyClass类里的所有方法的名称:");
    for(int i =0; i<methods.length; i++) {
      System.out.println(methods[i].getName());
    }
    Field [] fields = c.getDeclaredFields();   // 获取成员变量对象列表
    System.out.println("遍历MyClass类里的所有成员变量的名称:");
    for(int i =0; i<fields.length; i++) {
      System.out.println(fields[i].getName());
    }
 
    Constructor [] constructors = c.getConstructors();  // 获取构造函数对象列表
    System.out.println("遍历MyClass类里的所有构造函数的名称:");
    for(int i =0; i<constructors.length; i++) {
      System.out.println(constructors[i].getName());
    }
  }
    

  public static void main(String [] args) {
    MyClass myClass = new MyClass(1); // 创建一个MyClass对象
    printClassMessage(myClass);  // 打印这个对象所属类的相关信息
  }
}

运行结果:

遍历MyClass类里的所有方法的名称:
getValue
setValue
遍历MyClass类里的所有成员变量的名称:
value
遍历MyClass类里的所有构造函数的名称:
mypackage.MyClass

上面的例子仅仅是作为一个展示,Method/Field/Constructor对象的API当然不仅限于getName这样获取名称的简单操作,所以接下来我将分别介绍更具体的反射API

利用反射API分析类中方法信息

getMethods和getDeclaredMethods方法

getMethods和getDeclaredMethods的区别在于:

getMethods取得的method对应的方法包括从父类中继承的那一部分,而

getDeclaredMethods取得的method对应的方法不包括从父类中继承的那一部分

public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。
public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。

 

例如上面通过打印getDeclaredMethods打印的MyClass的方法信息:

getValue
setValue

让我们看看通过getMethods打印又会取得什么结果:

Test.java:

import java.lang.reflect.Method;
 
public class Test {
  public static void printMethodsMessage (Object obj) {
    Class c = obj.getClass();
    Method [] methods = c.getMethods();
    for (Method method : methods) {
      System.out.println(method.getName());
    }
  }
      public static void main(String [] args) {
    MyClass myClass = new MyClass(1);
    printMethodsMessage(myClass);
  }
}

运行结果:

getValue
setValue
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

让我们思考一下: 一个方法有哪些“信息”值得(或需要)我们去分析呢?

主要是两部分:

1. 返回值

2. 方法参数

你可能猜的出来, Method对象已经提供了一套API去获取这些信息了,让我们来看看:

通过method.getReturnType()获取方法返回值对应的Class对象

import java.lang.reflect.Method;
 
public class Test {
  public static void printMethodsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    Method [] methods = c.getDeclaredMethods(); // 取得obj所属类中方法对应的Method对象组成的数组
    for (Method method : methods) { // 遍历Method对象
      String name = method.getName();   // 取得方法名
      Class returnClass = method.getReturnType(); // 获取方法返回值对应的Class对象
      String returnName = returnClass.getName();  //获取返回值所属类的类名——也即返回值类型
      System.out.println(name + "方法的返回值类型是" + returnName);
    }
  }
  public static void main(String [] args) {
    MyClass myClass = new MyClass(1);
    printMethodsMessage(myClass);
  }}

运行结果:

getValue方法的返回值类型是int
setValue方法的返回值类型是void

通过method.getReturnType(),我们取得了该方法的返回值对应的Class对象(哈哈,绕了一圈后还是回到Class对象上了)

然后通过Class对象调用getName方法就取得了返回值所属的类的名称,也即返回值类型

通过method.getParameterTypes()获取方法各参数的Class对象组成的数组

MyClass.java:

public class MyClass {
  public void method1 (int a, long b) {   };
  public void method2 (float a, double b) {   };
  public void method3 (String str) {   };
}

Test.java:

public class Test {
  public static void printMethodsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    Method [] methods = c.getDeclaredMethods(); // 取得obj所属类中方法对应的Method对象组成的数组
    for (Method method : methods) { // 遍历Method对象
      String methodName = method.getName();   // 取得方法名
      String paramsStr = "";  // 用于存放某个方法参数类型列表的字符串
      Class [] paramsClasses = method.getParameterTypes();
      for (Class pc: paramsClasses) {
        String paramStr = pc.getName(); // 获取当前参数类型
        paramsStr+=paramStr + "  ";
      }
      System.out.println(methodName+ "方法的所有参数的类型列表:" + paramsStr);
    }
  }
  public static void main(String [] args) {
    MyClass myClass = new MyClass();
    printMethodsMessage(myClass);
  }
}

运行结果:

method2方法的参数类型列表:float  double 
method1方法的参数类型列表:int  long 
method3方法的参数类型列表:java.lang.String 

利用反射API分析类中成员变量信息

获取成员变量类型对应的的Class对象

读取成员变量的值

MyClass.java:

public class MyClass {
  private int number = 123;
  private String name ="彭湖湾";
}

Test.java:

import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Test {
  public static void printFieldsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    try {
      Field field = c.getDeclaredField("name");  // 取得名称为name的field对象
      field.setAccessible(true); // 这一步很重要!!!设置为true才能访问私有成员变量name的值!
      String nameValue = (String) field.get(obj); // 获取obj中name成员变量的值
      System.out.println("MyClass类中name成员变量的值为:" + nameValue);  // 输出
    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
       // TODO Auto-generated catch block
        e.printStackTrace();
    }
  }
      public static void main(String [] args) {
    MyClass myClass = new MyClass();
    printFieldsMessage(myClass);
  }}

运行结果:

MyClass类中name成员变量的值为:彭湖湾

通过getType方法读取成员变量类型的Class对象

这里只展现比较关键的两段代码:

Field field = class1.getDeclaredField(number");
System.out.print(field.getType().getName());

运行结果:

int

【注意】:因为java权限的原因,直接读取私有成员变量的值是非法的(加了field.setAccessible(true)后就可以了),但仍可以直接读取私有成员变量的类型

利用反射API分析类中构造器信息

分析构造函数的时候,其实思路大体上和分析方法时候一致,关键在于获取参数所属类的Class对象

区别在于:

1. 获取该类声明的构造器用的是getDeclaredConstructors方法而不是getDeclaredMethods方法

2. 构造函数没有返回值,所以不需要分析返回值(我似乎说了废话....)

废话不多说了,看下面的例子:

MyClass.java:

public class MyClass {
  public MyClass(int a, String str){}
}

Test.java:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
 
public class Test {
  public static void printContructorsMessage (Object obj) {
    Class c = obj.getClass();  // 取得obj所属类对应的Class对象
    Constructor [] constructors = c.getDeclaredConstructors();
    for (Constructor constructor : constructors) {
      Class [] paramsClasses =  constructor.getParameterTypes();
      String paramsStr = "";
      for (Class pc : paramsClasses) {
        String paramStr = pc.getName();
        paramsStr+=paramStr + "  ";
      }
      System.out.println("构造函数的所有参数的类型列表:" + paramsStr);
    }
  }
  public static void main(String [] args) {
    MyClass myClass = new MyClass(1, "彭湖湾");
    printContructorsMessage(myClass);
  }}

运行结果:

构造函数的所有参数的类型列表:int  java.lang.String 

利用反射动态加载类,并用该类创建实例对象

动态加载类

我们用普通的方式使用一个类的时候,类是静态加载的

而使用Class.forName("XXX")这种方式,则属于动态加载一个类

静态加载的类在编译的时候就能确定该类是否存在,但动态加载一个类的时候却无法在编译阶段确定是否存在该类,而是在运行时候才能够确定是否有这个类,所以要捕捉可能发生的异常

我们可以从eclipse的使用上有个相对直观的了解:

eclipse在保存的时候是可以自动编译的,SO

例如我们如果直接使用一个本就不存在的类NotExistClass的时候

保存后,在编译阶段就能够发现:“诶? 好像没有这个类哦!”

报的错误是:

NotExistClass cannot be resolved to a type

但是如果我们用Class.forName("XXX")动态加载一个类呢?

我们发现,保存后,在编译阶段已经不能发现这个错误了,对应的是要捕捉可能发生的异常

用该动态加载的类创建实例对象

Class对象有一个newInstance方法,我们可以用它来创建实例对象

Class classInstance = Class.forName("mypackage.MyClass");
MyClass myClass = (MyClass) classInstance.newInstance();

不过要注意的是,因为newInstance返回的是一个Object,所以要做强制类型转换,将其变成MyClass类型

捕捉可能产生的异常后:

public class Test {
   public static void main(String [] args) {
     try {
       Class classInstance = Class.forName("mypackage.MyClass");
       MyClass myClass = (MyClass) classInstance.newInstance();
     } catch (ClassNotFoundException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     } catch (InstantiationException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     } catch (IllegalAccessException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
     }
   }
}

全文的总结

如果您已经看到这里了,那么请允许我在用多那么一点罗嗦的文字做个总结:

总结

1.反射为我们提供了全面的分析类信息的能力,例如类的方法,成员变量和构造器等的相关信息,反射能够让我们很方便的获取这些信息, 而实现这个获取过程的关键是取得类的Class对象,然后根据Class对象取得相应的Method对象,Field对象和Constructor对象,再分别根据各自的API取得信息。

2.反射还为我们提供动态加载类的能力

一些细节

1. API中getDeclaredXXX和getXXX的区别在于前者只获取本类声明的XXX(如成员变量或方法),而不获取超类中继承的XXX, 后者相反

2. API中, getXXXs(注意后面的s)返回的是一个数组, 而对应的 getXXX("键")按键获取一个值(这个时候因为可能报已检查异常所以要用try-catch语句包裹)

3. 私有成员变量是不能直接获取到值的!因为java本身的保护机制,允许你取得私有成员变量的类型,但是不允许直接获取值,所以要对对应的field对象调用field.setAccessible(true) 放开权限

参考:利用反射实现JavaBean的自动赋值
import java.lang.reflect.Method;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import com.sns.exception.ApplicationException;
public final class ParameterUtil {
public static void setFormBean(HttpServletRequest request, Object bean) {
Class c = bean.getClass();
Method[] ms = c.getMethods();
for(int i=0; i<ms.length; i++) {
    String name = ms.getName();
    if(name.startsWith("set")) {
     Class[] cc = ms.getParameterTypes();
     if(cc.length==1) {
      String type = cc[0].getName(); // parameter type
      try {
        // get property name:
        String prop = Character.toLowerCase(name.charAt(3)) + name.substring(4);
        // get parameter value:
        String param = getString(request, prop);
        if(param!=null && !param.equals("")) {
         //ms.setAccessible(true);
         if(type.equals("java.lang.String")) {
          ms.invoke(bean, new Object[] {param});
         }
         else if(type.equals("int") || type.equals("java.lang.Integer")) {
          ms.invoke(bean, new Object[] {new Integer(param)});
         }
         else if(type.equals("long") || type.equals("java.lang.Long")) {
          ms.invoke(bean, new Object[] {new Long(param)});
         }
         else if(type.equals("boolean") || type.equals("java.lang.Boolean")) {
          ms.invoke(bean, new Object[] { Boolean.valueOf(param) });
         }
         else if(type.equals("java.util.Date")) {
          Date date = DateUtil.parseDateTime(param);
          if(date!=null)
            ms.invoke(bean, new Object[] {date});
         }
        }
      }
      catch(Exception e) {
        e.printStackTrace();
      }
     }
    }
}
}
}
每当发现JavaBean中的setXxx()方法时,便自动寻找表单的对应字段xxx,如果找到,就利用反射调用此方法,将对应的字段值赋给JavaBean。
由于表单传递的变量名和值全部是字符串,因此需要做某些转换。目前,该程序能处理的数据类型包括:boolean,Boolean,int,Integer,long,Long,Date,不被支持的数据类型被自动忽略。你也可以很方便地添加新的类型。
请 注意,只有public的set方法能够被调用。如果你希望private或protected的set方法也能被调用,请将红色标识的 getMethods()改为getDeclaredMethods(),以便获得包括private和protected在内的所有方法,并将 ms.setAccessible(true);的注释去掉,以便能正确调用private和protected方法。
反射是Java语言非常强大的功能,但是由于反射会破坏对象封装,并且反射调用的速度较慢,因此,只能在必要的工具类中使用。

参考资料:

《java核心技术 卷1》—— Cay S. Horstmann, Gary Cornell

时间: 2024-10-05 02:56:32

Java反射初探 ——“当类也学会照镜子”的相关文章

【java】java反射初探 ——“当类也照起镜子”

反射的作用 开门见山地说说反射的作用 1.为我们提供了全面的分析类信息的能力 2.动态加载类 我理解的“反射”的意义 (仅个人理解哈) 我理解的java反射机制就是: 提供一套完善而强大的API“反射“类的结构. 打个比方,反射机制就像是一面镜子,而类就像是一个在照着镜子的人. 镜子(反射机制)照出(反射)了人的全貌(类的全方位的信息,例如方法,成员变量和构造器等的相关信息) 为什么要照镜子? 因为不照镜子看不清楚自己的全貌,“镜子”就是为了解决这个问题出现的(为我们提供全面分析类的能力) 好吧

JAVA反射之CLass类的练习

1 package zhang; 2 /** 3 * JAVA反射之CLass类的练习 4 * 5 * 在面向对象的语言里,万事万物皆对象,那么类是谁的对象呢? 6 * 类的类型是CLass 7 * 8 * */ 9 class Test{ 10 void print(){ 11 System.out.println("Hello world!"); 12 } 13 }//Test是一个类. 14 public class ClassDemo { 15 public static vo

Java反射机制Class类的用法详解

本文转自http://blog.sina.com.cn/s/blog_6c9536b90100rqh4.html,感谢作者 java的Class类是java反射机制的基础,通过Class类我们可以获得关于一个类的相关信息,下面我们来了解一下有关java中Class类的相关知识! java.lang.Class是一个比较特殊的类,它用于封装被装入到JVM中的类(包括类和接口)的信息. 当一个类或接口被装入的JVM时便会产生一个与之关联的java.lang.Class对象,可以通过这个Class对象

java反射对实体类取值和赋值

项目中需要过滤前面表单页面中传过来的实体类的中的String类型变量的前后空格过滤,由于前几天看过一个其他技术博客的的java反射讲解,非常受益.于是,哈哈哈 public static <T> void modelTrim(T model){ Class<T> clazz = (Class<T>) model.getClass(); //获取所有的bean中所有的成员变量 Field[] fields = clazz.getDeclaredFields(); for(

java反射之遍历类中所有内部类及属性方法

package com.zr.entity; /** * 仅用于测试类,从实际项目中抽出来的 * @author zr * */ public class Constant { /** * 参数校验枚举类 * @author zr * */ public static enum ResultObjectType { //成功 SUCC(0), //失败,异常 ERROR(1), //参数错误 PARAMERROR(2), //数据为空 NODATA(9); private Integer val

java反射初探

java反射 反射是java的重要特性之一,java.lang.reflect 是jdk支持反射的重要包,我下面可能会对构造器Constructor,属性Filed,方法Method会用到.反射其实很常见,在java源码.spring就有很多运用,Spring AOP就应用到了反射.设计模式中代理模式就可以用java反射实现. 反射的例子 创建一个Bean package com.java.javabase.reflect; import lombok.AllArgsConstructor; i

JAVA 反射 动态获取类,并调用方法

Class<?> clazz = Class.forName("cn.walle.sysGrid.MyMethod"); Object show = (Object) clazz.newInstance(); getMethod("show",show,null);

java 反射 详解

本文来自:blog.csdn.net/ljphhj JAVA反射机制:   通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作他们. 理论的东东太多也没用,下面我们看看实践 Demo - Demo: package cn.lee.demo; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import

Java反射、Java代理模式

简单来说,JAVA反射是利用类名称来获得这个类的所有信息:代理,是有个传递过程,将自己的类对象传给其他类,让其他类来代理自己的类对象然后做事: 比喻:订外卖,送餐小哥手里的外卖就是我的对象,是我让他送的.静态代理就是我告诉送餐小哥外卖是我的(假设外卖小哥认识我),然后他直接就奔着我来了:动态代理就是我不告诉送餐小哥我是谁,他也不认识我,我只告诉他我的电话或者地址,他自己想办法找到我:注意,他有什么办法找到我呢?就是反射,他利用我的电话和地址,就知道我的一切了(夸张的说)就可以找到我了. 下面通过