真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Java中怎么重寫方法

Java 中怎么重寫方法,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。

站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到西充網(wǎng)站設(shè)計(jì)與西充網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、空間域名、網(wǎng)站空間、企業(yè)郵箱。業(yè)務(wù)覆蓋西充地區(qū)。

簡(jiǎn)單還原一下問(wèn)題, 我們有一個(gè)類SuperClass

public class SuperClass {      private int mSuperX;      public SuperClass() {         setX(99);     }      public void setX(int x) {         mSuperX = x;     } }

現(xiàn)在我們想隨時(shí)知道mSuperX是什么值, 不用反射, 因?yàn)楦割悘牟恢苯有薷膍SuperX的值, 總是通過(guò)setX來(lái)改, 那么最簡(jiǎn)單的方法就是繼承SuperClass, 重寫setX方法, 監(jiān)聽它的改變就好.下面是我們的子類SubClass:

public class SubClass extends SuperClass {      private int mSubX = 1;      public SubClass() {}      @Override     public void setX(int x) {         super.setX(x);         mSubX = x;         System.out.println("SubX is assigned " + x);     }      public void printX() {         System.out.println("SubX = " + mSubX);     } }

我使用mSubX來(lái)跟蹤mSuperX

因?yàn)樵赩iewGroup中, clipToPadding默認(rèn)值是true(為了簡(jiǎn)化問(wèn)題, 把它當(dāng)成boolean, 實(shí)際并不是),  而ViewGroup初始化有可能不調(diào)用setClipToPadding, 此時(shí)是默認(rèn)值, 為了模擬這種情況, 將mSubX初始化為1.

***在main里調(diào)用:

public class Main {     public static void main(String[] args) {         SubClass sc = new SubClass();         sc.printX();     } }

很多人, 包括我, 認(rèn)為終端輸出的結(jié)果應(yīng)該是:

SubX is assigned 99
SubX = 99

然而真正運(yùn)行后輸出的是:

SubX is assigned 99 SubX = 1

實(shí)際分析

要想知道發(fā)生了什么, 最簡(jiǎn)單的方法就是看看到底程序到底是怎么執(zhí)行的, 比如單步調(diào)試, 或者直接一點(diǎn), 看看Java字節(jié)碼.

下面是Main的字節(jié)碼

Compiled from "Main.java" public class bugme.Main {   ......   public static void main(java.lang.String[]);     Code:        0: new           #2                  // class bugme/SubClass        3: dup                  4: invokespecial #3                  // Method bugme/SubClass."":()V        ......  }

這是直接用javap反編譯.class文件得到的. 雖說(shuō)同樣是Java寫的, 用apktool反編譯APK文件(其中的dex文件)得到的smali代碼和Java Bytecode明顯長(zhǎng)得不一樣.

字節(jié)碼乍一看怪怪的, 只要知道它隱含了一個(gè)棧和局部變量表就好懂了.

這段代碼首先new一個(gè)SubClass實(shí)例, 把引用入棧, dup是把棧頂復(fù)制一份入棧, invokespecial #3將棧頂元素出棧并調(diào)用它的某個(gè)方法, 這個(gè)方法具體是什么要看常量池里第3個(gè)條目是什么, 但是javap生成的字節(jié)碼直接給我們寫在旁邊了, 即SubClass..

接下來(lái)看SubClass.,

public class bugme.SubClass extends bugme.SuperClass {   public bugme.SubClass();     Code:        0: aload_0              1: invokespecial #1                  // Method bugme/SuperClass."":()V        ......

這里面并沒(méi)有方法叫, 是因?yàn)閖avap為了方便我們閱讀, 直接把它改成類名bugme.SubClass, 順便一提, bugme是包名. 方法并非通常意義上的構(gòu)造方法, 這是Java幫我們合成的一個(gè)方法, 里面的指令會(huì)幫我們按順序進(jìn)行普通成員變量初始化, 也包括初始化塊里的代碼, 注意是按順序執(zhí)行, 這些都執(zhí)行完了之后才輪到構(gòu)造方法里代碼生成的指令執(zhí)行. 這里aload_0將局部變量表中下標(biāo)為0的元素入棧, 其實(shí)就是Java中的this, 結(jié)合invokespecial #1, 是在調(diào)用父類的構(gòu)造函數(shù), 也就是我們常見的super().

所以我們?cè)倏?code>SuperClass.

public class bugme.SuperClass {   public bugme.SuperClass();     Code:        0: aload_0              1: invokespecial #1                  // Method java/lang/Object."":()V        4: aload_0              5: bipush        99        7: invokevirtual #2                  // Method setX:(I)V       10: return     ......     }

同樣是先調(diào)了父類Object的構(gòu)造方法, 然后再將this, 99入棧, invokevirtual #2旁邊注釋了是調(diào)用setX, 參數(shù)分別是this99也就是this.setX(99), 然而這個(gè)方法被重寫了, 調(diào)用的是子類的方法, 所以我們?cè)倏?code>SubClass.setX:

public class bugme.SubClass extends bugme.SuperClass {   ......   public void setX(int);     Code:        0: aload_0              1: iload_1              2: invokespecial #3                  // Method bugme/SuperClass.setX:(I)V        ...... }

這里將局部變量表前兩個(gè)元素都入棧, ***個(gè)是this, 第二個(gè)是括號(hào)里的參數(shù), 也就是99, invokespecial #3調(diào)用的是父類的setX, 也就是我們代碼中寫的super.setX(int)

SuperClass.setX就很簡(jiǎn)單了:

public class bugme.SuperClass {   ......       public void setX(int);     Code:        0: aload_0              1: iload_1              2: putfield      #3                  // Field mSuperX:I        5: return        }

這里先把this入棧, 再把參數(shù)入棧, putfield #3使得前兩個(gè)入棧的元素全部出棧, 而成員mSuperX被賦值, 這四條指令只對(duì)應(yīng)代碼里的一句this.mSuperX = x;

接下來(lái)控制流回到子類的setX:

public class bugme.SubClass extends bugme.SuperClass {   ......   public void setX(int);     Code:        0: aload_0              1: iload_1              2: invokespecial #3                  // Method bugme/SuperClass.setX:(I)V      ->5: aload_0                           // 即將執(zhí)行這句        6: iload_1              7: putfield      #2                  // Field mSubX:I       10: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;       13: new           #5                  // class java/lang/StringBuilder       16: dup                 17: invokespecial #6                  // Method java/lang/StringBuilder."":()V       20: ldc           #7                  // String SubX is assigned       22: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;       25: iload_1             26: invokevirtual #9                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;       29: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;       32: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V       35: return }

從5處開始繼續(xù)分析, 5,6,7將參數(shù)的值賦給mSubX, 此時(shí)mSubX是99了, 下面那一堆則是在執(zhí)行System.out.println("SubX is assigned " + x);并返回, 還可以看到Java自動(dòng)幫我們使用StringBuilder優(yōu)化字符串拼接, 就不分析了.

說(shuō)了這么多, 我們的代碼才剛把下面箭頭指著的這句執(zhí)行完:

public class bugme.SubClass extends bugme.SuperClass {   public bugme.SubClass();     Code:        0: aload_0            ->1: invokespecial #1                  // Method bugme/SuperClass."":()V        4: aload_0              5: iconst_1             6: putfield      #2                  // Field mSubX:I        9: return           ......      }

此時(shí)mSubX已經(jīng)是99了, 再執(zhí)行下面的4,5,6, 這一部分是SubClass的初始化, 代碼將把1賦給mSubX, 99被1覆蓋了.

方法返回后, 相當(dāng)于我們執(zhí)行完了箭頭指的這一句代碼:

public class Main {     public static void main(String[] args) {       ->SubClass sc = new SubClass();         sc.printX();     } }

接下來(lái)執(zhí)行的代碼將打印mSubX的值, 自然就是1了.

以前就聽說(shuō)過(guò)JVM是基于棧的, Dalvik是基于寄存器的, 現(xiàn)在看了Java字節(jié)碼, 回想一下smali, 自然就能明白. 我在Android無(wú)需權(quán)限顯示懸浮窗, 兼談逆向分析app中有分析smali代碼, smali里面經(jīng)??吹筋愃苬0, v1這類東西, 是在操作寄存器, 而剛才分析的bytecode, 指令常常伴隨著入棧出棧.

理論解釋

我們都知道Java是面向?qū)ο蟮恼Z(yǔ)言, 面向?qū)ο笕筇匦灾欢鄳B(tài)性. 假如父類構(gòu)造方法中調(diào)用了某個(gè)方法, 這個(gè)方法恰好被子類重寫了, 會(huì)發(fā)生什么?

根據(jù)多態(tài)性, 實(shí)際被調(diào)用的是子類的方法, 這個(gè)沒(méi)錯(cuò). 再考慮有繼承時(shí), 初始化的順序. 如果是new一個(gè)子類, 那么初始化順序是:

父類static成員 -> 子類static成員 -> 父類普通成員初始化和初始化塊 -> 父類構(gòu)造方法-> 子類普通成員初始化和初始化塊 -> 子類構(gòu)造方法

父類構(gòu)造方法中調(diào)用了一次setX, 此時(shí)mSubX中已經(jīng)是我們要跟蹤的值, 但之后子類普通成員初始化將mSubX又初始化了一遍, 覆蓋了前面我們跟蹤的值, 自然得到的值就是錯(cuò)的.

Java中, 在構(gòu)造方法中唯一能安全調(diào)用的是基類中的final方法, 自己的final方法(自己的private方法自動(dòng)final), 如果類本身是final的, 自然就能安全調(diào)用自己所有的方法.

完全遵守這個(gè)準(zhǔn)則, 可以保證不會(huì)出這個(gè)bug. 實(shí)際上我們常常不能遵守, 所以要時(shí)刻小心這個(gè)問(wèn)題.

這個(gè)東西在Java編程思想(第四版) (機(jī)械工業(yè)出版社 2012年11月第1版) 的8.3.3小節(jié)有寫過(guò), 但是這種東西除非自己遇到bug了, 基本看過(guò)不會(huì)有印象.

這篇文章所有的知識(shí)點(diǎn)基本都是很基礎(chǔ)的, 我自己也都記得, 但當(dāng)這些知識(shí)合在一起的時(shí)候, 他們之間產(chǎn)生的反應(yīng)卻是我沒(méi)有注意過(guò)的. 這也是我寫這篇文章的原因.

如果以后有人面試拿這個(gè)問(wèn)題考你, 你可能是遇上drakeet了.

題外話

關(guān)于默認(rèn)初始化, 比如這樣寫:

public class SubClass extends SuperClass {     private int mSubX;      public SubClass() {}     ...... }

如果父類保證一定會(huì)在初始化時(shí)調(diào)用setX, 程序是不會(huì)出現(xiàn)上面說(shuō)的bug的, 因?yàn)槟J(rèn)初始化并不是靠生成下面這樣的代碼默認(rèn)初始化.

4: aload_0      
       5: iconst_1     
       6: putfield      #2                  // Field mSubX:I

所謂的默認(rèn)初始化, 其實(shí)是我們要實(shí)例化一個(gè)對(duì)象之前, 需要一塊內(nèi)存放我們的數(shù)據(jù), 這塊內(nèi)存被全部置為0, 這就是默認(rèn)初始化了.

下面這兩句話, 雖然效果一樣, 但實(shí)際是有區(qū)別的.

private int mSubX;

private int mSubX = 0;

一般情況下, 這兩句代碼對(duì)程序沒(méi)有任何影響(除非你遇到這個(gè)bug), 上面一句和下面一句的區(qū)別在于, 下面一句會(huì)導(dǎo)致方法里面生成3條指令, 分別是aload_0, iconst_0, putfield #**, 而上面一句則不會(huì).

看完上述內(nèi)容,你們掌握J(rèn)ava 中怎么重寫方法的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!


本文標(biāo)題:Java中怎么重寫方法
網(wǎng)站地址:http://weahome.cn/article/gishjg.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部