<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          教妹學(xué) Java 第 39 講:反射

          共 9305字,需瀏覽 19分鐘

           ·

          2021-06-24 02:55

          “二哥,什么是反射呀?”三妹開(kāi)門(mén)見(jiàn)山地問(wèn)。

          “要想知道什么是反射,就需要先來(lái)了解什么是‘正射’。”我笑著對(duì)三妹說(shuō),“一般情況下,我們?cè)谑褂媚硞€(gè)類(lèi)之前已經(jīng)確定它到底是個(gè)什么類(lèi)了,拿到手就直接可以使用 new 關(guān)鍵字來(lái)調(diào)用構(gòu)造方法進(jìn)行初始化,之后使用這個(gè)類(lèi)的對(duì)象來(lái)進(jìn)行操作。”

          Writer writer = new Writer();
          writer.setName("沉默王二");

          像上面這個(gè)例子,就可以理解為“正射”。而反射就意味著一開(kāi)始我們不知道要初始化的類(lèi)到底是什么,也就沒(méi)法直接使用 new 關(guān)鍵字創(chuàng)建對(duì)象了。

          我們只知道這個(gè)類(lèi)的一些基本信息,就好像我們看電影的時(shí)候,為了抓住一個(gè)犯罪嫌疑人,警察就會(huì)問(wèn)一些目擊證人,根據(jù)這些證人提供的信息,找專(zhuān)家把犯罪嫌疑人的樣貌給畫(huà)出來(lái)——這個(gè)過(guò)程,就可以稱(chēng)之為反射

          嗯,不知道怎么回事,此刻我想起了古裝劇里城墻上貼的重金懸賞的畫(huà)面。

          Class clazz = Class.forName("com.itwanger.s39.Writer");
          Method method = clazz.getMethod("setName", String.class);
          Constructor constructor = clazz.getConstructor();
          Object object = constructor.newInstance();
          method.invoke(object,"沉默王二");

          像上面這個(gè)例子,就可以理解為“反射”。

          “反射的寫(xiě)法比正射復(fù)雜得多啊!”三妹感慨地說(shuō)。

          “是的,反射的成本是要比正射的高得多。”我說(shuō),“反射的缺點(diǎn)主要有兩個(gè)。”

          • 破壞封裝:由于反射允許訪(fǎng)問(wèn)私有字段和私有方法,所以可能會(huì)破壞封裝而導(dǎo)致安全問(wèn)題。
          • 性能開(kāi)銷(xiāo):由于反射涉及到動(dòng)態(tài)解析,因此無(wú)法執(zhí)行 Java 虛擬機(jī)優(yōu)化,再加上反射的寫(xiě)法的確要復(fù)雜得多,所以性能要比“正射”差很多,在一些性能敏感的程序中應(yīng)該避免使用反射。

          “那反射有哪些好處呢?”三妹問(wèn)。

          反射的主要應(yīng)用場(chǎng)景有:

          • 開(kāi)發(fā)通用框架:像 Spring,為了保持通用性,通過(guò)配置文件來(lái)加載不同的對(duì)象,調(diào)用不同的方法。
          • 動(dòng)態(tài)代理:在面向切面編程中,需要攔截特定的方法,就會(huì)選擇動(dòng)態(tài)代理的方式,而動(dòng)態(tài)代理的底層技術(shù)就是反射。
          • 注解:注解本身只是起到一個(gè)標(biāo)記符的作用,它需要利用發(fā)射機(jī)制,根據(jù)標(biāo)記符去執(zhí)行特定的行為。

          “好了,來(lái)看一下完整的例子吧。”我對(duì)三妹說(shuō)。

          Writer 類(lèi),有兩個(gè)字段,然后還有對(duì)應(yīng)的 getter/setter。

          public class Writer {
              private int age;
              private String name;

              public int getAge() {
                  return age;
              }

              public void setAge(int age) {
                  this.age = age;
              }

              public String getName() {
                  return name;
              }

              public void setName(String name) {
                  this.name = name;
              }
          }

          測(cè)試類(lèi):

          public class ReflectionDemo1 {
              public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
                  Writer writer = new Writer();
                  writer.setName("沉默王二");
                  System.out.println(writer.getName());

                  Class clazz = Class.forName("com.itwanger.s39.Writer");
                  Constructor constructor = clazz.getConstructor();
                  Object object = constructor.newInstance();

                  Method setNameMethod = clazz.getMethod("setName", String.class);
                  setNameMethod.invoke(object, "沉默王二");
                  Method getNameMethod = clazz.getMethod("getName");
                  System.out.println(getNameMethod.invoke(object));
              }
          }

          來(lái)看一下輸出結(jié)果:

          沉默王二
          沉默王二

          只不過(guò),反射的過(guò)程略顯曲折了一些。

          第一步,獲取反射類(lèi)的 Class 對(duì)象:

          Class clazz = Class.forName("com.itwanger.s39.Writer");

          第二步,通過(guò)  Class 對(duì)象獲取構(gòu)造方法 Constructor 對(duì)象:

          Constructor constructor = clazz.getConstructor();

          第三步,通過(guò) Constructor 對(duì)象初始化反射類(lèi)對(duì)象:

          Object object = constructor.newInstance();

          第四步,獲取要調(diào)用的方法的 Method 對(duì)象:

          Method setNameMethod = clazz.getMethod("setName", String.class);
          Method getNameMethod = clazz.getMethod("getName");

          第五步,通過(guò) invoke() 方法執(zhí)行:

          setNameMethod.invoke(object, "沉默王二");
          getNameMethod.invoke(object)

          “三妹,你看,經(jīng)過(guò)這五個(gè)步驟,基本上就掌握了反射的使用方法。”我說(shuō)。

          “好像反射也沒(méi)什么復(fù)雜的啊!”三妹說(shuō)。

          我先對(duì)三妹點(diǎn)點(diǎn)頭,然后說(shuō):“是的,掌握反射的基本使用方法確實(shí)不難,但要理解整個(gè)反射機(jī)制還是需要花一點(diǎn)時(shí)間去了解一下 Java 虛擬機(jī)的類(lèi)加載機(jī)制的。”

          要想使用反射,首先需要獲得反射類(lèi)的 Class 對(duì)象,每一個(gè)類(lèi),不管它最終生成了多少個(gè)對(duì)象,這些對(duì)象只會(huì)對(duì)應(yīng)一個(gè) Class 對(duì)象,這個(gè) Class 對(duì)象是由 Java 虛擬機(jī)生成的,由它來(lái)獲悉整個(gè)類(lèi)的結(jié)構(gòu)信息。

          也就是說(shuō),java.lang.Class 是所有反射 API 的入口。

          而方法的反射調(diào)用,最終是由 Method 對(duì)象的 invoke() 方法完成的,來(lái)看一下源碼(JDK 8 環(huán)境下)。

          @CallerSensitive
          public Object invoke(Object obj, Object... args)
                  throws IllegalAccessException, IllegalArgumentException,
                  InvocationTargetException
          {
              if (!override) {
                  if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                      Class<?> caller = Reflection.getCallerClass();
                      checkAccess(caller, clazz, obj, modifiers);
                  }
              }
              MethodAccessor ma = methodAccessor;             // read volatile
              if (ma == null) {
                  ma = acquireMethodAccessor();
              }
              return ma.invoke(obj, args);
          }

          兩個(gè)嵌套的 if 語(yǔ)句是用來(lái)進(jìn)行權(quán)限檢查的。

          invoke() 方法實(shí)際上是委派給 MethodAccessor 接口來(lái)完成的。

          MethodAccessor 接口有三個(gè)實(shí)現(xiàn)類(lèi),其中的 MethodAccessorImpl 是一個(gè)抽象類(lèi),另外兩個(gè)具體的實(shí)現(xiàn)類(lèi)繼承了這個(gè)抽象類(lèi)。

          • NativeMethodAccessorImpl:通過(guò)本地方法來(lái)實(shí)現(xiàn)反射調(diào)用;
          • DelegatingMethodAccessorImpl:通過(guò)委派模式來(lái)實(shí)現(xiàn)反射調(diào)用;

          通過(guò) debug 的方式進(jìn)入 invoke() 方法后,可以看到第一次反射調(diào)用會(huì)生成一個(gè)委派實(shí)現(xiàn) DelegatingMethodAccessorImpl,它在生成的時(shí)候會(huì)傳遞一個(gè)本地實(shí)現(xiàn) NativeMethodAccessorImpl。

          也就是說(shuō),invoke() 方法在執(zhí)行的時(shí)候,會(huì)先調(diào)用 DelegatingMethodAccessorImpl,然后調(diào)用 NativeMethodAccessorImpl,最后再調(diào)用實(shí)際的方法。

          “為什么不直接調(diào)用本地實(shí)現(xiàn)呢?”三妹問(wèn)。

          “之所以采用委派實(shí)現(xiàn),是為了能夠在本地實(shí)現(xiàn)和動(dòng)態(tài)實(shí)現(xiàn)之間切換。動(dòng)態(tài)實(shí)現(xiàn)是另外一種反射調(diào)用機(jī)制,它是通過(guò)生成字節(jié)碼的形式來(lái)實(shí)現(xiàn)的。如果反射調(diào)用的次數(shù)比較多,動(dòng)態(tài)實(shí)現(xiàn)的效率就會(huì)更高,因?yàn)楸镜貙?shí)現(xiàn)需要經(jīng)過(guò) Java 到 C/C++ 再到 Java 之間的切換過(guò)程,而動(dòng)態(tài)實(shí)現(xiàn)不需要;但如果反射調(diào)用的次數(shù)比較少,反而本地實(shí)現(xiàn)更快一些。”我說(shuō)。

          “那臨界點(diǎn)是多少呢?”三妹問(wèn)。

          “默認(rèn)是 15 次。”我說(shuō),“可以通過(guò) -Dsun.reflect.inflationThreshold 參數(shù)類(lèi)調(diào)整。”

          來(lái)看下面這個(gè)例子。

          Method setAgeMethod = clazz.getMethod("setAge"int.class);
          for (int i = 0;i < 20; i++) {
              setAgeMethod.invoke(object, 18);
          }

          invoke() 方法處加斷點(diǎn)進(jìn)入 debug 模式,當(dāng) i = 15 的時(shí)候,也就是第 16 次執(zhí)行的時(shí)候,會(huì)進(jìn)入到 if 條件分支中,改變 DelegatingMethodAccessorImpl 的委派模式 delegate 為 (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(),而之前的委派模式 delegate 為 NativeMethodAccessorImpl。

          “這下明白了吧?三妹。”我說(shuō),“接下來(lái),我們?cè)賮?lái)熟悉一下反射當(dāng)中常用的 API。”

          1)獲取反射類(lèi)的 Class 對(duì)象

          Class.forName(),參數(shù)為反射類(lèi)的完全限定名。

          Class c1 = Class.forName("com.itwanger.s39.ReflectionDemo3");
          System.out.println(c1.getCanonicalName());

          Class c2 = Class.forName("[D");
          System.out.println(c2.getCanonicalName());

          Class c3 = Class.forName("[[Ljava.lang.String;");
          System.out.println(c3.getCanonicalName());

          來(lái)看一下輸出結(jié)果:

          com.itwanger.s39.ReflectionDemo3
          double[]
          java.lang.String[][]

          類(lèi)名 + .class,只適合在編譯前就知道操作的 Class。。

          Class c1 = ReflectionDemo3.class;
          System.out.println(c1.getCanonicalName());

          Class c2 = String.class;
          System.out.println(c2.getCanonicalName());

          Class c3 = int[][][].class;
          System.out.println(c3.getCanonicalName());

          來(lái)看一下輸出結(jié)果:

          com.itwanger.s39.ReflectionDemo3
          java.lang.String
          int[][][]

          2)創(chuàng)建反射類(lèi)的對(duì)象

          通過(guò)反射來(lái)創(chuàng)建對(duì)象的方式有兩種:

          • 用 Class 對(duì)象的 newInstance() 方法。
          • 用 Constructor 對(duì)象的 newInstance() 方法。
          Class c1 = Writer.class;
          Writer writer = (Writer) c1.newInstance();

          Class c2 = Class.forName("com.itwanger.s39.Writer");
          Constructor constructor = c2.getConstructor();
          Object object = constructor.newInstance();

          3)獲取構(gòu)造方法

          Class 對(duì)象提供了以下方法來(lái)獲取構(gòu)造方法 Constructor 對(duì)象:

          • getConstructor():返回反射類(lèi)的特定 public 構(gòu)造方法,可以傳遞參數(shù),參數(shù)為構(gòu)造方法參數(shù)對(duì)應(yīng) Class 對(duì)象;缺省的時(shí)候返回默認(rèn)構(gòu)造方法。
          • getDeclaredConstructor():返回反射類(lèi)的特定構(gòu)造方法,不限定于 public 的。
          • getConstructors():返回類(lèi)的所有 public 構(gòu)造方法。
          • getDeclaredConstructors():返回類(lèi)的所有構(gòu)造方法,不限定于 public 的。
          Class c2 = Class.forName("com.itwanger.s39.Writer");
          Constructor constructor = c2.getConstructor();

          Constructor[] constructors1 = String.class.getDeclaredConstructors();
          for (Constructor c : constructors1) {
              System.out.println(c);
          }

          4)獲取字段

          大體上和獲取構(gòu)造方法類(lèi)似,把關(guān)鍵字 Constructor 換成 Field 即可。

          Method setNameMethod = clazz.getMethod("setName", String.class);
          Method getNameMethod = clazz.getMethod("getName");

          5)獲取方法

          大體上和獲取構(gòu)造方法類(lèi)似,把關(guān)鍵字 Constructor 換成 Method 即可。

          Method[] methods1 = System.class.getDeclaredMethods();
          Method[] methods2 = System.class.getMethods();

          “注意,三妹,如果你想反射訪(fǎng)問(wèn)私有字段和(構(gòu)造)方法的話(huà),需要使用 Constructor/Field/Method.setAccessible(true) 來(lái)繞開(kāi) Java 語(yǔ)言的訪(fǎng)問(wèn)限制。”我說(shuō)。

          “好的,二哥。還有資料可以參考嗎?”三妹問(wèn)。

          “有的,有兩篇文章寫(xiě)得非常不錯(cuò),你在學(xué)習(xí)反射的時(shí)候可以作為參考。”我說(shuō)。

          第一篇:深入理解 Java 反射和動(dòng)態(tài)代理

          鏈接:https://dunwu.github.io/javacore/basics/java-reflection.html#_1-%E5%8F%8D%E5%B0%84%E7%AE%80%E4%BB%8B

          第二篇:大白話(huà)說(shuō)Java反射:入門(mén)、使用、原理:

          鏈接:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html


          不知不覺(jué),《教妹學(xué)Java》專(zhuān)欄已經(jīng)更新到第 39 講了,少說(shuō)應(yīng)該有 5 萬(wàn)字了吧?不由得往前翻了翻,看到之前有篇里面有個(gè) CS 小姐姐的留言,好溫暖!

          來(lái)來(lái)來(lái),有沒(méi)有人要高度評(píng)價(jià)一下這個(gè)專(zhuān)欄?作為第 40 講更新的動(dòng)力?

          ??

          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  激情五月色婷婷 | 九七人妻| 91九九热| 黑人大鸡巴视频 | 一区二区久久在线 |