xscn

博客园 首页 新随笔 联系 订阅 管理

 

一般而言,开发者社群说到动态语言,大致认同的一个定义是:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。

尽管在这样的定义与分类下Java不是动态语言,它却有着一个非常突出的动态相关机制:Reflection。这个字的意思是“反射、映象、倒影”,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

反射的基石-----Class类

Class是Reflection起源。针对任何想探勘的class,唯有先为它产生一个Class object,接下来才能经由后者唤起为数十多个的Reflection APIs。

Java中类用于描述一类事物的共性,该类事物有什么属性,没有什么属性,至于这个属性的值是什么,则是由这个类的实例对象来确定的,不同的实例对象有不同的属性值。Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,注意与小写class关键字的区别。Class类描述了哪些方面的信息呢?类的名字,类的访问属性,类所属于的包名,字段名称的列表、方法名称的列表,等等。
学习反射,首先就要明白Class这个类。写如下代码进行对比理解:

1 Person p1 = new Person("zhangsan");
2 Person p2 = new Person("lisi");
3 /*
4 Class x1 = Vector类在内存里的字节码
5 Class x2 = Date类在内存里的字节码*/
6 
7 Class x1 = Vector.class;
8 Class x2 = Date.class; 

Person类代表人,它的实例对象就是张三,李四这样一个个具体的人。

Java程序中的各个Java类属于同一类事物,描述这类事物的Java类名就是Class。

Class类的各个实例对象又分别对应什么呢?对应各个类在内存中的字节码(类模版。虚拟机只会产生一份字节码, 用这份字节码可以产生多个实例对象)。例如,Person类的字节码,ArrayList类的字节码,等等。

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?Class类型。

如何获取一个类的字节码?

  1. 类名.class,例如,System.class
  2. 对象.getClass(),例如,new Date().getClass()
  3. Class.forName("类名"),例如,Class.forName("java.util.Date")

8个基本数据类型也有对应的Class对象。void也有对应的Class对象,例如,Class v = void.class;所以共有9个预定义的Class对象。
isPrimitive(),判断是否是基本数据类型。基本类型获得字节码,例如,Class  i = int.class;注意,基本类型的字节码和包装类的字节码不一样,例如,int.class和Integer.class对应的字节码不一样。但是,Integer.TYPE表示包装类内部基本数据类型的字节码,所以int.class 和Integer.TYPE是一致的。
基本数据类型组成的数组,也有对应的字节码,是Class的实例对象,但不是基本类型,是数组类型,可以用isArray()判断是否是数组类型。

 1 public class ReflectTest {
 2 
 3     public static void main(String[] args) throws Exception {
 4         
 5         String str1 = "abc";
 6         Class cls1 = str1.getClass();
 7         Class cls2 = String.class;
 8         Class cls3 = Class.forName("java.lang.String");
 9         System.out.println(cls1 == cls2);//true
10         System.out.println(cls1 == cls3);//true
11         
12         System.out.println(cls1.isPrimitive());//false
13         System.out.println(int.class.isPrimitive());//true
14         System.out.println(int.class == Integer.class);//false
15         System.out.println(int.class == Integer.TYPE);//true
16         System.out.println(int[].class.isPrimitive());//false
17         System.out.println(int[].class.isArray());//true        
18      }
19 }

反射机制的应用

在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中:
Field 类:代表类的成员变量(或叫字段或属性)。
Method 类:代表类的方法。
Constructor 类:代表类的构造函数。
Array 类:提供了动态创建数组,以及访问数组的元素的静态方法。
Annotation 类:通过Annotation[] arr = Fileld.getAnnotations();获得字段的注释;

Constructor类代表某个类中的一个构造方法。通过调用Class中的方法获得Constructor对象。

Constructor<T> getConstructor(Class<?>... parameterTypes)
返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法。
Constructor<?>[] getConstructors()
返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。

得到某个类所有的构造方法:
例子:Constructor [] conall= Class.forName("java.lang.String").getConstructors();
得到某一个构造方法:
例子:Constructor con1= Class.forName(“java.lang.String”).getConstructor(StringBuffer.class); //此处StringBuffer.class是传入获得构造方法时要用到的类型
  
通过得到的构造方法创建实例对象:
String str = (String)con1.newInstance(new StringBuffer("abc"));//此处StringBuffer是调用获得的方法时要用到上面指定的相同类型的实例对象。

参考下面API使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。个别参数会自动解包,以匹配基本形参,必要时,基本参数和引用参数都要进行方法调用转换。

T newInstance(Object... initargs)
使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。


Class.newInstance()方法:
例子:String str= (String)Class.forName("java.lang.String").newInstance();
Class类的newInstance()方法,用于创建空参数的实例对象。底层仍然是调用Constructor类的newInstance()方法,使用缓冲机制保存实例对象,等到使用时提供出去,非常占用资源。

Field类代表某个类中的一个成员变量

Field getField(String name)
返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段。
Field[] getFields()
返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段。

 

Field getDeclaredField(String name)
返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。
Field[] getDeclaredFields()
返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。

 

Object get(Object obj)
返回指定对象上此 Field 表示的字段的值。

 

void set(Object obj, Object value)
将指定对象变量上此 Field 对象表示的字段设置为指定的新值。

 

void setAccessible(boolean flag)
将此对象的 accessible 标志设置为指示的布尔值。

 

AccessibleObject类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获取字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。


假如有一个类ReflectPoin。操作需求如下

1.通过反射获取Reflect类中的成员变量x和y的值

2.将Reflect类中所有字符串变量中内容中的b改成a

 1 public class Reflect {
 2     private int x;
 3     public int y;
 4     public String str1 = "ball";
 5     public String str2 = "basketball";
 6     public String str3 = "allstar";
 7     
 8     public Reflect(int x, int y) {
 9         super();
10         this.x = x;
11         this.y = y;
12     }    
13 
14     public String toString(){
15         return str1 + ":" + str2 + ":" + str3;
16     }    
17 }
 1 /*需求
 2 1.通过反射获取Reflect类中的成员变量x和y的值
 3 2.将Reflect类中所有字符串变量中内容中的b改成a
 4 */
 5 import java.lang.reflect.*;
 6 class  ReflectDemo
 7 {
 8     public static void main(String[] args) throws Exception
 9     {   
10         //通过构造函数对变量x和y传值
11         Reflect r1 = new Reflect(3,5);
12         //通过反射获取y的值
13         Field fieldY = r1.getClass().getField("y");
14         //fieldY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上的变量,要用它去取某个对象上对应的值。
15         System.out.println(fieldY.get(r1));
16         //通过反射获取y的值
17         Field fieldX = r1.getClass().getDeclaredField("x");
18         fieldX.setAccessible(true);//对于私有变量,需要“暴力反射”才能获得对应值
19         System.out.println(fieldX.get(r1));    
20         
21         
22         changeStringValue(r1);
23         System.out.println(r1);                
24     }   
25      public static void changeStringValue(Object obj) throws Exception {
26            Field[] fields = obj.getClass().getFields();
27            for(Field field : fields){//迭代所有返回的Field字段
28             //if(field.getType().equals(String.class)){比较字节码用==
29             if(field.getType() == String.class){
30                 //如果是String.class类型,通过字段获取变量值
31                 String oldValue = (String)field.get(obj);
32                 String newValue = oldValue.replace('b', 'a');
33                 field.set(obj, newValue);
34             }
35         }
36         
37     }
38 }
39 ---------- 运行 ----------
40 5
41 3
42 aall:aasketaall:allstar
43 
44 输出完成 (耗时 0 秒) - 正常终止

 

Method类代表某个类中的一个成员方法。

Class.getMethod方法用于得到一个方法,该方法要接受什么参数呢?显然要一个方法名,而一个同名的方法有多个重载形式,用什么方式可以区分清楚想得到重载方法系列中的哪个方法呢?根据参数的个数和类型。
例如,Class.getMethod(name,Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。再强调一遍参数类型用什么来表示啊?用Class对象!

 

Object invoke(Object obj, Object... args)
对带有指定参数的指定对象调用由Method 对象表示的底层方法
 1 import java.lang.reflect.*;
 2 class ReflectMethod 
 3 {
 4     public static void main(String[] args) throws Exception
 5     {
 6         String str1 = "abc";
 7         String str2 = "def";
 8         int  x =123;
 9         
10         Method m1 = Class.forName("java.lang.String").getMethod("concat", String.class);
11         Method m2 = Class.forName("java.lang.String").getMethod("charAt", int.class);
12         Method m3 = Class.forName("java.lang.String").getMethod("valueOf",int.class);
13         //格式(方法名,参数对象)
14         //参数类型用Class对象表示!
15 
16         System.out.println(m1.invoke(str1, str2)); 
17         System.out.println(m2.invoke(str2, 1)); 
18         //如果传递给Method对象的invoke()方法的第一个参数为null。说明该Method对象对应的是一个静态方法!
19         System.out.println(m3.invoke(null, x));             
20     }
21 }
22 ---------- 运行 ----------
23 abcdef
24 e
25 123456
26 
27 输出完成 (耗时 0 秒) - 正常终止

 
jdk1.4和jdk1.5的invoke方法的区别:
Jdk1.5:public Object invoke(Object obj,Object... args)
Jdk1.4:public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(“str2”, new Object[]{1})形式。

JDK1.5为了兼容JDK1.4,如果传入一个数组,会默认为Object数组,自动拆包,将数组中的元素作为参数。将上例的数组拆包成2个字符串对象。为了防止这种情况,处理方法有二种:

1、将数组变成一个Object数组对象,例如,new Object[]{(new String[]{"kk","qq"})}。因为数组也属于Object类的子类。

2、转换数组的类型,例如,(Object)new String[]{"kk","qq"}。

数组的反射

有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。
代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class。
基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

Arrays的asList()方法在处理基本类型数组(int[])和引用类型数组(String[])时的差异:

 1 import java.lang.reflect.*;
 2 import java.util.*;
 3 class ReflectMethod 
 4 {
 5     public static void main(String[] args) throws Exception
 6     {
 7         int [] a1 = new int[]{1,2,3};
 8         String [] a4 = new String[]{"a","b","c"};        
 9         
10         System.out.println(Arrays.asList(a1));
11         System.out.println(Arrays.asList(a4));    
12     }
13 }

可以将引用数据的数组变成集合,数组中的元素变成集合中的元素;会将整个基本数据的数组变成一个Object对象。因为JDK1.4中,asList()接受的是数组Object[];JDK1.5中,asList()接受的是可变参数T…a,T可以是基本型或引用型。例如,a4按照1.4处理,直接打印;a1按照1.5处理,打印出哈希值。

Array类提供了动态创建和静态访问数组元素的方法。

static Object newInstance(Class<?> componentType, int... dimensions)
创建一个具有指定的组件类型和维度的新数组。
static Object newInstance(Class<?> componentType, int length)
创建一个具有指定的组件类型和长度的新数组。
 1 import java.lang.reflect.*;
 2 public class ArrayTester1 {
 3 
 4     public static void main(String args[]) throws Exception {
 5 
 6         Class<?> classType = Class.forName("java.lang.String");
 7         // 创建一个长度为10的字符串数组
 8         Object array = Array.newInstance(classType, 10);
 9         // 把索引位置为5的元素设为"hehe"
10         Array.set(array, 5, "hehe");
11         // 获得索引位置为5的元素的值     
12         System.out.println((String)Array.get(array, 5));
13     }
14 }
 1 import java.lang.reflect.*;
 2 public class ArrayTester2 {
 3     public static void main(String args[]) throws Exception
 4         {
 5         
 6         int[] dims = new int[]{8, 9, 10};
 7 
 8         //创建一个具有指定的组件类型和维度的新数组。三维数组。
 9         Object array_3 = Array.newInstance(Integer.TYPE, dims);
10  
11         //获取三维数组的第3个数组组件array_2,是一个二维数组
12         Object array_2 = Array.get(array_3, 3);
13         //获取array_2的第5个数组组件array_1,是一个一维数组
14         Object array_1 = Array.get(array_2, 5);
15         //设置array_1的第7个元素的值
16         Array.set(array_1, 7, 9);
17 
18         //int array[][][] = (int[][][])array_3;
19         //System.out.println(array[3][5][7]);
20         System.out.println(((int[][][])array_3)[3][5][7]);
21     }
22 
23 }

用反射判断对应数组的长度,然后遍历整个数组

 1 import java.lang.reflect.*;
 2 public class ArrayReflectDemo {
 3 
 4     public static void main(String[] args) {
 5         String[] s1 = { "abc", "bcd" };
 6         int[] i = { 12, 34 };
 7         double[] d = { 12.34, 34.12 };
 8         ArrayReflectDemo[] af = {null,null};
 9         String[][] s2 = new String[][]{{"abc","bcd"},{"1bc","1cd"}};
10         
11         getValue(s1);    
12         getValue(s2);
13         getValue(i);
14         getValue(d);
15         getValue(af);
16 
17 }
18 public static void getValue(Object obj){
19         boolean b = obj.getClass().isArray();
20         if(b){
21             for(int i=0;i<Array.getLength(obj);i++){
22                 System.out.println(Array.get(obj,i));
23             }
24         }     
25     }
26 }
27 ---------- 运行 ----------
28 abc
29 bcd
30 [Ljava.lang.String;@bb0d0d
31 [Ljava.lang.String;@55e55f
32 12
33 34
34 12.34
35 34.12
36 null
37 null
38 
39 输出完成 (耗时 0 秒) - 正常终止

反射实现框架功能

先建框架,然后再写类。运行时,框架调用这些类。
因为在写才程序时无法知道要被调用的类名,所以,在程序中无法直接new 某个类的实例对象了,而要用反射方式来做。这是框架要解决的核心问题。反射是一种让框架能够根据 "以字符串形式存在的信息---即配置文件" 来调用对象的属性和函数的技术,是Java对C++最大的进步之一---让框架编程真正走向平民化。

创建一个File文件config.properties作为配置文件,设置className=类名,需要时可以通过更改配置文件,将框架改造成自己需要的样子。配置文件放在工程目录下。

 1 import java.io.*;
 2 import java.lang.reflect.*;
 3 import java.util.*;
 4 public class ReflectTest1 {
 5 
 6     public static void main(String[] args) {
 7 
 8         try {
 9             FileInputStream fis = new FileInputStream("config.properties");
10             Properties p = new Properties();
11             p.load(fis);
12             String cs = p.getProperty("className");//指向配置文件的key:className的value
13             /*此处配置文件设置为1或者2
14             1.className=java.util.HashSet
15             2.className=java.util.ArrayList
16             */
17             Collection con = (Collection)Class.forName(cs).newInstance();
18 
19             fis.close();
20             ReflectPoint1 p1 = new ReflectPoint1(1, 1);
21             ReflectPoint1 p2 = new ReflectPoint1(2, 2);
22             ReflectPoint1 p3 = new ReflectPoint1(1, 1);
23            
24             con.add(p1);
25             con.add(p2);
26             con.add(p3);
27             
28             Iterator it = con.iterator();
29             while(it.hasNext())
30             {
31               ReflectPoint1 r1=(ReflectPoint1)it.next();
32               System.out.println(r1.getX()+"----"+r1.getY());
33             }
34         } catch (Exception e) {
35             e.printStackTrace();
36         }
37     }
38 }
39 class ReflectPoint1 {
40     private int x;
41 
42     private int y;
43 
44     public ReflectPoint1(int x, int y) {
45         super();
46         this.x = x;
47         this.y = y;
48     }
49 
50     @Override
51     public int hashCode() {
52         final int prime = 31;
53         int result = 1;
54         result = prime * result + x;
55         result = prime * result + y;
56         return result;
57     }
58 
59     @Override
60     public boolean equals(Object obj) {
61         if (this == obj)
62             return true;
63         if (obj == null)
64             return false;
65         if (getClass() != obj.getClass())
66             return false;
67         ReflectPoint1 other = (ReflectPoint1) obj;
68         if (x != other.x)
69             return false;
70         if (y != other.y)
71             return false;
72         return true;
73     }
74     public int getX()
75      {
76          return x;
77      }
78      public int getY()
79      {
80          return y;
81      }
82 }
83 配置文件1和2结果分别为:
84 ---------- 运行 ----------
85 1----1
86 2----2
87 
88 
89 输出完成 (耗时 0 秒) - 正常终止
90 ---------- 运行 ----------
91 1----1
92 2----2
93 1----1
94 
95 输出完成 (耗时 0 秒) - 正常终止

 

 

 

posted on 2013-08-22 16:50  xscn  阅读(201)  评论(0编辑  收藏  举报