- 为什么要学习反射?
首先说一点:反射是比较低级的一个知识,如果是单纯的撸码来是实现功能的话,反射基本用不到,而且反射的性能呢也肯定不怎么好的,但是我个人觉得反射是一个很重要的知识点,在我老早学习的时候就是这么认为的,主要的原因有2点:
1,java的编译和运行。java是强类型语言,运行java每一个对象都会出现2个种类型:编译时类型和运行时类型,好多的知识点都只是关于编译时类型的,比如说多态,比如说泛型。java在运行时那个对象是自己实实在在的那个对象,并不是说编译时候的类型,所以要研究好反射,就可以得到这个实实在在的那个对象了,这对于理解java的工作原理是很重要的。举个例子:如果程序需要在运行时发现类和对象的真实信息,而且在编译阶段就根本不知道这个对象确切的属于哪个类,那么程序只能依靠运行时信息来发现该对象和类的真实情况,就必须要用反射了。注意:在反射中没有简单数据类型,所有的编译时类型都是对象。反射把编译时应该解决的问题留到了运行时。2,框架的底层。我记得在老早的时候,几个搞android的是朋友就说框架不好的,怎么怎么地的,性能了什么的,不好了怎么怎么样,我觉得很不可理解。在以前我个人也觉得框架是一个比较深奥的东西,现在看来框架其实也没有什么的。说白了,框架就2个东西,一个是解析配置文件,一个是反射动态来获得对象。先说配置文件:这个可能就是j2ee和.net最大的区别,j2ee领域存在大量的配置,不管是XML配置文件,还是通过注解方式,其实就是把一些不关于业务逻辑的代码抽离出来,方便以后的改动,如果你不这样子实现的话,那只能硬码写4了,几乎没有任何可变性,那就是不是框架了。然后动态获得对象,除去性能了,安全了,可维护等等多个有利条件,框架就是把所谓的一些通用的步骤整理起来,这样子就彻底的解放了码农,码农要做的只是把自己的业务逻辑代码放进去,这样子就可以跑了,那么问题来了:框架都是T前写好的jar文件,已经编译过的,预先已经定义好了好多的对象在那里运行呢,那么我们自己放进去的对象如何才能插入到框架里面跑起来呢?很简单:反射。所以要认真的研究好反射,这样子对于理解框架的底层很重要的。说实际的,现在框架对于我来说并不是什么很高深的东西了,无非就是通过一些很好的设计模式,来完成一些通用功能。我们在使用的时候,写一些配置文件和自己的业务逻辑代码,框架会根据配置文件,来解析XML或者注解,把我们的代码参与到运行阶段,这就是任何一个框架的原理。
- Class类
定义:在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。虚拟机利用类型标识选用相应的方法执行。可以通过专门的类访问这些信息。保存这些信息的类被称为Class(类)。
每一个类被加载之后,系统都会为该类生成一个Class对象,通过该Class对象就可以访问JVM中的这个类,说白了,这个Class就是JVM的2进制文件,JDK中是这么说:Class就是表示Class 对象建模的类,用这个类在创建对象的。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。Class 对象只能由系统建立对象,一个类在 JVM 中只会有一个Class实例,每个类的实例都会记得自己是由哪个 Class 实例所生成。
注意:对象照镜子后可以得到的信息:某个类的数据成员名、方法和构造器、某个类到底实现了哪些接口。这句话其实也说出了反射的最准备和直观的意义:就是通过JVM中的这个类的一部分来获得所有的关于这个类的信息,并为之转换成一个其他的可以使用的类型。
现在很明显了,玩反射第一步就是要或者这个对象或者是类在JVM中的那个Class对象,然后调用他的方法来获得其他的东西。那么如何获得Class对象?有3种方式:
(1)通过类名获得类对象 Class c1 = String.class; //类.Class;
(2)通过对象获得类对象 Class c2 = s.getClass(); //类对象.getClass();
(3)通过Class.forName(“类的全名”)来获得类对象 //Class.forName(包名.类名); Class c3 = Class.forName(“Java.lang.String”);//这会强行加载类到内存里,前两种不加载。 注:第三种方式是最常用最灵活的方式。第三种方式又叫强制类加载。
关于这3种方式的取舍:如果现在我们得到的是一个对象,那么就只能用getClass()这个方法了,如果现在给了类名了,那么我们就可以使用这个类名打点.class这个属性,其实一般使用这个最多。如果我们现在拿到的只是一个普通的字符串,我们需要获得这个字符串对应的Class对象,就只能使用Class类的静态方法:ForName()了。
package linkin; public class Linkin { public static void main(String[] args) throws ClassNotFoundException { //通过类的Class属性来获取,这个方法最安全可靠,程序的性能也是最高的 Class<String> class1 = String.class; //通过一个对象来获取 Class<String> class2 = (Class<String>) "LinkinPark...".getClass(); //通过Class类的静态方法来获取,这里要抛异常的 Class<String> class3 = (Class<String>) Class.forName("java.lang.String"); } }
- 一旦获得某个类所对应的Class对象后,就可以调用Class类的方法来获得这个类的所有的真实信息了,包括构造器,属性,方法,注解,泛型。
方法:
属性:
注解:
declared 申明的,公然的,容易发现上面API中,后面的方法都是在前面的方法签名上加了这个单词,意思就是可以获取所有的对应的成员,与这个成员的访问权限无关,要是直接使用前面的话,就只能获取这个类对应的用public来修饰的成员。此外,其他的方法不是经常使用,比如获得有关内部类的情况,继承的是父类,实现的接口,类的修饰符,所在包,类名等等,要是用的话自己去翻API。代码都比较简单的,这里就不敲了。注意一点:对于只能在源代码上保留的注释,使用运行时获得Class对象无法访问该注释对象。
- 通过Class对象可以得到大量的Method,Constructor,Field等对象,然后可以通过这些对象来执行实际的功能,比如说调用方法,创建实例。
通过反射来生成对象有2中方式:(1),使用Class对象的newInstance()方法。要求该CLass类必须要有默认的构造器。这里创建实例实际调用的就是这个默认的构造器。(2),先使用Class对象来获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方式可以使用指定的构造器来创建实例。
package linkin; import java.lang.reflect.Constructor; public class Linkin { private String name; public Linkin() { } public Linkin(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void main(String[] args) throws Exception { Class<Linkin> class1 = Linkin.class; //调用默认的构造器 class1.newInstance(); //调用自己指定的构造器,注意后面方法的参数是传入的参数类型。 Constructor<Linkin> cons = class1.getConstructor(String.class); cons.newInstance("LinkinPark"); } }
2,调用方法
调用方法就一种方式,先获取这个方法,然后再用这个方法的invoke()方法。
package linkin; import java.lang.reflect.Method; public class Linkin { private String name; public Linkin() { } public Linkin(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public static void test(String str) { System.out.println("这里是该类的静态方法:"+str); } public static void main(String[] args) throws Exception { Class<Linkin> class1 = Linkin.class; Linkin linkin = class1.newInstance(); Method mth = class1.getMethod("setName", String.class); mth.invoke(linkin, "LinkinPark.."); System.out.println(linkin.getName()); Method mth1 = class1.getMethod("test", String.class); //要是这个方法时一个静态方法,那么传入主调是一个null就好了 mth1.invoke(null, "Binger"); } }
3,访问属性值
这个也很简单,通过Class对象可以获得对象的属性。
如果是基本类型的话,就在get和set后面加上相应的XXX类型就可以。
package linkin; import java.lang.reflect.Field; class Linkin { public String name = "LinkinPark..."; private int age; private String andress = "hangzhou"; public String getAndress() { return andress; } public void setAndress(String andress) { this.andress = andress; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class Test { public static void main(String[] args) throws Exception { Class<Linkin> class1 = Linkin.class; Linkin linkin = class1.newInstance(); Field name = class1.getField("name"); String nameValue = (String) name.get(linkin); System.out.println(nameValue); //不管限定符,可以获取所有的属性 Field andress = class1.getDeclaredField("andress"); //如果是私有的,或者是其他不能访问的,这里就设置可以到达,这样子就可以访问了 andress.setAccessible(true); String andressValue = (String) andress.get(linkin); System.out.println(andressValue); Field age = class1.getDeclaredField("age"); age.setAccessible(true); age.setInt(linkin, 25); System.out.println(linkin.getAge()); } }
总结:以上3个知识点经常用到,有2点注意,1,一旦在调反射得到某一个成员的时候说没有这个成员(Exception in thread "main" java.lang.NoSuchFieldException: age),那么就把原来的方法上加上declared ,这样子就不会去管这个成员上面的限定符了。2,要是说某一个私有的东西不能到达或者访问,(Class linkin.Test can not access a member of class linkin.Linkin with modifiers "private")那么就设置这个成员的setAccessible()为true。
另外还有2个东西可以了解下,不是很常用到:
1,操作数组:java.lang.reflect下还提供了一个Array类,这个类的对象可以代表所有的数组。下面的方法全是都是静态的。
这个方法没有使用泛型,自己可以封装下:public static <T> T[] newInstance(Class<T> type,int len);
package linkin; import java.lang.reflect.Array; public class Test { public static void main(String[] args) throws Exception { Object arr = Array.newInstance(String.class, 5); //下面的get和set方法全部都是静态的,使用起来不是很方便耶 Array.set(arr, 0, "LinkinPark..."); Array.set(arr, 1, "Binger..."); String linkin = (String) Array.get(arr, 0); String binger = (String) Array.get(arr, 1); System.out.println(linkin+":"+binger); //这里强转上面那个object对象,就可以使用循环了。这个方法有点恶心,既然有了泛型了,为什么不整成泛型的呢? String[] huhu = (String[]) arr; for (String string : huhu) { System.out.println(string); } } }
2,操作泛型:通过在反射中使用泛型,可以避免使用反射生成的对象做强转。
package linkin; public class Test { public static <T> T getInstance(Class<T> cls) throws InstantiationException, IllegalAccessException { return cls.newInstance(); } public static void main(String[] args) throws Exception { Class<String> cls = String.class; //这里要强转 String str1 = (String) cls.newInstance(); //这里不需要强转了 String str2 = Test.getInstance(String.class); } }
3,使用反射来获取泛型信息
前面所讲的获取属性后,属性里面有一个方法:getType();这个方法只是对普通的属性有效,要是这个属性使用了泛型的话,就要使用getGenericType()方法。获得对应的Type类型后,就可以掉上面的方法来获得具体的泛型类型了,其实这也是就hibernate中根据配置文件来读取泛型实体属性的类型的原理。
package linkin; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; public class Test { private Map<String,Integer> map; public static void main(String[] args) throws Exception { Class<Test> cla = Test.class; Field map = cla.getDeclaredField("map"); Class<?> type1 = map.getType(); System.out.println(type1);//interface java.util.Map Type type2 = map.getGenericType(); if(type2 instanceof ParameterizedType) { //强转成为泛型参数化类型 ParameterizedType pType2 = (ParameterizedType) type2; //原始类型 Type rType = pType2.getRawType(); System.out.println(rType);//interface java.util.Map Type[] tTypes = pType2.getActualTypeArguments(); for (Type type : tTypes) { //class java.lang.String class java.lang.Integer System.out.println(type); } } else { System.out.println("获取属性的泛型类型出错了吆..."); } } }