Java基础知识_反射

来源:互联网 发布:云计算就业工资多少 编辑:程序博客网 时间:2024/06/05 07:25

反射的这一段内容我整整看了三遍才算看明白,并不是因为内容多么的深奥,而是一直不明白这东西是用来干什么。不关注“是什么”和“为什么”而只关注“怎么用”是学习时最痛苦的事情。因为你会发现,明明每一步你都能看懂,但连在一起就不知道是什么意思,或者写着上一步却不知道下一步该怎么写,这就是没有概览全局的弊端。


(以下内容一部分是从网上搜集的资料)


一 、什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。其中LEAD/LEAD++ 、OpenC++ 、MetaXa和OpenJava等就是基于反射机制的语言。最近,反射机制也被应用到了视窗系统、操作系统和文件系统中。
反射本身并不是一个新概念,尽管计算机科学赋予了反射概念新的含义。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。


二、什么是Java中的类反射:

Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。Java 的这一能力在实际应用中用得不是很多(主要用于开发框架,目前在Android中也会运用到此技术),但是在其它的程序设计语言中根本就不存在这一特性。例如,Pascal、C 或者 C++ 中就没有办法在程序中获得函数定义相关的信息。

Reflection 是 Java 被视为动态(或准动态)语言的关键,允许程序于执行期 Reflection APIs 取得任何已知名称之 class 的內部信息,包括 package、type parameters、superclass、implemented interfaces、inner classes, outer class, fields、constructors、methods、modifiers,並可于执行期生成instances、变更 fields 內容或唤起 methods。


三、Java类反射中所必须的类:

Java的类反射所需要的类并不多,它们分别是:Field、Constructor、Method、Class、Object,下面我将对这些类做一个简单的说明。
Field类:提供有关类或接口的属性的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)属性或实例属性,简单的理解可以把它看成一个封装反射类的属性的类。
Constructor类:提供关于类的单个构造方法的信息以及对它的访问权限。这个类和Field类不同,Field类封装了反射类的属性,而Constructor类则封装了反射类的构造方法。
Method类:提供关于类或接口上单独某个方法的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。 这个类不难理解,它是用来封装反射类方法的一个类。
Class类:类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。

Object类:每个类都使用 Object 作为超类。所有对象(包括数组)都实现这个类的方法。


四、Java的反射类怎么用

说到如何运用反射,就得分别谈谈每一种反射类的用途。

(一)构造函数的反射(Constructor类)

Constructor类代表某个类的一个构造方法。
package cn.icecino.d1;import java.lang.reflect.Constructor;public class ConstructorDemo {public static void main(String[] args) throws Exception{// TODO 自动生成的方法存根//得到某个类所有的构造方法Constructor<?>[] constructors = Class.forName("java.lang.String").getConstructors();//取得指定类的构造方法Class classType = Class.forName("java.lang.String");        Constructor constructor = classType.getDeclaredConstructor(StringBuffer.class);                /*创建实例对象*/        //通常方式:        String str = new String(new StringBuffer("abc"));        //反射方式        String str1 = (String)constructor.newInstance(new StringBuffer("abc"));                //Class.NewInstance()方法        String str2 = (String)Class.forName("java.lang.String").newInstance();        /*该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。该方法的内部用到了缓存机制来保存默认构造方法的实例对象*/                //获得构造方法并创建实例对象        Constructor constructor1 = String.class.getConstructor(StringBuffer.class);        /*getConstructor()中用到是不定长度参数,1.4版本之前,则是通过传入数组来调节参数类型和数组不确定的情况*/        String str3 = (String)constructor1.newInstance(new StringBuffer("aaa"));}}

(二)字段的反射(Field类)

在一个类中,他的属性可能是公有的,也可能是私有的,这时我们可以根据不同的需要来选择反射的方法。getFields()用于反射类中的公有成员,getDeclaredField()用于反射类中的所有成员。
package cn.icecino.d1;import java.lang.reflect.Field;public class FieldDemo {public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {// TODO 自动生成的方法存根/*首先,通过getFields()方法,我们将获得类中的public属性*/Field[] fields = Demo.class.getFields();for (Field f : fields){System.out.println(f.toString());}/* 输出: *  public java.lang.String cn.itcast.day1.Demo.name *  public int cn.itcast.day1.Demo.number *  都是public的属性 *//*如果我们不光想要反射出公有的属性,还想获得被私有的属性,则需要使用getDeclaredFields()方法*/Field[] fields2 = Demo.class.getDeclaredFields();for (Field f : fields2){f.setAccessible(true);//将可访问设置为true/*这种强行输出类中私有属性的反射也被称为暴力反射*/System.out.println(f.toString());}/* 输出: *  public java.lang.String cn.itcast.day1.Demo.name *  public int cn.itcast.day1.Demo.number *  private int cn.itcast.day1.Demo.age *  将Demo类中的私有方法也输出出来 *//*也可以反射出指定的属性*/Demo d = new Demo("a",123,23);Field field = Demo.class.getDeclaredField("age");//由于反射的是一个私有属性,所以需要使用setAccessible()方法field.setAccessible(true);//获取制定对象中的属性信息Integer i = (Integer)field.get(d);System.out.println(i);/* * 输出:23 */}}class Demo{public String name;public int number;private int age=23;public Demo(String name, int number, int age) {super();this.name = name;this.number = number;this.age = age;}public Demo() {// TODO 自动生成的构造函数存根}}

练习:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"

  1. import java.lang.reflect.Field;  
  2. public class Reflectest {  
  3.     public static void main(String[] args) throws Exception {  
  4.         ReflectPoint pt1=new ReflectPoint();  
  5.         changeStringValue(pt1);  
  6.         System.out.println(pt1);  
  7.     }  
  8.   
  9.     private static void changeStringValue(Object obj) throws Exception{  
  10.         Field[] fields=obj.getClass().getFields();//获取所有的成员变量  
  11.         //遍历成员变量  
  12.         for(Field field:fields){  
  13.   //比较字节码用==  
  14.             if(field.getType()==String.class){  
  15.             String oldValue=(String)field.get(obj);//获取obj的String类型的成员变量  
  16.             String newValue=oldValue.replace('b''a');//将b换成a  
  17.             field.set(obj, newValue);//将此 Field表示的字段设置为指定的新值  
  18.         }  
  19.         }  
  20.     }  
  21. }  
  22. class ReflectPoint {  
  23.     public String str1="ball";  
  24.     public String str2="basketball";  
  25.     public String str3="itcast";  
  26.     //重写toString方法  
  27.     public String toString(){  
  28.         return str1+"  "+str2+"  "+str3+"  ";  
  29.     }  
  30. }  


(三)成员方法的反射(Metho类)

Method类代表某个类(接口)或对象中的一个成员方法,这个方法可以是抽象的。JDK API中对Method的描述是这样的:
“A Method provides information about, and access to, a single method on a class or interface. The reflected method may be a class method or an instance method (including an abstract method).”

package cn.icecino.d1;import java.lang.reflect.Method;public class MethoDemo {public static void main(String[] args) throws Exception {String str = "like java";//获取String的charAt()方法Method McharAt = String.class.getMethod("charAt", int.class);/*调用该方法*///通常方式System.out.println(str.charAt(2));//输出:k//反射方式System.out.println(McharAt.invoke(str, 3));//输出:e/*如果传给invoke方法的第一个参数不是对象而是null,说明调用的是一个静态方法*/}}

在这里,有个特殊的问题需要考虑,那就是在反射方法时,有时方法的参数会是数组类型。在jdk1.4版本及以前是没有不定长度参数的,所以涉及到无法确定参数个数的时候会使用数组,然后由jvm将数组拆包,数组里的每个元素都作为一个参数。而在jdk1.5的新特性中添加了不定长度参数,那么在我们通过invoke传入参数的时候,是按照1.4的规则编译还是1.5的规则编译呢?——显然,本着向下兼容的特性,肯定是按照1.4版本的规则编译,但这样才反射时就会出现一些问题。


package cn.icecino.d1;import java.lang.reflect.Method;public class MethoDemo {public static void main(String[] args) throws Exception {Sring className = args[0];Method method = Class.forName(className).getMethod("main", String[].class);method.invoke(null, (Object)new String[]{"aaa","bbb","ccc"});/* * 如果按照一般写法,传递参数应该是这样: * method.invoke(null, new String[]{"aaa","bbb","ccc"}); * 但是由于jvm自动拆包,会将String数组当作三个参数传入,这个main方法中只接受一个String[]不符,编译器会报错,所以有两种解决方案。 * 其一:像上述程序中所写的那样,在前面加上强制类型转换,告诉编译器这是一个整体,不要拆包 * 其二:可以这样写——method.invoke(null, new Object[]{new String[]{"aaa","bbb","ccc"}}); *         定义一个Object类型数组,并将String[]整体作为一个元素放入数组中,编译器拆包后得到的便是一个String[]类型参数。 */}}class Test{public static void main(String[] args){for (String str : args){System.out.println(str);}}}


输出结果:
aaa
bbb
ccc


(四)数组的反射

<1>数组与Object类的关系及其反射类型

数组类型及其反射类型名称
 
让我们来看一个Demo:
package cn.icecino.d1;import java.lang.String;import java.lang.System;import java.util.Arrays;public class ReflectArrayDemo {public static void main(String[] args) {// TODO 自动生成的方法存根int[] a1 = new  int[]{1,2,3};int[] a2 = new  int[4];int[][] a3 = new int[3][4];String[] a4 = new String[]{"a","b","c"};System.out.println(a1.getClass() == a2.getClass());System.out.println(a1.getClass().getName());System.out.println(a1.getClass().getSuperclass().getName());System.out.println(a3.getClass().getSuperclass().getName());System.out.println(a4.getClass().getSuperclass().getName());/* * output: * true * [I * class java.lang.Object * */Object o1 = a1;Object o2 = a4;/*Object[] o3 = a1; 不能这样写,因为定义引用的时候申明了数组内装的是Object,但是int不是Object,只是一个基本数据类型*/Object[] o4 = a3;Object[] o5 = a4;System.out.println(a1);System.out.println(a4);System.out.println(Arrays.asList(a1));System.out.println(Arrays.asList(a4));/* * ourput: * [I@55e55f  打印出a1的字节码类型和hashcode值 * [Ljava.lang.String;@145c859   打印出a4的字节码类型和hashcode值 * [[I@55e55f] 利用数组工具类取int数组中的数值,失败 * [a, b, c] 利用数组工具类取String数组中的值,成功 *  */}}


为什么同样是用Arrays工具类取数组中的值,int数组无法取出,而String却成功了呢?

让我们先来看一下API中的方法说明:
首先是JDK1,7中的内容:
public static <T> List<T> asList(T... a)
这里asList中接收的是一个可变参数类型,但是我们知道,在1.4及其之前的版本是没有可变参数类型的,所以我们要向下对1.4版本兼容。
在1.4版本的API中,asList接收的是“Object[]  obj”所以当我们使用该方法接受String类型数组时,会用Object数组来调用该方法,这是可以成功的。但是当asList接收的是一个int类型数组时,Object数组中无法接受int元素,所以1.4的方法无法编译,便使用可变参数类型来进行编译,将整个int数组看成一个对象,得到的便是他的哈希码。


<2>数组的反射应用

数组的反射可以用java.lang.reflext.Array类(注意不是Arrays),该类提供的所有方法均为静态,我们可以通过它来判断判断对应数组的长度,然后遍历整个数组

public static void printObject(Object obj){Class clazz = obj.getClass();if (clazz.isArray()){int len = Array.getLength(obj);for (int i = 0; i < len; i++){System.out.println(Array.get(clazz, i));}}else{System.out.println(obj);}}

结束语:
Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象,无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。Java reflection 非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。Java 的这一特性非常强大,并且是其它一些常用语言,如 C、C++、Fortran 或者 Pascal 等都不具备的。

但反射有两个缺点。第一个是性能问题。用于字段和方法接入时反射要远慢于直接代码。性能问题的程度取决于程序中是如何使用反射的。如果它作为程序运行中相对很少涉及的部分,缓慢的性能将不会是一个问题。即使测试中最坏情况下的计时图显示的反射操作只耗用几微秒。仅反射在性能关键的应用的核心逻辑中使用时性能问题才变得至关重要。