1、引言 html
不少時候咱們的程序可能須要在運行時識別對象和類的信息,好比多態就是基於運行時環境進行動態判斷實際引用的對象。在運行時識別對象和類的信息主要有兩種方式:1.RTTI,具體是Class對象,它假定咱們在編譯時已經知道了全部類型。2.反射機制,運行咱們在運行時發現和使用類的信息。java
2、RTTIdom
RTTI(Run-Time Type Infomation),運行時類型信息。能夠在運行時識別一個對象的類型。類型信息在運行時經過Class對象表示,Class對象包含與類有關的信息,可使用Class對象來建立類的實例。ide
每一個類對應一個Class對象,這個Class對象放在.class文件中,當咱們的程序中首次主動使用某類型時,會把該類型所對應的Class對象加載進內存,在這篇文章JVM之類加載器中闡述了哪些狀況符合首次主動使用。post
既然RTTI和Class對象有莫大的關係,即有了Class對象,就能夠進行不少操做,那麼,咱們如何獲取到Class對象呢?有三種方法1. Class.forName("全限定名");(其中,全限定名爲包名+類名)。2. 類字面常量,如String.class,對應String類的Class對象。3.經過getClass()方法獲取Class對象,如String str = "abc";str.getClass();。this
經過一個類對應的Class對象後,咱們能夠作什麼?咱們能夠獲取該類的父類、接口、建立該類的對象、該類的構造器、字段、方法等等。總之,威力至關大。url
下面咱們經過一個例子來熟悉Class對象的各類用法。spa
package com.hust.grid.leesf.algorithms; import java.lang.reflect.Constructor; import java.lang.reflect.Method; interface SuperInterfaceA { }; interface SuperInterfaceB { }; class SuperC { private String name; public SuperC() { } public SuperC(String name) { this.name = name; } @Override public String toString() { return name; } } class Sub extends SuperC implements SuperInterfaceA, SuperInterfaceB { private String name; public Sub() { super(); } public Sub(String name) { super(name); this.name = name; } public String getName() { return name; } } public class Main { public static Sub makeInstance(Class<?> clazz) { Sub sub = null; try { sub = (Sub) clazz.newInstance(); } catch (InstantiationException | IllegalAccessException e) { e.printStackTrace(); } return sub; } public static void printBasicInfo(Class<?> clazz) { System.out.println("CanonicalName : " + clazz.getCanonicalName()); System.out.println("Name : " + clazz.getName()); System.out.println("Simple Name : " + clazz.getSimpleName()); System.out.println("SuperClass Name : " + clazz.getSuperclass().getName()); Class<?>[] interfaces = clazz.getInterfaces(); for (Class<?> inter : interfaces) { System.out.println("Interface SimpleName : " + inter.getSimpleName()); } Constructor<?>[] constructors = clazz.getConstructors(); for (Constructor<?> cons : constructors) { System.out.println("Constructor Name : " + cons.getName() + " And Parameter Count : " + cons.getParameterCount()); } Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { System.out.println("Method Name : " + method.getName()); } } public static void main(String[] args) { //Sub sub = new Sub(); //Class<?> clazz = sub.getClass(); Class<?> clazz = Sub.class; Sub instance = makeInstance(clazz); if (instance != null) { System.out.println("make instance successful"); } else { System.out.println("make instance unsuccessful"); } printBasicInfo(clazz); } }
運行結果: 代理
make instance successful CanonicalName : com.hust.grid.leesf.algorithms.Sub Name : com.hust.grid.leesf.algorithms.Sub Simple Name : Sub SuperClass Name : com.hust.grid.leesf.algorithms.SuperC Interface SimpleName : SuperInterfaceA Interface SimpleName : SuperInterfaceB Constructor Name : com.hust.grid.leesf.algorithms.Sub And Parameter Count : 0 Constructor Name : com.hust.grid.leesf.algorithms.Sub And Parameter Count : 1 Method Name : getName
說明:使用method一、method二、method3三種方法均可以得到Class對象,運行結果是等效的。可是三者仍是有稍許的區別。區別是從類的初始化角度來看的。如Class.forName("全限定名")會致使類型的加載、連接、初始化過程,而.class則不會初始化該類。顯然,getClass確定是會初始化該類的,由於這個方法時依託於類的對象。code
下面咱們經過一個例子比較.class和forName()兩種方法的區別。
package com.hust.grid.leesf.algorithms; import java.util.Random; class Init1 { static final int staticFinal1 = 1; static final int staticFinal2 = Main.random.nextInt(100); static { System.out.println("init init1"); } } class Init2 { static int staticNonFinal1 = 3; static { System.out.println("init init2"); } } class Init3 { static int staticNonFinal1 = 5; static { System.out.println("init init3"); } } public class Main { public static Random random = new Random(47); public static void main(String[] args) { Class<?> clazzClass = Init1.class; System.out.println("after init init1 ref"); System.out.println(Init1.staticFinal1); System.out.println(Init1.staticFinal2); System.out.println(Init2.staticNonFinal1); try { Class<?> clazz1 = Class.forName("com.hust.grid.leesf.algorithms.Init3"); System.out.println("after init init3 ref"); System.out.println(Init3.staticNonFinal1); } catch (ClassNotFoundException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } }
運行結果:
after init init1 ref 1 init init1 58 init init2 3 init init3 after init init3 ref 5
說明:從結果也進一步驗證了.class不會初始化類,而.forName()會初始化類。而且,對常量靜態域的使用也不會致使類的初始化。
3、反射
與RTTI必須在編譯器就知道全部類型不一樣,反射沒必要在編譯期就知道全部的類型,它能夠在運行過程當中使用動態加載的類,而這個類沒必要在編譯期就已經知道。反射主要由java.lang.reflect類庫的Field、Method、Constructor類支持。這些類的對象都是JVM在運行時進行建立,用來表示未知的類。
關於二者的區別更深入表達以下:對於RTTI而言,編譯器在編譯時打開和檢查.class文件;對於反射而言,.class文件在編譯時是不可獲取的,因此在運行時打開和檢查.class文件。
其實在的第一個例子中咱們已經用到了Constructor、Method類,如今咱們來更加具體的瞭解Constructor、Method、Field類。
package com.hust.grid.leesf.algorithms; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Scanner; class Human { } class Girl extends Human { private boolean beautiful; private int height; private String name; public Girl() { } public Girl(String name, int height, boolean beautiful) { this.name = name; this.height = height; this.beautiful = beautiful; } public boolean isBeautiful() { return beautiful; } public String toString() { return "height = " + height + " name = " + name + " beautiful = " + beautiful; } private void print() { System.out.println("i am a private method"); } } class Boy extends Human { private boolean handsome; private int height; private String name; public Boy() { } public Boy(String name, int height, boolean handsome) { this.name = name; this.height = height; this.handsome = handsome; } public boolean isHandsome() { return handsome; } public String toString() { return "height = " + height + " name = " + name + " handsome = " + handsome; } private void print() { System.out.println("i am a private method"); } } public class Test { public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException { Scanner scanner = new Scanner(System.in); String input = scanner.nextLine(); Human human = null; String name = "leesf"; int height = 180; boolean handsome = true; boolean flag = false; if ("boy".equals(input)) { human = new Boy(name, height, handsome); flag = true; } else { human = new Girl("dyd", 168, true); } scanner.close(); Class<?> clazz = human.getClass(); Constructor<?> constructor = clazz.getConstructor(String.class, int.class, boolean.class); Human human1 = (Human) constructor.newInstance("leesf_dyd", 175, true); System.out.println(human1); Method method = null; if (flag) { method = clazz.getMethod("isHandsome"); } else { method = clazz.getMethod("isBeautiful"); } System.out.println(method.invoke(human)); Method method2 = clazz.getDeclaredMethod("print"); method2.setAccessible(true); method2.invoke(human); Field field = clazz.getDeclaredField("height"); System.out.println(human); field.setAccessible(true); field.set(human, 200); System.out.println(human); } }
輸入:boy
運行結果:
boy height = 175 name = leesf_dyd handsome = true true i am a private method height = 180 name = leesf handsome = true height = 200 name = leesf handsome = true
說明:反射可讓咱們建立一個類的實例、在類外部訪問類的私有方法、私有字段。反射真的很強大~
4、動態代理-反射的應用
動態建立代理而且動態處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器上。
下面是動態代理的例子
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Interface { void doSomething(); void doSomethingElse(String str); } class RealObject implements Interface { @Override public void doSomething() { System.out.println("doSomething"); } @Override public void doSomethingElse(String str) { System.out.println("doSomething else " + str); } } class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().startsWith("do")) { System.out.println("call do*** methods"); } method.invoke(proxied, args); return null; } } public class DynamicProxy { public static void main(String[] args) { RealObject proxied = new RealObject(); proxied.doSomething(); proxied.doSomethingElse("leesf"); Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class .getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(proxied)); proxy.doSomething(); proxy.doSomethingElse("leesf"); } }
運行結果:
doSomething doSomething else leesf call do*** methods doSomething call do*** methods doSomething else leesf
說明:能夠在invoke方法中進行過濾操做。過濾出以do開頭的方法進行轉發。
5、總結
RTTI和反射分析就到此爲止,RTTI和反射確實很強大,能夠幫助咱們幹不少事情,用對地方絕對威力無窮,謝謝各位園友的觀看~