前言java
忽然发现很久没写博客了,前面写的都是关于Android的东西,今天心血来潮忽然有一种冲动想写一篇基于JAVA技术的博客,别问我为何?有钱、任性!框架
今天就来谈谈反射机制;学过JAVA的人不必定懂得反射,可是必定据说过反射,不过也仅仅是据说过而已;由于反射用的地方也不会那么多,可是反射用的妙常常会解决咱们挠破头皮的大问题。至于诸如为何叫作反射、而不叫作正射倒射此类的历史问题,仍是交给历史学家去研究吧。。。this
反射的基石.net
在谈反射以前,咱们应该先了解下类的概念来引入。类是一种抽象的概念,举个例子“我爸是李刚我爸李双江”,从这句话中咱们发现有李刚、李双江这两我的,咱们来抽象它们的特色,咱们发现它们都像人。没错,那么咱们就能够将人做为它们的一个抽象,反过来讲李刚和李双江就是人的一个具体实例;因此咱们能够用一个Person类表明人来表示这种抽象。既然理解了类的概念,那些年那些陪咱们度过日日夜夜的java类们,咱们是否是也应该抽象出一个类来证实一下他们,没错,那就是Class了!对象
Class就是java类的抽象,它抽象出了java的共性,如类的名字、类的构造方法、类的成员变量、类的老爸、类的方法等等等。既然这么说,那么咱们经过这个Class,咱们就能够获得这个类的方方面面的信息、兴许还能比查户口还详细呢。咱们建立出的每个类,例如person类,说到底也就是咱们实例化了一个Class的实例,来保存person类的名字、变量、方法这些信息,在内存中表示就是保存了person类的字节码,若是你理解了这些而且接受了个人见解,那么我们有共同语言,能够继续往下说。游戏
既然person类有拥有本身的字节码,那么咱们能够获取到这个字节码吗?答案是确定的,并且还不止一种方法。参见代码:内存
public static void main(String[] args) throws Exception { //第一种方法,直接经过Person类来获取字节码 Class cls1 = Person.class; //第二种方法,经过类的实例来获取Person类的字节码 Person person = new Person(); Class cls2 = person.getClass(); //第三种方法,调用Class类的静态方法来获取对应类的字节码,该方法会抛出异常 Class cls3 = Class.forName("Person"); }
从代码中看,咱们能够判定:Person类的字节码就是Class的具体实例;咱们也能够猜到,至于类的字节码有包含什么东东,你们尽管猜吧,后面我会慢慢讲解。咱们再来看看下面的代码:字符串
System.out.println(cls1 == cls2); System.out.println(cls2 == cls3);
咱们运行程序,会发现输出了:get
true
true博客
这三个玩意居然是同一个东西,那么就很好解释了:在java的虚拟机中,每个类都会被保存成为一个字节码,用来保存该类的信息如名字、父类、变量、方法等。一个类的字节码在虚拟机中有且只有一个,也就是在第一次加载该类的时候会将类的字节码加载到java虚拟机中,而上面有三种方法能够从虚拟机中获取类的字节码(PS:第三种方法最为经常使用),可是你别疑惑获取这个字节码干吗嘛用,咱们要反射嘛,说白了咱们就是要来强暴这字节码(Class)。。。。。(~ o ~)~zZ
理解反射
既然前面讲解了Class类,如今咱们能够开始讲反射了。反射是什么呢?反射就是将类的各类成分映射成各类类,咱们知道一个java类能够用一个class的对象来表示,这个类的组成成分有名字、变量、构造方法等信息,咱们固然能够用一个个java类的表示。换句话说,表示java类的Class类提供了一系列的方法给咱们用来获取其中的变量、方法、构造方法等信息,这些信息也有相应的类的实例来表示,也就是Field、Method、Constructor等等。或者更通俗的说,Field就是java类中的全部变量的抽象、同理Method就是java类中全部方法的抽象,若是仍是看不懂,很正常,往下看代码估计更好理解。
构造方法的反射
从前面咱们知道,Constructor就是java类全部构造方法的抽象。那么咱们怎么经过反射来获取类的构造方法呢,参见代码:
public class Test{ public static void main(String[] args) throws Exception { Class cls = Person.class;//获取Person类的字节码 Constructor constructor1 = cls.getConstructor();//调用getConstructor()获取Person无参构造方法 Person p1 = (Person) constructor1.newInstance();//经过调用newInstance()来执行无参构造方法 Constructor constructor2 = cls.getConstructor(int.class);//调用getConstructor(*.class)获取Person带参构造方法 Person p2 = (Person) constructor2.newInstance(1);//经过调用newInstance(int)来执行带参构造方法 } } class Person{ public Person(){System.out.println("无参构造方法");} public Person(int i){System.out.println("带参构造方法");} }
控制台输出:
无参构造方法
带参构造方法
这里咱们开始讲解一下,代码经过Person.class来获取Person类的字节码并将其保存在一个Class类的实例cls中,而后再经过cls.getConstructor()来获取字节码中的构造方法并将其放入Constructor的实例constructor之中,很明显,这个constructor并非Person的构造方法,而是保存Person构造方法的一个实例,因此咱们能够经过调用newInstance()来获取保存在constructor中的person类的构造方法并执行,构造方法执行并返回一个Object的实例,并将其强转为Person并保存在person的变量中,这就是调用反射来获取构造方法生成实例的全过程。
在代码中,咱们也能够知道怎么获取带参的构造方法,这是咱们须要在getConstructor()是传入构造方法对应参数的字节码,例如代码中Person(int i)咱们须要传入一个int.class(或者是Integet.TYPE)的字节码提供给Class定位须要获取的构造方法。可是若是你比较贪心想获取所有的构造方法,没问题,经过getConstructors():
Class cls = Person.class;//获取Person类的字节码 Constructor[] constructors = cls.getConstructors();//调用getConstructor()获取Person无参构造方法 for(Constructor c : constructors){ //Person p = c.newInstance(****);遍历执行构造方法 }
而后经过for循环,就能够处理你所须要的构造方法了。
成员变量的反射
咱们说完了构造方法的反射,咱们就接下来谈谈成员变量的反射的用法。惯例仍是先看代码:
public class Test{ public static void main(String[] args) throws Exception { Person p = new Person("小红", 20); Class cls = Class.forName("com.net168.test.Person"); Field fieldName = cls.getField("name"); //fieldNmae的值是小红吗?错!它只是表明Person类身上name的这个变量,并无对应到对象身上 // System.out.println(fieldNmae); //fieldNmae不表明具体的值,只表明一个变量,因此咱们须要传入一个person实例才能获取到其对应的值 System.out.println(fieldName.get(p)); } } class Person{ public Person(String name, int age){ this.name = name; this.age = age; } public String name; private int age;//对于某些人来讲,年龄是秘密! }
跟构造方法的反射的实现差很少,咱们也是先经过获取Person的字节码cls,而后从其中将Person的成员变量映射成一个Field类,在这里咱们将Person.name这个变量映射成fieldName这个对象,固然咱们不可能单纯的从fieldName这个对象中获取我们的“小红”,由于fieldName是从cls中获取的而并非从person的实例中获取的,因此它值并非小红;而是咱们能够经过小红这个person的实例p与fieldName联系起来,也就是调用fieldName.get(p)才能获取小红这个字符串。
可是咱们若是想获取小红年龄呢,女人的年龄大可能是秘密,私有变量咱们也能够这样获取吗?修改下代码:
Person p = new Person("小红", 20); Class cls = Class.forName("com.net168.test.Person"); Field fieldAge = cls.getField("age"); System.out.println(fieldAge.get(p));
执行结果是:
Exception in thread "main" java.lang.NoSuchFieldException: age
at java.lang.Class.getField(Unknown Source)
at com.net168.test.Test.main(Test.java:11)
没有这个字段,明明是有这个age的字段呀!可是咱们发现,原来这个女生的年龄是私有的,她就是不愿告诉我们啊,那怎么办?她不想告诉咱们,咱们就无法知道了吗?屌丝是不会那么容易屈服的!因此咱们能够稍做一点处理,以下:
Person p = new Person("小红", 20); Class cls = Class.forName("com.net168.test.Person"); Field fieldAge = cls.getDeclaredField("age");//获取类的私有变量 fieldAge.setAccessible(true);//设置该私有变量可被外面访问 System.out.println(fieldAge.get(p));
能够经过getDeclaredField()来获取Person类的私有变量,并且咱们还能够在获取到外界看不到的私有变量后,再经过setAccessible(true)设置该私有变量能够被强制访问。暴力吧,JAVA的反射也被有些人叫作暴力反射。。。运行代码,咱们就知道了小红的芳龄 了:20
成员方法的反射
若是你们看懂了前面成员变量和构造方法的反射,基本上再了解成员方法的反射就没有什么困难了,不卖关子,仍是先上下代码:
public class Test{ public static void main(String[] args) throws Exception { Person p = new Person(); Class cls = p.getClass();//获取Person的字节码 //获取setName()方法,须要传入参数为String Method method1 = cls.getMethod("setName", String.class); method1.invoke(p, "小明");//关联p,输入“小明”并执行该方法 //获取getName()方法,无参则设为null Method method2 = cls.getMethod("getName", null); String name = (String) method2.invoke(p, null);//invoke返回的类型为Object System.out.println(name); //获取静态方法,因为静态方法只依赖与类,因此不须要提供具体的实例 Method method3 = cls.getMethod("show", int.class); // method3.invoke(p, 1);提供具体实例p也可经过编译 method3.invoke(null, 1); } } class Person{ public String name; public String getName(){ return name; } public void setName(String name){ this.name = name; System.out.println("设置name值为:" + name); } public static void show(int i){ System.out.println("这是一个静态方法:" + i); } }
程序运行结果:
设置name值为:小明
小明
这是一个静态方法:1
在方法的反射中,咱们是利用了Method这个类,因为跟构造方法相似,因此我不就再就获取有参无参的方法的不一样之处进行讲解。整体来讲,就是经过Person的字节码获取到Person类中对应的方法并将其保存到Method的一个对象中,而后经过这个对象跟Person的具体实例进行搭配,经过invoke()就能够调用到具体实例的对应方法。在这里咱们须要注意的时静态方法的反射,因为静态方法属于一个类并非属于特定的一个对象,因此咱们在调用静态方法的invoke()时,并不须要传入一个对象,固然你非要传入一个具体的实例也是没有关系的,答案依然正确。
对于反射就先讲这么多吧,后面有时间再来说讲反射的深刻应用以及反射在框架搭建的用处。又浪费我一夜游戏时间了。。。。任性呀。。。