<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【轉】-Java反射

      Java 反射由淺入深 | 進階必備

      原文鏈接

      本博文主要記錄我學習 Java 反射(reflect)的一點心得,在了解反射之前,你應該先了解 Java 中的 Class 類,如果你不是很了解,可以先簡單了解下。

      一、Java 反射機制

      參考了許多博文,總結了以下個人觀點,若有不妥還望指正:

      Java 反射機制在程序運行時,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性。這種 動態的獲取信息 以及 動態調用對象的方法 的功能稱為 java 的反射機制

      反射機制很重要的一點就是“運行時”,其使得我們可以在程序運行時加載、探索以及使用編譯期間完全未知的 .class 文件。換句話說,Java 程序可以加載一個運行時才得知名稱的 .class 文件,然后獲悉其完整構造,并生成其對象實體、或對其 fields(變量)設值、或調用其 methods(方法)。

      不知道上面的理論你能否明白,反正剛接觸反射時我一臉懵比,后來寫了幾個例子之后:哦~~原來是這個意思!

      若暫時不明白理論沒關系,先往下看例子,之后再回來看相信你就能明白了。

      二、使用反射獲取類的信息

      為使得測試結果更加明顯,我首先定義了一個 FatherClass 類(默認繼承自 Object 類),然后定義一個繼承自 FatherClass 類的 SonClass 類,如下所示。可以看到測試類中變量以及方法的訪問權限不是很規范,是為了更明顯得查看測試結果而故意設置的,實際項目中不提倡這么寫。

      FatherClass.java

      public class FatherClass {
          public String mFatherName;
          public int mFatherAge;
      
          public void printFatherMsg(){}
      }
      

      SonClass.java

      public class SonClass extends FatherClass{
      
          private String mSonName;
          protected int mSonAge;
          public String mSonBirthday;
      
          public void printSonMsg(){
              System.out.println("Son Msg - name : "
                      + mSonName + "; age : " + mSonAge);
          }
      
          private void setSonName(String name){
              mSonName = name;
          }
      
          private void setSonAge(int age){
              mSonAge = age;
          }
      
          private int getSonAge(){
              return mSonAge;
          }
      
          private String getSonName(){
              return mSonName;
          }
      }
      

      1. 獲取類的所有變量信息

      /**
       * 通過反射獲取類的所有變量
       */
      private static void printFields(){
          //1.獲取并輸出類的名稱
          Class mClass = SonClass.class;
          System.out.println("類的名稱:" + mClass.getName());
      
          //2.1 獲取所有 public 訪問權限的變量
          // 包括本類聲明的和從父類繼承的
          Field[] fields = mClass.getFields();
      
          //2.2 獲取所有本類聲明的變量(不問訪問權限)
          //Field[] fields = mClass.getDeclaredFields();
      
          //3. 遍歷變量并輸出變量信息
          for (Field field :
                  fields) {
              //獲取訪問權限并輸出
              int modifiers = field.getModifiers();
              System.out.print(Modifier.toString(modifiers) + " ");
              //輸出變量的類型及變量名
              System.out.println(field.getType().getName()
                       + " " + field.getName());
          }
      }
      

      以上代碼注釋很詳細,就不再解釋了。需要注意的是注釋中 2.1 的 getFields() 與 2.2的 getDeclaredFields() 之間的區別,下面分別看一下兩種情況下的輸出。看之前強調一下:
      SonClass extends FatherClass extends Object

      • 調用 getFields() 方法,輸出 SonClass 類以及其所繼承的父類( 包括 FatherClassObject ) 的 public 方法。注:Object 類中沒有成員變量,所以沒有輸出。

          類的名稱:obj.SonClass
          public java.lang.String mSonBirthday
          public java.lang.String mFatherName
          public int mFatherAge
        
      • 調用 getDeclaredFields() , 輸出 SonClass 類的所有成員變量,不問訪問權限。

          類的名稱:obj.SonClass
          private java.lang.String mSonName
          protected int mSonAge
          public java.lang.String mSonBirthday
        

      2. 獲取類的所有方法信息

      /**
       * 通過反射獲取類的所有方法
       */
      private static void printMethods(){
          //1.獲取并輸出類的名稱
          Class mClass = SonClass.class;
          System.out.println("類的名稱:" + mClass.getName());
      
          //2.1 獲取所有 public 訪問權限的方法
          //包括自己聲明和從父類繼承的
          Method[] mMethods = mClass.getMethods();
      
          //2.2 獲取所有本類的的方法(不問訪問權限)
          //Method[] mMethods = mClass.getDeclaredMethods();
      
          //3.遍歷所有方法
          for (Method method :
                  mMethods) {
              //獲取并輸出方法的訪問權限(Modifiers:修飾符)
              int modifiers = method.getModifiers();
              System.out.print(Modifier.toString(modifiers) + " ");
              //獲取并輸出方法的返回值類型
              Class returnType = method.getReturnType();
              System.out.print(returnType.getName() + " "
                      + method.getName() + "( ");
              //獲取并輸出方法的所有參數
              Parameter[] parameters = method.getParameters();
              for (Parameter parameter:
                   parameters) {
                  System.out.print(parameter.getType().getName()
                          + " " + parameter.getName() + ",");
              }
              //獲取并輸出方法拋出的異常
              Class[] exceptionTypes = method.getExceptionTypes();
              if (exceptionTypes.length == 0){
                  System.out.println(" )");
              }
              else {
                  for (Class c : exceptionTypes) {
                      System.out.println(" ) throws "
                              + c.getName());
                  }
              }
          }
      }
      

      同獲取變量信息一樣,需要注意注釋中 2.1 與 2.2 的區別,下面看一下打印輸出:

      • 調用 getMethods() 方法
        獲取 SonClass 類所有 public 訪問權限的方法,包括從父類繼承的。打印信息中,printSonMsg() 方法來自 SonClass 類, printFatherMsg() 來自 FatherClass 類,其余方法來自 Object 類。

          類的名稱:obj.SonClass
          public void printSonMsg(  )
          public void printFatherMsg(  )
          public final void wait(  ) throws java.lang.InterruptedException
          public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
          public final native void wait( long arg0, ) throws java.lang.InterruptedException
          public boolean equals( java.lang.Object arg0, )
          public java.lang.String toString(  )
          public native int hashCode(  )
          public final native java.lang.Class getClass(  )
          public final native void notify(  )
          public final native void notifyAll(  )
        
      • 調用 getDeclaredMethods() 方法

        打印信息中,輸出的都是 SonClass 類的方法,不問訪問權限。

          類的名稱:obj.SonClass
          private int getSonAge(  )
          private void setSonAge( int arg0, )
          public void printSonMsg(  )
          private void setSonName( java.lang.String arg0, )
          private java.lang.String getSonName(  )
        

      三、訪問或操作類的私有變量和方法

      在上面,我們成功獲取了類的變量和方法信息,驗證了在運行時 動態的獲取信息 的觀點。那么,僅僅是獲取信息嗎?我們接著往后看。

      都知道,對象是無法訪問或操作類的私有變量和方法的,但是,通過反射,我們就可以做到。沒錯,反射可以做到!下面,讓我們一起探討如何利用反射訪問 類對象的私有方法 以及修改 私有變量或常量

      老規矩,先上測試類。

      注:

      1. 請注意看測試類中變量和方法的修飾符(訪問權限);
      2. 測試類僅供測試,不提倡實際開發時這么寫 : )

      TestClass.java

      public class TestClass {
      
          private String MSG = "Original";
      
          private void privateMethod(String head , int tail){
              System.out.print(head + tail);
          }
      
          public String getMsg(){
              return MSG;
          }
      }
      

      3.1 訪問私有方法

      以訪問 TestClass 類中的私有方法 privateMethod(...) 為例,方法加參數是為了考慮最全的情況,很貼心有木有?先貼代碼,看注釋,最后我會重點解釋部分代碼。

      /**
       * 訪問對象的私有方法
       * 為簡潔代碼,在方法上拋出總的異常,實際開發別這樣
       */
      private static void getPrivateMethod() throws Exception{
          //1. 獲取 Class 類實例
          TestClass testClass = new TestClass();
          Class mClass = testClass.getClass();
      
          //2. 獲取私有方法
          //第一個參數為要獲取的私有方法的名稱
          //第二個為要獲取方法的參數的類型,參數為 Class...,沒有參數就是null
          //方法參數也可這么寫 :new Class[]{String.class , int.class}
          Method privateMethod =
                  mClass.getDeclaredMethod("privateMethod", String.class, int.class);
      
          //3. 開始操作方法
          if (privateMethod != null) {
              //獲取私有方法的訪問權
              //只是獲取訪問權,并不是修改實際權限
              privateMethod.setAccessible(true);
      
              //使用 invoke 反射調用私有方法
              //privateMethod 是獲取到的私有方法
              //testClass 要操作的對象
              //后面兩個參數傳實參
              privateMethod.invoke(testClass, "Java Reflect ", 666);
          }
      }
      

      需要注意的是,第3步中的 setAccessible(true) 方法,是獲取私有方法的訪問權限,如果不加會報異常 IllegalAccessException,因為當前方法訪問權限是“private”的,如下:

      java.lang.IllegalAccessException: Class MainClass can not access a member of class obj.TestClass with modifiers "private"
      

      正常運行后,打印如下,調用私有方法成功:

      Java Reflect 666
      

      3.2 修改私有變量

      以修改 TestClass 類中的私有變量 MSG 為例,其初始值為 "Original" ,我們要修改為 "Modified"。老規矩,先上代碼看注釋。

      /**
       * 修改對象私有變量的值
       * 為簡潔代碼,在方法上拋出總的異常
       */
      private static void modifyPrivateFiled() throws Exception {
          //1. 獲取 Class 類實例
          TestClass testClass = new TestClass();
          Class mClass = testClass.getClass();
      
          //2. 獲取私有變量
          Field privateField = mClass.getDeclaredField("MSG");
      
          //3. 操作私有變量
          if (privateField != null) {
              //獲取私有變量的訪問權
              privateField.setAccessible(true);
      
              //修改私有變量,并輸出以測試
              System.out.println("Before Modify:MSG = " + testClass.getMsg());
      
              //調用 set(object , value) 修改變量的值
              //privateField 是獲取到的私有變量
              //testClass 要操作的對象
              //"Modified" 為要修改成的值
              privateField.set(testClass, "Modified");
              System.out.println("After Modify:MSG = " + testClass.getMsg());
          }
      }
      

      此處代碼和訪問私有方法的邏輯差不多,就不再贅述,從輸出信息看出 修改私有變量 成功:

      Before Modify:MSG = Original
      After Modify:MSG = Modified
      

      3.3 修改私有常量

      在 3.2 中,我們介紹了如何修改私有 變量,現在來說說如何修改私有 常量

      01. 真的能修改嗎?

      常量是指使用 final 修飾符修飾的成員屬性,與變量的區別就在于有無 final 關鍵字修飾。在說之前,先補充一個知識點。

      Java 虛擬機(JVM)在編譯 .java 文件得到 .class 文件時,會優化我們的代碼以提升效率。其中一個優化就是:JVM 在編譯階段會把引用常量的代碼替換成具體的常量值,如下所示(部分代碼)。

      編譯前的 .java 文件:

      //注意是 String  類型的值
      private final String FINAL_VALUE = "hello";
      
      if(FINAL_VALUE.equals("world")){
          //do something
      }
      

      編譯后得到的 .class 文件(當然,編譯后是沒有注釋的):

      private final String FINAL_VALUE = "hello";
      //替換為"hello"
      if("hello".equals("world")){
          //do something
      }
      

      但是,并不是所有常量都會優化。經測試對于 intlongboolean 以及 String 這些基本類型 JVM 會優化,而對于 IntegerLongBoolean 這種包裝類型,或者其他諸如 DateObject 類型則不會被優化。

      總結來說:對于基本類型的靜態常量,JVM 在編譯階段會把引用此常量的代碼替換成具體的常量值

      這么說來,在實際開發中,如果我們想修改某個類的常量值,恰好那個常量是基本類型的,豈不是無能為力了?反正我個人認為除非修改源碼,否則真沒辦法!

      這里所謂的無能為力是指:我們在程序運行時刻依然可以使用反射修改常量的值(后面會代碼驗證),但是 JVM 在編譯階段得到的 .class 文件已經將常量優化為具體的值,在運行階段就直接使用具體的值了,所以即使修改了常量的值也已經毫無意義了

      下面我們驗證這一點,在測試類 TestClass 類中添加如下代碼:

      //String 會被 JVM 優化
      private final String FINAL_VALUE = "FINAL";
      
      public String getFinalValue(){
          //劇透,會被優化為: return "FINAL" ,拭目以待吧
          return FINAL_VALUE;
      }
      

      接下來,是修改常量的值,先上代碼,請仔細看注釋:

      /**
       * 修改對象私有常量的值
       * 為簡潔代碼,在方法上拋出總的異常,實際開發別這樣
       */
      private static void modifyFinalFiled() throws Exception {
          //1. 獲取 Class 類實例
          TestClass testClass = new TestClass();
          Class mClass = testClass.getClass();
      
          //2. 獲取私有常量
          Field finalField = mClass.getDeclaredField("FINAL_VALUE");
      
          //3. 修改常量的值
          if (finalField != null) {
      
              //獲取私有常量的訪問權
              finalField.setAccessible(true);
      
              //調用 finalField 的 getter 方法
              //輸出 FINAL_VALUE 修改前的值
              System.out.println("Before Modify:FINAL_VALUE = "
                      + finalField.get(testClass));
      
              //修改私有常量
              finalField.set(testClass, "Modified");
      
              //調用 finalField 的 getter 方法
              //輸出 FINAL_VALUE 修改后的值
              System.out.println("After Modify:FINAL_VALUE = "
                      + finalField.get(testClass));
      
              //使用對象調用類的 getter 方法
              //獲取值并輸出
              System.out.println("Actually :FINAL_VALUE = "
                      + testClass.getFinalValue());
          }
      }
      

      上面的代碼不解釋了,注釋巨詳細有木有!特別注意一下第3步的注釋,然后來看看輸出,已經迫不及待了,擦亮雙眼:

      Before Modify:FINAL_VALUE = FINAL
      After Modify:FINAL_VALUE = Modified
      Actually :FINAL_VALUE = FINAL
      

      結果出來了:

      第一句打印修改前 FINAL_VALUE 的值,沒有異議;

      第二句打印修改后常量的值,說明FINAL_VALUE確實通過反射修改了;

      第三句打印通過 getFinalValue() 方法獲取的 FINAL_VALUE 的值,但還是初始值,導致修改無效!

      這結果你覺得可信嗎?什么,你還不信?問我怎么知道 JVM 編譯后會優化代碼?那要不這樣吧,一起來看看 TestClass.java 文件編譯后得到的 TestClass.class 文件。為避免說代碼是我自己手寫的,我決定不粘貼代碼,直接截圖:

      TestClass.class 文件TestClass.class 文件

      看到了吧,有圖有真相,getFinalValue() 方法直接 return "FINAL"!同時也說明了,程序運行時是根據編譯后的 .class 來執行的

      順便提一下,如果你有時間,可以換幾個數據類型試試,正如上面說的,有些數據類型是不會優化的。你可以修改數據類型后,根據我的思路試試,看輸出覺得不靠譜就直接看 .classs 文件,一眼就能看出來哪些數據類型優化了 ,哪些沒有優化。下面說下一個知識點。

      02. 想辦法也要修改!

      不能修改,這你能忍?別著急,不知你發現沒,剛才的常量都是在聲明時就直接賦值了。你可能會疑惑,常量不都是在聲明時賦值嗎?不賦值不報錯?當然不是啦。

      方法一

      事實上,Java 允許我們聲明常量時不賦值,但必須在構造函數中賦值。你可能會問我為什么要說這個,這就解釋:

      我們修改一下 TestClass 類,在聲明常量時不賦值,然后添加構造函數并為其賦值,大概看一下修改后的代碼(部分代碼 ):

      public class TestClass {
      
          //......
          private final String FINAL_VALUE;
      
          //構造函數內為常量賦值 
          public TestClass(){
              this.FINAL_VALUE = "FINAL";
          }
          //......
      }
      

      現在,我們再調用上面貼出的修改常量的方法,發現輸出是這樣的:

      Before Modify:FINAL_VALUE = FINAL
      After Modify:FINAL_VALUE = Modified
      Actually :FINAL_VALUE = Modified
      

      納尼,最后一句輸出修改后的值了?對,修改成功了!想知道為啥,還得看編譯后的 TestClass.class 文件的貼圖,圖中有標注。

      TestClass.class 文件TestClass.class 文件

      解釋一下:我們將賦值放在構造函數中,構造函數是我們運行時 new 對象才會調用的,所以就不會像之前直接為常量賦值那樣,在編譯階段將 getFinalValue() 方法優化為返回常量值,而是指向 FINAL_VALUE ,這樣我們在運行階段通過反射修改敞亮的值就有意義啦。但是,看得出來,程序還是有優化的,將構造函數中的賦值語句優化了。再想想那句 程序運行時是根據編譯后的 .class 來執行的 ,相信你一定明白為什么這么輸出了!

      方法二

      請你務必將上面捋清楚了再往下看。接下來再說一種改法,不使用構造函數,也可以成功修改常量的值,但原理上都一樣。去掉構造函數,將聲明常量的語句改為使用三目表達式賦值:

      private final String FINAL_VALUE
              = null == null ? "FINAL" : null;復制代碼
      

      其實,上述代碼等價于直接為 FINAL_VALUE 賦值 "FINAL",但是他就是可以!至于為什么,你這么想:null == null ? "FINAL" : null 是在運行時刻計算的,在編譯時刻不會計算,也就不會被優化,所以你懂得。

      總結來說,不管使用構造函數還是三目表達式,根本上都是避免在編譯時刻被優化,這樣我們通過反射修改常量之后才有意義!好了,這一小部分到此結束!

      最后的強調

      必須提醒你的是,無論直接為常量賦值通過構造函數為常量賦值 還是 使用三目運算符,實際上我們都能通過反射成功修改常量的值。而我在上面說的修改"成功"與否是指:我們在程序運行階段通過反射肯定能修改常量值,但是實際執行優化后的 .class 文件時,修改的后值真的起到作用了嗎?換句話說,就是編譯時是否將常量替換為具體的值了?如果替換了,再怎么修改常量的值都不會影響最終的結果了,不是嗎?

      其實,你可以直接這么想:反射肯定能修改常量的值,但修改后的值是否有意義

      03. 到底能不能改?

      到底能不能改?也就是說反射修改后到底有沒有意義?

      如果你上面看明白了,答案就簡單了。俗話說“一千句話不如一張圖”,下面允許我用不太規范的流程圖直接表達答案哈。

      注:圖中"沒法修改"可以理解為"能修改值但沒有意義";"可以修改"是指"能修改值且有意義"。

      判斷能不能改判斷能不能改

      四、總結

      好了,本次記錄就到這兒了,突然不知不覺發現寫了好多,感謝耐心聽我叨逼完。我想這篇博客如果你認真的看完,肯定會有收獲的!最后,因為內容較多,知識點較多,如果文中有任何錯誤或欠妥的地方,還望指正。歡迎留言交流!

      posted @ 2024-07-09 17:58  booleandev  閱讀(50)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲精品综合网二三区| 2019亚洲午夜无码天堂| 日本高清一区免费中文视频| 亚洲欧美一区二区三区在线| 无码专区人妻系列日韩精品| 蜜桃av无码免费看永久| 免费观看全黄做爰大片| 无码小电影在线观看网站免费| 精品国产大片中文字幕| 国产精品女人毛片在线看| 爆乳日韩尤物无码一区| 老司机亚洲精品一区二区| 国内精品无码一区二区三区| 中文字幕日韩有码av| 五月丁香六月狠狠爱综合| 人妻中文字幕精品系列| 蜜臀av久久国产午夜| 午夜福利国产盗摄久久性| 国内精品久久久久影院网站| 大香伊蕉在人线国产最新2005| 真人在线射美女视频在线观看| 国产精品丝袜一区二区三区| 好爽毛片一区二区三区四| 国产无套内射普通话对白| 夜爽8888视频在线观看| 亚洲综合色区另类av| 精品一区二区三区在线播放视频 | 夜夜嗨久久人成在日日夜夜| 精品视频不卡免费观看| 国产成人午夜福利院| 亚洲国产精品线观看不卡| 久久婷婷国产精品香蕉| 非会员区试看120秒6次| 在线观看国产精品日韩av| 亚洲www永久成人网站| 国色天香成人一区二区| 日韩女同一区二区三区久久 | 99re视频在线| 久久精品熟女亚洲av艳妇| 中文字幕精品亚洲字幕成| 大屁股国产白浆一二区|