反射的作用

 
开门见山地说说反射的作用
 
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 =; i<methods.length; i++) {
System.out.println(methods[i].getName());
}
Field [] fields = c.getDeclaredFields();   // 获取成员变量对象列表
System.out.println("遍历MyClass类里的所有成员变量的名称:");
for(int i =; i<fields.length; i++) {
System.out.println(fields[i].getName());
}
 
Constructor [] constructors = c.getConstructors();  // 获取构造函数对象列表
System.out.println("遍历MyClass类里的所有构造函数的名称:");
for(int i =; i<constructors.length; i++) {
System.out.println(constructors[i].getName());
}
}
    public static void main(String [] args) {
    MyClass myClass = new MyClass(); // 创建一个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对应的方法不包括从父类中继承的那一部分
 
例如上面通过打印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();
    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();
  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 = ;
  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(, "彭湖湾");
    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) 放开权限
 
最后
反射的API其实还有一堆...但是抱歉的是没办法一一给大家介绍,只能当作个“引子”给大家尝个鲜,另外,本人java小白,有诸多不当之处,还望大家指出,谢谢大家。
 
【完】
 

参考资料:
《java核心技术 卷1》—— Cay S. Horstmann, Gary Cornell

 

【java】java反射初探 ——“当类也照起镜子”的更多相关文章

  1. 【java】java反射初探 ——“当类也学会照镜子”

    反射的作用   开门见山地说说反射的作用   1.为我们提供了全面的分析类信息的能力 2.动态加载类   我理解的“反射”的意义 (仅个人理解哈)   我理解的java反射机制就是: 提供一套完善而强 ...

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

    反射的作用 开门见山地说说反射的作用   1.为我们提供了全面的分析类信息的能力 2.动态加载类   我理解的“反射”的意义 (仅个人理解哈)   我理解的java反射机制就是: 提供一套完善而强大的 ...

  3. java通过反射取得一个类的完整结构

    首先我们在person包中新建一个Person.java: package person; import sex.Sex; public class Person{ private String na ...

  4. java利用反射打印出类的结构

    1 输入一个类名:java.lang.String将打印出String类定义的结构,例如: public final class java.lang.String { public java.lang ...

  5. Java通过反射机制修改类中的私有属性的值

    首先创建一个类包含一个私有属性: class PrivateField{ private String username = "Jason"; } 通过反射机制修改username ...

  6. Java关于反射

    反射的概念:JAVA反射机制是在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性:这种动态获取信息以及动态调用对象方法的功能称为java ...

  7. 七分钟理解 Java 的反射 API

    像java一样,一种具有反射功能的语言.允许开发人员在运行时检查类型.方法.字段.注解等,并在程序运行时决定是否使用. 为此,Java的反射API提供类,类,字段,构造函数,方法,注释和其他. 使用它 ...

  8. java 深入技术七(类的结构和反射)

    1.java反射(reflect) java 类的结构 java反射机制就是把java类按结构分解,每一部分对应特定的反射类 java反射机制允许运行时加载,探知和使用在编译期间完全未知的classe ...

  9. java中的反射机制,以及如何通过反射获取一个类的构造方法 ,成员变量,方法,详细。。

    首先先说一下类的加载,流程.只有明确了类这个对象的存在才可以更好的理解反射的原因,以及反射的机制. 一.  类的加载 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三 ...

随机推荐

  1. 【Linux】Apache Httpd 服务管理

    基本的操作方法: 本文假设你的apahce安装目录为/usr/local/apache2,这些方法适合任何情况 apahce启动命令: 推荐 [user@master1 ~]$  /usr/local ...

  2. IDE UltraEdit 图文激活+安装教程

    IDE UltraEdit 安装+激活图文.. ---------------- ---------------- ---------------- ---------------- -------- ...

  3. NTP原理及配置使用

    一.NTP简介 1.NTP简介   NTP(Network Time Protocol,网络时间协议)是用来使网络中的各个计算机时间同步的一种协议.它的用途是把计算机的时钟同步到世界协调时UTC,其精 ...

  4. 1_NAT模式和桥接模式下的网络配置

    相信当你看到这篇文章时,你一定对linux的桥接模式和NAT模式有所了解,所以你应该是能看懂这篇文件的,希望对你有所帮助,figthing    ! 一.桥接模式:虚拟机和主机是连在同一个路由下的. ...

  5. 使用VMware Workstation安装win7镜像文件时遇见的错误

    最近打算使用虚拟机安装个系统玩玩,就去网站上找了教程安装下win7系统.但是在安装过程中遇见两个问题,现在把遇见的问题总结记录一下,以及解决方法罗列一下. 我当时使用的是VMware 12版的,系统是 ...

  6. python进阶学习(三)

    本节通过SQLite了解数据库操作 ------------------------- 数据库支持 使用简单的纯文本只能实现有退限的功能,所需要引入数据库,完成更强大的功能,本节使用的简单数据库SQL ...

  7. 最常见的三个排序(冒泡、直接插入、快速)的JS实现

    //冒泡排序function bubble(arr){ for(var i=0;i<arr.length;i++){ for(var j=0;j<arr.length-i;j++){ if ...

  8. HTTP协议是无状态协议,怎么理解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp24 HTTP协议是无状态协议,怎么理解?  2010-02-23 09:4 ...

  9. Liunx-常用命令的总结(5)

    cd     ../dir        上一节目录下dir目录 cd -                    返回上次目录 ifconfig            查看IP地址 sudo   if ...

  10. FFmpeg 常用命令收集

    FFmpeg 常用命令 合并视频 ffmpeg -i "KTDS-820A_FHD.mp4" -c copy -bsf:v h264_mp4toannexb -f mpegts i ...