on java 8 学习笔记 2022.2.17-2022.2.18
2022.2.17
問題
正如你在第8章會(huì)看到的,當(dāng)引入繼承時(shí),通過繼承而來的類(子類)可以訪問父類的protected成員以及public成員(但不能訪問private成員)。只有當(dāng)兩個(gè)類在同一個(gè)包中時(shí),它才可以訪問父類的包訪問權(quán)限成員。但現(xiàn)在不必?fù)?dān)心繼承和protected。7.2.1
沒看懂這句話想表達(dá)什么
答:意思很簡單,就是強(qiáng)調(diào)默認(rèn)包的訪問權(quán)限
然而,僅僅因?yàn)橐粋€(gè)對(duì)象的引用在類中是private的,并不意味著其他對(duì)象不能擁有對(duì)同一個(gè)對(duì)象的public引用。(請(qǐng)參閱進(jìn)階卷第2章了解別名問題。)7.2.3
這句話我也沒看懂
第七章 實(shí)現(xiàn)隱藏
對(duì)于僅用于實(shí)現(xiàn)類但不提供給客戶程序員直接使用的方法也是如此。
這里的方法對(duì)應(yīng)的應(yīng)該是我們的私有方法
當(dāng)編譯一個(gè).java文件時(shí),文件中的每個(gè)類都會(huì)有一個(gè)輸出文件。輸出文件的名字就是其在.java文件中對(duì)應(yīng)的類的名字,但擴(kuò)展名為.class。因此,你可以從少量的.java文件中得到相當(dāng)多的.class文件。如果使用編譯型語言寫過程序,你可能習(xí)慣于編譯器輸出一個(gè)中間形式(通常是obj文件),然后使用鏈接器(linker)或庫生成器(librarian,用來創(chuàng)建庫)將它與其他同類文件打包在一起,以創(chuàng)建一個(gè)可執(zhí)行文件。Java不是這樣的。在Java中一個(gè)可運(yùn)行程序就是一堆.class文件,可以使用jar歸檔器將它們打包并壓縮成一個(gè)Java檔案文件(JAR)。Java解釋器負(fù)責(zé)查找、加載和解釋這些文件。
這種寫法可以讓你直接調(diào)用靜態(tài)方法
fun()而不用寫成這樣
類名.方法名(),挺奇妙的,protected的使用居然是為了在包外可以使用繼承的基類的方法,這確實(shí)讓我挺意外的
不是public的類的引用,如果在包外使用了,編譯器是找不到的
還有protected修飾符不能用于類
protected class one {protected void f(){System.out.println("hello");} }這么寫編譯不過
有時(shí)候基類的創(chuàng)建者想要把特定成員的訪問權(quán)限賦給子類,而不是所有的類,這時(shí)候protected就可以發(fā)揮作用了。protected還提供了包訪問權(quán),也就是說,同一包中的其他類也可以訪問protected元素。
如果你回顧文件Cookie.java,就會(huì)知道下面的類不能調(diào)用包訪問權(quán)限成員bite():
// hiding/ChocolateChip.java // 無法在另一個(gè)包里調(diào)用包訪問權(quán)限的成員 import hiding.dessert.*;public class ChocolateChip extends Cookie {public ChocolateChip() {System.out.println("ChocolateChip constructor");}public void chomp() {//- bite(); // 無法訪問bite}public static void main(String[] args) {ChocolateChip x = new ChocolateChip();x.chomp();} } /* 輸出: Cookie constructor ChocolateChip constructor */如果類Cookie中存在一個(gè)方法bite(),那么這個(gè)方法也存在于任何繼承Cookie的類中。但是bite()只具有包訪問權(quán)限并且位于另一個(gè)包中,因此無法在當(dāng)前包中使用它。你可以將其修改為public,但這樣的話每個(gè)人就都可以訪問它了,這也許不是你想要的。如果按如下方式更改類Cookie:
// hiding/cookie2/Cookie.java package hiding.cookie2;public class Cookie {public Cookie() {System.out.println("Cookie constructor");}protected void bite() {System.out.println("bite");} }這樣任何繼承Cookie的類都可以訪問bite():
// hiding/ChocolateChip2.java import hiding.cookie2.*;public class ChocolateChip2 extends Cookie {public ChocolateChip2() {System.out.println("ChocolateChip2 constructor");}public void chomp() { bite(); } // protected方法public static void main(String[] args) {ChocolateChip2 x = new ChocolateChip2();x.chomp();} } /* 輸出: Cookie constructor ChocolateChip2 constructor bite */這時(shí)盡管bite()也有包訪問權(quán)限,但它不是public的。
總結(jié)一下,如果將不同的包看作使用者,那么public就是公車.如果將類看作使用者的話,那么默認(rèn)類就是公車,默認(rèn)類至少保證了同一個(gè)包中的引用還是可用的,不至于無法創(chuàng)建引用.(注意我這里沒提構(gòu)造器的事)
然后,變量的訪問權(quán)限大于類的訪問權(quán)限沒有意義
請(qǐng)注意,類不能是private(這將使除該類之外的任何類都無法訪問它)或protected的5。因此,對(duì)于類訪問權(quán)限,只有兩種選擇:包訪問權(quán)限和public。如果想要防止對(duì)該類的訪問,可以將其所有的構(gòu)造器都設(shè)為private,從而禁止其他人創(chuàng)建該類的對(duì)象,而你則可以在這個(gè)類的靜態(tài)方法中創(chuàng)建對(duì)象:
5實(shí)際上,內(nèi)部類可以是private的或protected的,但這是特殊情況。這些主題在第11章中會(huì)介紹。
默認(rèn)變量只在同一個(gè)包中可以隨便用,也就是它是同一個(gè)包下類的公車
package example;public class one {int i=9;public static void main(String[] args){System.out.println(new two().i);} } class two {int i=0; } package Test;import example.one;public class test {public static void main(String[] args){one a = new one();System.out.println(a.i);}}你會(huì)發(fā)現(xiàn)下面的代碼編譯通不過
注意,訪問權(quán)限控制側(cè)重于庫開發(fā)者和該庫的外部客戶之間的關(guān)系,這也是一種通信方式。不過有很多情況并非如此。例如,你自己編寫所有的代碼,或者你與一個(gè)小團(tuán)隊(duì)密切合作,而且所有的內(nèi)容都放在同一個(gè)包中。這些情況是另一種不同的通信方式,嚴(yán)格遵守訪問權(quán)限規(guī)則可能不是最佳選擇。默認(rèn)的(包)訪問權(quán)限可能就夠用了。
第八章 復(fù)用
初始化引用有下列4種方式。
以下是這4種方式的示例:
// reuse/Bath.java // 使用組合進(jìn)行構(gòu)造器初始化class Soap {private String s;Soap() {System.out.println("Soap()");s = "Constructed";}@Override public String toString() { return s; } }public class Bath {private String // 在定義時(shí)初始化s1 = "Happy",s2 = "Happy",s3, s4;private Soap castile;private int i;private float toy;public Bath() {System.out.println("Inside Bath()");s3 = "Joy";toy = 3.14f;castile = new Soap();}// 實(shí)例初始化{ i = 47; }@Override public String toString() {if(s4 == null) // 延遲初始化s4 = "Joy";return"s1 = " + s1 + "\n" +"s2 = " + s2 + "\n" +"s3 = " + s3 + "\n" +"s4 = " + s4 + "\n" +"i = " + i + "\n" +"toy = " + toy + "\n" +"castile = " + castile;}public static void main(String[] args) {Bath b = new Bath();System.out.println(b);} } /* 輸出: Inside Bath() Soap() s1 = Happy s2 = Happy s3 = Joy s4 = Joy i = 47 toy = 3.14 castile = Constructed */?
如果省略訪問權(quán)限修飾符,則該成員的權(quán)限默認(rèn)是包訪問權(quán)限,僅允許包內(nèi)的成員進(jìn)行訪問。因此,在這個(gè)包內(nèi),如果沒有訪問權(quán)限修飾符,任何人都可以使用這些方法。例如Detergent就沒有問題。但是,如果來自其他包的類要繼承Cleanser,那它就只能訪問public成員。因此,考慮到繼承,作為一般規(guī)則,應(yīng)該將所有字段設(shè)為private,將所有方法設(shè)為public(稍后你將學(xué)到,protected成員也允許子類訪問)。在特定情況下,你必須進(jìn)行調(diào)整,但一般來說這是一個(gè)有用的指導(dǎo)方針。
注意下,protected修飾符是專門用來針對(duì)public修飾符的,默認(rèn)修飾符對(duì)他影響不大
public class test extends two {public static void main(String[] args){one a = new one();System.out.println(a.i);two b = new two();} } package example;public class one {int i=9;public static void main(String[] args){System.out.println(new two().i);} } class two {int i=0;protected void f(){System.out.println("hello");} }會(huì)顯示example.two在example中不是公共的; 無法從外部程序包中對(duì)其進(jìn)行訪問,還有默認(rèn)變量無法在包外訪問,還有這里光是導(dǎo)入two這個(gè)類就會(huì)報(bào)錯(cuò)
package Test;import example.one;public class test extends one {public static void main(String[] args){one a = new one();new test().f();}} package example;public class one {int i=9;public static void main(String[] args){System.out.println(new two().i);}protected void f(){System.out.println("hello");} } class two {int i=0;protected void f(){System.out.println("hello");} }正如在scrub()中看到的那樣,可以使用基類中定義的方法并對(duì)其進(jìn)行修改。在這個(gè)示例中,你可能想從新版本的方法里調(diào)用繼承來的基類方法。但是在scrub()中不能簡單地調(diào)用scrub(),因?yàn)檫@會(huì)產(chǎn)生遞歸調(diào)用。為了解決這個(gè)問題,Java提供了super關(guān)鍵字,來指代當(dāng)前類繼承的“超類”(基類)。因此,表達(dá)式super.scrub()調(diào)用了基類版本的scrub()方法。
這里會(huì)遞歸調(diào)用主要是因?yàn)闀?huì)寫成這樣
f(){f() }如果不是的話,其實(shí)沒必要,不過把這個(gè)堵死了也好,畢竟可能產(chǎn)生歧義
現(xiàn)在涉及兩個(gè)類:基類和子類。想象一下子類產(chǎn)生的對(duì)象,這可能會(huì)令人困惑。從外部看,新類與基類具有相同的接口,或許還有一些額外的方法和字段。但是繼承不只是復(fù)制基類的接口這么簡單。當(dāng)創(chuàng)建子類對(duì)象時(shí),它里面包含了一個(gè)基類的子對(duì)象(subobject)。這個(gè)子對(duì)象與直接通過基類創(chuàng)建的對(duì)象是一樣的。只是從外面看,基類的子對(duì)象被包裹在了子類的對(duì)象中。
對(duì)基類構(gòu)造器的調(diào)用必須是子類構(gòu)造器的第一個(gè)操作
看了半天他這個(gè)代碼,終于看懂了他的意思,就是要隱藏一個(gè)類,定義的public實(shí)際上就類似于一個(gè)空殼子,里面private的對(duì)象才是實(shí)際上掌控方法的,就怎么說呢,這個(gè)寫的是邏輯吧,境界太高,只能理解到這了
其實(shí)有那么一點(diǎn)感覺,委托模式好像是代理模式的前身,雖然兩者差別很大.
沒看懂8.4.1,我尋思著,你這是要按照棧的順序清理是嗎?看著太像入棧和出棧了
其他
現(xiàn)在想想,其實(shí)構(gòu)造器作為一種靜態(tài)方法確實(shí)很神奇,它居然可以訪問到非靜態(tài)的變量,現(xiàn)在對(duì)構(gòu)造器的理解應(yīng)該是一種特殊的方法,因?yàn)樗軌蛐薷念惖膒rivate變量,會(huì)導(dǎo)致一定程度的封裝破壞,所以對(duì)它的使用就比較嚴(yán)格
而轉(zhuǎn)過來想一想,也正是因?yàn)闃?gòu)造器實(shí)際上是一種靜態(tài)方法,所以才可以在構(gòu)造器方法里調(diào)用另一個(gè)構(gòu)造器方法,雖然不能同時(shí)調(diào)用兩個(gè)構(gòu)造器,且構(gòu)造器必須出現(xiàn)在構(gòu)造器方法的最上面
public class test {int i ,j,m,n;public test(int i){this.i=i;}public test(int i,int j,int m){this(i);this.m=m;this.j=j;}public test(int i,int j){this(i);this.j=j;}void f(){System.out.println("i="+i+"j="+j+"m="+m);}public static void main(String[] args){new test(5,3,2).f();} }這里的this我側(cè)重于理解this方法,this靜態(tài)方法.其實(shí)感覺如果理解了靜態(tài)方法的概念,也就理解this()方法的精髓了,這個(gè)構(gòu)造器方法確實(shí)巧妙
雖然靜態(tài)方法可以產(chǎn)生內(nèi)部類,不過這個(gè)內(nèi)部類外部是不用想著訪問了
public class test {public static void f(){class a {public a(){System.out.println("hello");}}new a();}public static void main(String[] args){a one = new a();test.f();} }上面的代碼會(huì)顯示找不到對(duì)象a,所以即便我將a的構(gòu)造器作為了public對(duì)象,外界也別想訪問這個(gè)對(duì)象,不是因?yàn)槌跏蓟霾坏?而是找不到這個(gè)類
2022.2.18
第八章 復(fù)用
組合和繼承都會(huì)將子對(duì)象放置在新類中(組合是顯式執(zhí)行此操作,而繼承是隱式執(zhí)行)。你可能想知道兩者之間的區(qū)別,以及如何在兩者之間做出選擇。
當(dāng)希望在新類中使用現(xiàn)有類的功能而不是其接口時(shí),應(yīng)該使用組合。也就是說,在新類中嵌入一個(gè)對(duì)象(通常是private)來實(shí)現(xiàn)自己的特性。新類的用戶看到的是新類定義的接口,而不是嵌入對(duì)象的接口。
對(duì)于新類里通過組合得到的成員,有時(shí)候允許類的使用者直接訪問它們是合理的。為此,可以將成員對(duì)象設(shè)為public(你可以將其視為一種“半委托”)。成員對(duì)象隱藏自己的實(shí)現(xiàn),所以這種做法是安全的。當(dāng)用戶了解到你正在組裝一堆組件時(shí),會(huì)更容易理解你的接口。car對(duì)象就是一個(gè)很好的例子:
// reuse/Car.java // 使用公共對(duì)象來實(shí)現(xiàn)組合class Engine { public void start() {} public void rev() {} public void stop() {} }class Wheel { public void inflate(int psi) {} }class Window { public void rollup() {} public void rolldown() {} }class Door { public Window window = new Window(); public void open() {} public void close() {} }public class Car { public Engine engine = new Engine(); public Wheel[] wheel = new Wheel[4]; public Doorleft = new Door(),right = new Door(); // 雙門車 public Car() {for(int i = 0; i < 4; i++)wheel[i] = new Wheel(); } public static void main(String[] args) {Car car = new Car();car.left.window.rollup();car.wheel[0].inflate(72); } }很有意思的一種組合方式,用public確實(shí)可以讓人更加清晰的了解
還有這里學(xué)習(xí)一下調(diào)用基類的toString();
常量之所以有用,有兩個(gè)原因:
對(duì)編譯時(shí)常量來說,編譯器可以將常量值“折疊”到計(jì)算中;也就是說,計(jì)算可以在編譯時(shí)進(jìn)行,這節(jié)省了一些運(yùn)行時(shí)開銷。在Java里,這些常量必須是基本類型,并用final關(guān)鍵字表示。在定義常量時(shí)必須提供一個(gè)值。
當(dāng)final關(guān)鍵字與對(duì)象引用而非基本類型一起使用時(shí),其含義可能會(huì)令人困惑。對(duì)于基本類型,final使其值恒定不變,但對(duì)于對(duì)象引用,final使引用恒定不變。一旦引用被初始化為一個(gè)對(duì)象,它就永遠(yuǎn)不能被更改為指向另一個(gè)對(duì)象了。但是,對(duì)象本身是可以修改的。Java沒有提供使對(duì)象恒定不變的方法。(但是,你可以編寫類,使對(duì)象具有恒定不變的效果。)這個(gè)限制同樣適用于數(shù)組,它們也是對(duì)象。
最后輸出的是other不過要在是s1前加this,不然就是second
不加s1,就不是two這個(gè)對(duì)象的字段被賦值,而是s1這個(gè)變量被賦值
“重寫”只有在方法是基類接口的一部分時(shí)才會(huì)發(fā)生。也就是說,必須能將一個(gè)對(duì)象向上轉(zhuǎn)型為其基類類型并能調(diào)用與其相同的方法(下一章中你會(huì)更理解這一點(diǎn))。如果一個(gè)方法是private的,它就不是基類接口的一部分。它只是隱藏在類中的代碼,只不過恰好具有相同的名稱而已。即使在子類中創(chuàng)建了具有相同名稱的public、protected或包訪問權(quán)限的方法,它與基類中這個(gè)相同名稱的方法也沒有任何聯(lián)系。你并沒有重寫該方法,只不過是創(chuàng)建了一個(gè)新的方法。private方法是不可訪問的,并且可以有效地隱藏自己,因此除了定義它的類的代碼組織之外,它不會(huì)影響任何事情。
final類的字段可以是final,也可以不是,根據(jù)個(gè)人選擇而定。無論類是否定義為final,相同的規(guī)則都適用于字段的final定義。然而,由于final類禁止繼承,它的所有方法都是隱式final的,因?yàn)闊o法重寫它們。你可以在final類的方法中包含final修飾符,但它不會(huì)添加任何意義。
首先final類本身不能繼承,所以即便字段是public的,也不能用子類進(jìn)行訪問
但是一個(gè)public的final類并不會(huì)禁止包外的類除了自己的子類外訪問自己public字段
package Test;import example.a; import example.one; import example.*; public class test extends two {public static void main(String[] args){new one().s1 = "heool";} } package example; public final class one {public String s1 = "hello"; }當(dāng)你運(yùn)行java Beetle時(shí),首先會(huì)嘗試訪問靜態(tài)方法Beetle.main(),所以加載器會(huì)去Beetle.class文件中找到Beetle類的編譯代碼。在加載它的代碼時(shí),加載器注意到有一個(gè)基類,然后它就會(huì)去加載基類。無論是否創(chuàng)建該基類的對(duì)象,都會(huì)發(fā)生這種情況。(可以嘗試注釋掉對(duì)象創(chuàng)建來驗(yàn)證一下。)
如果基類又有自己的基類,那么第二個(gè)基類也將被加載,以此類推。接下來,會(huì)執(zhí)行根基類(本例中為Insect)中的靜態(tài)初始化,然后是下一個(gè)子類,以此類推。這很重要,因?yàn)樽宇惖撵o態(tài)初始化可能依賴于基類成員的正確初始化。
現(xiàn)在所有必要的類都已加載,因此可以創(chuàng)建對(duì)象了。首先,該對(duì)象中的所有基本類型都被設(shè)為其默認(rèn)值,并且對(duì)象引用被設(shè)為null——這通過將對(duì)象中的內(nèi)存設(shè)置為二進(jìn)制零來一步實(shí)現(xiàn)。然后調(diào)用基類構(gòu)造器。這里的調(diào)用是自動(dòng)的,但也可以通過super關(guān)鍵字來指定基類構(gòu)造器的調(diào)用(需要作為Beetle構(gòu)造器中的第一個(gè)操作)。基類構(gòu)造器以與子類構(gòu)造器相同的順序經(jīng)歷相同的過程。基類構(gòu)造器完成后,子類的實(shí)例變量按文本順序初始化。最后,執(zhí)行子類構(gòu)造器的其余部分。
第九章 多態(tài)
將一個(gè)方法調(diào)用和一個(gè)方法體關(guān)聯(lián)起來的動(dòng)作稱為綁定。在程序運(yùn)行之前執(zhí)行綁定(如果存在編譯器和鏈接器的話,由它們來實(shí)現(xiàn)),稱為前期綁定。你之前可能沒有聽說過這個(gè)術(shù)語,因?yàn)樵诿嫦蜻^程語言中默認(rèn)就是前期綁定的。例如,在C語言中只有一種方法調(diào)用,那就是前期綁定。
上述程序之所以令人困惑,主要是由于前期綁定。這是因?yàn)楫?dāng)編譯器只有一個(gè)Instrument引用時(shí),它無法知道哪個(gè)才是要調(diào)用的正確方法。
解決這個(gè)問題的方案稱為后期綁定,這意味著綁定發(fā)生在運(yùn)行時(shí),并基于對(duì)象的類型。后期綁定也稱為動(dòng)態(tài)綁定或運(yùn)行時(shí)綁定。當(dāng)一種語言實(shí)現(xiàn)后期綁定時(shí),必須有某種機(jī)制在運(yùn)行時(shí)來確定對(duì)象的類型,并調(diào)用恰當(dāng)?shù)姆椒āR簿褪钦f,編譯器仍然不知道對(duì)象的類型,但方法調(diào)用機(jī)制能找到并調(diào)用正確的方法體。后期綁定機(jī)制因語言而異,但可以想象,必須要將某種類型信息放在對(duì)象里。
這里前期調(diào)用需要理解的最重要的一句話就是方法調(diào)用和方法體是緊密一對(duì)一的鏈接的,下面舉一個(gè)例子
f()->方法diaoyonf(){//方法體 int i =0; }然后前期綁定所無法做道的就是,無法通過一個(gè)方法名去定位有多個(gè)方法體的目標(biāo),學(xué)過c語言的就會(huì)感覺一個(gè)方法體可以有多個(gè)目標(biāo)本身就很離譜
下面我串一下c語言的函數(shù),學(xué)過c語言的應(yīng)該都知道函數(shù)指針這種東西,它的解引用其實(shí)就是方法體的位置,那么其實(shí)可以很容易的推斷出來,一個(gè)指針變量所能指向的位置是唯一確定的,但問題來了,如果指針指向的方法存在多種形式(也就是多種同名方法),那么每個(gè)形式的方法體占的地址肯定也是不同的,而這就是蛋疼的地方了,這種特性導(dǎo)致了它能實(shí)現(xiàn)重載的可能性直接變0.
Java中的所有方法綁定都是后期綁定,除非方法是static或final的(private方法隱式為final)。
static的方法其實(shí)還是有c語言的遺風(fēng)的,然后final方法我不是很能理解為什么可以不是后期綁定機(jī)制
final方法可能原因是不能進(jìn)行重寫吧,重載是編譯時(shí)的多態(tài),重寫才是運(yùn)行時(shí)多態(tài)的表現(xiàn)
當(dāng)Sub對(duì)象向上轉(zhuǎn)型為Super引用時(shí),任何字段訪問都會(huì)被編譯器解析,因此不是多態(tài)的。在此示例中,Super.field和Sub.field被分配了不同的存儲(chǔ)空間。因此,Sub實(shí)際上包含兩個(gè)被稱為field的字段:它自己的字段和它從Super繼承的字段。然而,當(dāng)你在Sub中引用field時(shí),Super版本并不是默認(rèn)的那個(gè)。要獲得Super的field,必須明確地說super.field。
如果要調(diào)用父類的字段,要顯示用super調(diào)用,當(dāng)然,如果時(shí)private字段,你用super也沒用
這里層次結(jié)構(gòu)中的每個(gè)類都包含類型為Characteristic和Description的成員對(duì)象,它們也必須被銷毀。處置順序應(yīng)該與初始化順序相反,以防子對(duì)象依賴于其他對(duì)象。對(duì)于字段,這意味著與聲明順序相反(因?yàn)樽侄问前绰暶黜樞虺跏蓟?#xff09;。對(duì)于基類(遵循C++中析構(gòu)函數(shù)的形式),首先執(zhí)行子類清理,然后是基類清理。這是因?yàn)樽宇愒谇謇頃r(shí)可能會(huì)調(diào)用基類中的一些方法,這些方法可能需要基類組件處于存活狀態(tài),因此不能過早地銷毀它們。輸出顯示了Frog對(duì)象的所有部分都是按照與創(chuàng)建順序相反的順序進(jìn)行銷毀的。
有點(diǎn)能夠理解為什么要以相反的順序清理了,就是為了防止某些對(duì)象是和其他對(duì)象有關(guān)聯(lián)的,比如基類子類,刪了基類子類就殘疾了
這個(gè)其實(shí)是一件特別奇妙的事情,我用c的角度來審視這個(gè)問題就是,在創(chuàng)建一個(gè)對(duì)象時(shí),該對(duì)象的全部信息應(yīng)該存儲(chǔ)在堆中的一塊區(qū)域當(dāng)中,那么,本例中的兩個(gè)對(duì)象本身其實(shí)他們的draw()方法其實(shí)就是存儲(chǔ)在他們對(duì)應(yīng)的區(qū)域中,但是,java的后期綁定導(dǎo)致了一個(gè)問題,在java中,構(gòu)造器和方法其實(shí)是由個(gè)隱形的參數(shù)this的,用來確定對(duì)象使用的,那么這里構(gòu)造子對(duì)象時(shí),其實(shí)是在子對(duì)象的空間中進(jìn)行調(diào)用,也就是說,基類構(gòu)造器構(gòu)造的過程中是會(huì)被傳入一個(gè)this的隱參來進(jìn)行定位,也就是這個(gè)父對(duì)象其實(shí)是在這個(gè)子對(duì)象的空間當(dāng)中的,這也就導(dǎo)致了在調(diào)用方法時(shí),其實(shí)是隱式調(diào)用了this.fun(),而不是super.fun().
實(shí)際的初始化過程如下所示。
這里我著重理解的是初始化為二進(jìn)制0,因?yàn)槲覍W(xué)過一點(diǎn)匯編,也學(xué)過一點(diǎn)逆向,在我的理解中,c語言是直接劃定了一邊區(qū)域作為了某個(gè)變量的值,里面不一定是什么,而java這是劃定區(qū)域以后,把里面的值先設(shè)置為0.
編寫構(gòu)造器時(shí)有一個(gè)很好的準(zhǔn)則:“用盡可能少的操作使對(duì)象進(jìn)入正常狀態(tài),如果可以避免的話,請(qǐng)不要調(diào)用此類中的任何其他方法。”只有基類中的final方法可以在構(gòu)造器中安全調(diào)用(這也適用于private方法,它們默認(rèn)就是final的)。這些方法不能被重寫,因此不會(huì)產(chǎn)生這種令人驚訝的問題。
Java 5添加了協(xié)變返回類型(covariant return type),這表示子類中重寫方法的返回值可以是基類方法返回值的子類型:
// polymorphism/CovariantReturn.javaclass Grain { @Override public String toString() {return "Grain"; } }class Wheat extends Grain { @Override public String toString() {return "Wheat"; } }class Mill { Grain process() { return new Grain(); } }class WheatMill extends Mill { @Override Wheat process() {return new Wheat(); } }public class CovariantReturn { public static void main(String[] args) {Mill m = new Mill();Grain g = m.process();System.out.println(g);m = new WheatMill();g = m.process();System.out.println(g); } } /* 輸出: Grain Wheat */Java 5與其之前版本的主要區(qū)別在于,其之前版本強(qiáng)制要求process()的重寫版本返回Grain,而不能是Wheat,即使Wheat繼承自Grain,因而也是一個(gè)合法的返回類型。協(xié)變返回類型允許更具體的Wheat返回類型。
這個(gè)其實(shí)很有意思
Stage對(duì)象包含了一個(gè)Actor的引用,它被初始化為一個(gè)HappyActor對(duì)象。這意味著performPlay()方法會(huì)產(chǎn)生特定的行為。因?yàn)橐每梢栽谶\(yùn)行時(shí)重新綁定到不同的對(duì)象,所以可以將actor中的引用替換為對(duì)SadActor對(duì)象的引用,這樣performPlay()產(chǎn)生的行為也隨之改變。因此你就在運(yùn)行時(shí)獲得了動(dòng)態(tài)靈活性(這也稱為狀態(tài)模式)。相反,你不能在運(yùn)行時(shí)決定以不同的方式來繼承,這必須在編譯期間就完全確定下來。
“使用繼承來表達(dá)行為上的差異,使用字段來表達(dá)狀態(tài)上的變化”在前面的例子中,兩者都用到了:通過繼承得到了兩個(gè)不同的類來表達(dá)act()方法的差異,而Stage使用組合來允許其狀態(tài)發(fā)生改變。在這里,狀態(tài)的改變恰好導(dǎo)致了行為的變化。
不得不說,真是精妙的思想,繼承只是為了接口的多態(tài)性,如果不能使接口產(chǎn)生多態(tài)性,應(yīng)該使用組合的方式來實(shí)現(xiàn)狀態(tài)的多樣性.
總結(jié)
以上是生活随笔為你收集整理的on java 8 学习笔记 2022.2.17-2022.2.18的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Mysql:Access denied
- 下一篇: on java8学习笔记2022.2.1