Java反射

来源:互联网 发布:天心天思软件 编辑:程序博客网 时间:2024/05/22 12:10

一 反射:   

        其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象,并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个类进行解剖。

    反射也可以理解为把Java类中的各种成分映射成相应的Java类。 其实就是通过一个类的Class对象(字节码)把类中的各种成分映射成相应的java类的对象来使用。例如一个Java类中用一个Class的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等信息用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类是显然要提供一系列方法,来获得其中的变量 方法 构造方法 修饰符 包等信息,这些信息就是用相应类的实例对象来表示,它们是是Field Method Constructor Package等等。一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

反射的好处:大大的增强了程序的扩展性

二 反射的基石-->Class类:

    Java类用于描述一类事物的共性,这类事物有什么属性,没有什么属性,至于这个属性是什么,则是由这个类的实例对象来确定的,不同的实例对象又不同的属性值,Java程序中的各个Java类,它们是否属于同一类事物,是不是可以用一个类来描述这类事物呢?这个类的名字就是Class,要注意与class关键字的区别。Class类描述了哪方面的信息呢?例如:类的名字,类的访问属性,类属于的包名,字段名称的列表,方法名称的列表,等等。学习反射首先要明白Class这个类。
Class即字节码,内存里面的每一份字节码就是一个Class类的的实例对象。java虚拟机里面每一个类只保存一份字节码。

1 反射的基本步骤:

    1) 获得Class对象,就是获取到指定的名称的字节码文件对象。

    2) 实例化对象,获得类的属性、方法或构造函数。

    3) 访问属性、调用方法、调用构造函数创建对象。

2 返回得到字节码(Class类)对应的实例对象的三种方式:

    1)类名.class;例如:System.class
    2)对象名.getClass();
例如:new Date().getClass()                
    3)Class.forName("类名") 这份字节码被曾经加载过已经在Java虚拟机当中直接返回。如果要加载的类字节码没有被加载过,那么就用类加载器去加载,把加载进来的字节码缓存到虚拟机里面。
例如:     Class.forName("java.utils.Date")

   4)根据字节码实例对象获取实例对象

    Object obj=new Person("zhangsan",19)

    clazz=obj.getClass()

   Object obj = clazz.newInstance()//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();

public static void method_2() throws Exception {Class clazz = Class.forName("cn.kid.bean.Person");// 既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。// 获取一个带参数的构造器。Constructor constructor = clazz.getConstructor(String.class, int.class);// 想要对对象进行初始化,使用构造器的方法newInstance();Object obj = constructor.newInstance("zhagnsan", 30);// 获取所有构造器。Constructor[] constructors = clazz.getConstructors();// 只包含公共的constructors = clazz.getDeclaredConstructors();// 包含私有的for (Constructor con : constructors) {System.out.println(con);}}

问:众多的人用一个什么类表示?众多的Java类用一个什么类表示?
答:Person类代表人,它的实例对象就是张三,李四应选择一个个具体的人,Class类代表Java类,它的各个实例对象又分别对应什么呢?

问:对应各个类型在内存中的字节码,例如Person类的字节码,ArrayList类的字节码,不同的类的字节码是不同的,所以他们在内存中的内容是不同的,这一个个的空间可分别用一个个对象来表示,这些对象显然具有相同的类型,这个类型是什么呢?

3 九个预定义Class实例对象(八个基本类型和 void):
    1)八大基本类型也有其对应的Class实例对象,void也有对应的Class实例 void.class
    2)参看Class.isPrimitive方法的帮助
    3)int.class==Integer.TYPE      
       
int.class.isPrimitive();-----true    //判断class类是否为基本数据类型。int[].class.isArray();-----true   //判断Class类是否为数组类。
   Integer.TYPE返回的是对应的基本数据类型的那份字节码。
   关于int 和Integer是两种不同的类型 可参考以下代码:
              
public class TestClass {public static void main(String args[]) {System.out.println(Integer.class);System.out.println(Integer.TYPE);System.out.println(int.class);}}打印结果为:class java.lang.Integerintint

提示:只要是在源程序中出现的类型,都有各自的Class实例对象,如:int[], void 等。
4 反射的用法:
   1)需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:
   Class.forName(classname) //用于做类加载
   obj.getClass() //用于获得对象的类型
   类名.class 用于获得指定的类型,传参用
   2) 反射类的成员方法:
   Class clazz = Person.class;
   Method method = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
   method.invoke();

   3) 反射类的构造函数:
   Constructor con = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
   con.newInstance(params...)

   4) 反射类的属性:
   Field field = clazz.getField(fieldName)
   field.setAccessible(true)
   field.setObject(value)

三 数组类型的Class实例对象:
    1.Class类里的 isArray方法,判断此Class实例对象是否为数组类型
    2.总之只要在原程序中出现的类型,都有各自的Class实例对象,例如int[ ],void..........
   数组的反射:
    1)具有相同参数和元素类型的数组属于同一类型,即具有相同的Class实例对象
    2)代表数组的Class实例对象的getSuperClass()方法返回的父类为Object对应的Class
    3)基本类型的一维数组可以被当作Object类型使用,不能当作Object[ ]类型使用,非基本类型的一维数组,既可以当作Object类型使用,又可以当作Object[ ]类型使用
    4)Arrays工具类用于完成对数组的反射操作,Arrays.asList()方法处理int[ ]和String[ ]时的差异。
例子代码如下
int[ ] a1=new int[ ]{1,2,3};String[ ] str=new String[ ]{"a","b","c"  };System.out.println(Arrays.asList(a1));//被当做Object类System.out.println(Arrays.asList(str)); //被当做Object[]类

打印结果为

[[I@1cfb549]
[a,b,c]

例子代码2:

import java.util.ArrayList;import java.util.Arrays;import java.util.List;public class Test {public static void main(String args[]) {String str = "abcdefghijklmnopq";System.out.println(str.substring(1, 3));// 1包3不包List<String> l = (List<String>) Arrays.asList(new String[] { "aa","bb", "cc", "dd" });System.out.println(l.toString());}}
打印结果:
bc
[aa, bb, cc, dd]

5)
数组反射的应用

import java.lang.reflect.Array;class arrReflectDemo {private 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(obj, i));}} else {System.out.println(obj);}}public static void main(String args[]) {printObject(new String[] { "aa", "bb", "cc" });printObject(new int[] { 1, 2, 3 });}}

6)Array与Arrays的区别:
    java.lang.reflect.Array:此类提供了动态创建和访问 Java 数组的方法。
    java.util.Arrays:此类包含用来操作数组(比如排序和搜索)的各种方法。此类还包含一个允许将数组作为列表来查看的静态工厂。前者倾向于操作数组的单个元素,后者倾向于操作整个数组。
Array工具类用于完成对数组的反射操作,Array类里面的所有方法都是静态方法。如:Array.getLength(obj); Array.get(obj, index);     
数组反射的简单应用程序片段如下:
public void printObject(Object obj) {    if(obj.getClass().isArray()) {    for(int i = 0; i < Array.getLength(obj); i++) {               System.out.println(Array.get(obj, i));          }     } else {                System.out.println(obj);     }}

四 Constructor类:

该对象内部的具体代码是怎么写的呢?用到了缓存机制来保存默认构造方法的实例对象。

获取了字节码文件对象后,最终都需要创建指定类的对象:

创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):

1)调用空参数的构造函数:使用了Class类中的newInstance()方法。

2)调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的newInstance(实际参数) 进行对象的初始化。

综上所述,第二种方式,必须要先明确具体的构造函数的参数类型,不便于扩展。所以一般情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。

1 得到某个类所有的构造方法:

例子:
Contructor[ ] constructor=Class.forName("java.lang.String").getConstructor();
2 得到某个构造方法:
例子:
Constructor constructor=Class.forName("java.lang.String").getConstructor(StringBuffer.class);//获取方法时要用到的类型
3 创建实例对象:
  通常方式:String str=new String(new StringBuffer("abc"));
  反射方式:String str=(String)constructor.newInstance(new StringBuffer("abc")); 
4 Class.newInstance()方法:
例子:
String obj=(String)Class.forName("java.lang.String").new Instance();//该方法内部先得到默认的构造方法,然后用该构造方法创建对象

五 Field类:

     1 Field类代表某个类中的一个成员变量,Field实例对象只代表类字节码里面的一个变量,并不代表某一对象里面的变量本身具体的值。我们需要调用Field类中的方法get(Object obj)(注意:此方法返回类型是Object类型,很多时候我们需要做类型强制转换。)或其他对应的方法才能获得具体实例对象所对应的此字段的值。
     2 问题:得到的Field对象是对应到类上面的成员变量还是对应到对象的成员变量?类只有一个,而该类的实例对象有多个,如果是与对象关联,那关联的是哪个对象呢?所以字段fieldx代表的是x的定义,而不是具体的x变量。
 示例代码(省略了部分代码):
ReflectPoint pt1=new ReflectPoint(3,5);Field fieldY=pt1.getClass().getField("y");
filedY的值是多少?是5,错!fieldY不是对象身上的变量,而是类上,要用它去取某个对象上对应的值。        
     3 暴力反射:
     类里面的字段声明为private int x;可通过,暴力反射去获得。在对象里面声明为非public的的成员变量需要用getDeclaredField(String name)才能得到Class对象中的此变量名所对应的Field实例对象。然后如果需要访问此Field实例对象所对应的具体的值,那么需要调用Field父类的方法setAccessible(true)来指示反射的对象在使用时应该取消Java语言访问检查,然后才能访问具体对象所对应的此字段的值:

Field fieldX=pt1.getClass().getDeclareField("x");     fieldX.setAccessible(true);     System.out.println(fieldX.get(pt1));
事例代码:
package com.test.Package2;import java.lang.reflect.Field;class ReflectPoint {    private int y;private int x;    ReflectPoint(int x, int y) {this.x = x;this.y = y;}}class ArrReflectDemo {public static void main(String args[]) throws Exception {ReflectPoint point = new ReflectPoint(1, 7);        Field field_y = Class.forName("com.test.Package2.ReflectPoint").getDeclaredField("y");////因为Y字段是私有的,无法直接用getField方法获得,只能用getDeclaredField()方法        /* Field field_y = point.getClass().getDeclaredField("y"); */field_y.setAccessible(true);////下面输出时,由于字段是私有的,也无法直接输出,必须设为可处理的状态。System.out.println(field_y.get(point));Field x = Class.forName("com.test.Package2.ReflectPoint").getDeclaredField("x");x.setAccessible(true);System.out.println(x.get(point));}}
打印的结果是:
7
1  
       

 例子:
     通过反射换掉一个对象中的字段,部分代码如下:
     
package com.kid.reflect;import java.lang.reflect.Field;public class RefChangeStringValue {public static void changeStringValue(Object obj) {Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {if (field.getType() == String.class) {try {field.setAccessible(true);field.set(obj, ((String) field.get(obj)).replace('b', 'a'));} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}public static void main(String[] args) {TestField tv = new TestField();changeStringValue(tv);System.out.println(tv);}}class TestField {String s = "abbbbbbbbbb";    @Overridepublic String toString() {return s;}}

打印结果为:
aaaaaaaaaaa

六 Method 类:

 反射指定类中的方法:

//获取类中所有的方法。

public static void method_1() throws Exception {Class clazz = Class.forName("cn.kid.bean.Person");Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。for(Method method : methods) {System.out.println(method);}}
//获取类中所有的方法。
public static void method_1() throws Exception {Class clazz = Class.forName("cn.kid.bean.Person");Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。for(Method method : methods) {System.out.println(method);}}

//获取指定方法;

public static void method_2() throws Exception {Class clazz = Class.forName("cn.kid.bean.Person");//获取指定名称的方法。Method method = clazz.getMethod("show", int.class,String.class);//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。Object obj = clazz.newInstance();method.invoke(obj, 39,"hehehe");//执行一个方法}

//想要运行私有方法。

public static void method_3() throws Exception {Class clazz = Class.forName("cn.kid.bean.Person");//想要获取私有方法。必须用getDeclearMethod();Method method = clazz.getDeclaredMethod("method", null);// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。}
//反射静态方法。
public static void method_4() throws Exception {Class clazz = Class.forName("cn.kid.bean.Person");Method method = clazz.getMethod("function",null);method.invoke(null,null);}

       1 Method类代表某个类的一个方法
    2 得到类中的某一个方法
      例子:
     Method charAt=Class.forName("java.lang.String").getMethod("charAt",int.class);
    3 调用方法
    通常方法:System.out.println(str.chartAt()); 反射方式:System.out.println(chartAt.invoke(str,1));
    提示: 如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么意义呢?说明该Method对象对应的是一个静态方法。 静态方法的反射调用时invoke()方法中的对象参数可以是null,如:method.invoke(null, "hello")
    4.用反射的方式执行某个类中的main方法
     目标:写一个程序。这个程序能够根据用户提供的类名,去执行该类的main方法。用普通方法调用完后,要明白为什么要用反射去调。
     问题:启动Java程序的main方法的参数是一个字符串数组,即public static void main(String args[ ]),通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?jdk1.5的语法,整个数组是一个参数,而按jdk1.4的语法,整个数组的每个元素对应一个参数,当吧一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?jdk1.5肯定兼容jdk1.4的语法,会按jdk1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以在给main方法传递参数时,不能使用代码。
     mainMethod.invoke(null,new String[ ]{ "xx1","xx2" });
     因为javac只能把它当作jdk1.4的语法进行理解,而不把它当作jdk1.4的语法进行理解,因此会出现参数类型不对的问题。
     解决办法:
        mainMethod.invoke(null,new Object[ ]{new String{ "xx1","xx2" } }); 
        mainMethod.invoke(null,(Object)new String{ "xx1","xx2"  });
     编译器会做特殊处理,编译时不会把参数当作数组看待,因此不会将数组打散成若干个参数。
Method类的使用,如下代码:
package com.kid.reflect;import java.lang.reflect.Method;public class RefMethodTest {public static void main(String[] args) throws Exception {String str = "hello";Method methodCharAt = String.class.getMethod("charAt", int.class);System.out.println(methodCharAt.invoke(str, 0));Method methodMin = Math.class.getMethod("min", int.class, int.class);System.out.println(methodMin.invoke(null, 3, 20));//返回两数较少的一个ClassTest.main(new String[] { "aa", "bb" });//正常方式调用Method methodMain = Class.forName("com.kid.reflect.ClassTest").getMethod("main", String[].class);//利用反射调用main方法methodMain.invoke(null, (Object) new String[] { "hello", "world" });methodMain.invoke(null,new Object[] { new String[] { "hello", "world" } });}}class ClassTest {public static void main(String[] args) {for (String arg : args) {System.out.println(arg);}}}

打印结果为:

h
3
aa
bb
hello
world
hello
world

七.反射的作用==》实现框架功能
1.框架与框架要解决的核心问题:
    开发商房子卖给用户住,由用户自己安装门窗和空调,开发商做的房子就是框架,用户需要使用我的框架,吧门窗插入进开发商提供的框架中,框架工具类有区别,工具类被用户的类调用用户提供的类。
2.框架要解决的核心问题:
    开发者在写框架的时候(做房子),用户可能还不会写程序。那么框架程序程序怎么调用你以后写的类(门窗)?因为在写程序时无法知道要被调用的类名,所以在程序中无法直接new某个类的实例对象了,而要用反射方式做。
3.框架通常都有配置文件Structs Spring hibernate,所以配置文件都放在classpath目录下。



          

















                          

               

0 0
原创粉丝点击