日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

on java 8 学习笔记 2022.2.17-2022.2.18

發(fā)布時(shí)間:2024/4/18 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 on java 8 学习笔记 2022.2.17-2022.2.18 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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é)查找、加載和解釋這些文件。

  • import static onjava.Range.*;

    這種寫法可以讓你直接調(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種方式。

  • 在定義對(duì)象時(shí)。這意味著它們將始終在調(diào)用構(gòu)造器之前被初始化。
  • 在該類的構(gòu)造器中。
  • 在對(duì)象實(shí)際使用之前。這通常稱為延遲初始化(lazy initialization)。在對(duì)象創(chuàng)建成本高昂且不需要每次都創(chuàng)建的情況下,它可以減少開銷。
  • 使用實(shí)例初始化
  • 以下是這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è)操作

  • // reuse/SpaceShipDelegation.javapublic class SpaceShipDelegation {private String name;private SpaceShipControls controls =new SpaceShipControls();public SpaceShipDelegation(String name) {this.name = name;}// 委托方法:public void back(int velocity) {controls.back(velocity);}public void down(int velocity) {controls.down(velocity);}public void forward(int velocity) {controls.forward(velocity);}public void left(int velocity) {controls.left(velocity);}public void right(int velocity) {controls.right(velocity);}public void turboBoost() {controls.turboBoost();}public void up(int velocity) {controls.up(velocity);}public static void main(String[] args) {SpaceShipDelegation protector =new SpaceShipDelegation("NSEA Protector");protector.forward(100);} } public class SpaceShipControls {void up(int velocity) {}void down(int velocity) {}void left(int velocity) {}void right(int velocity) {}void forward(int velocity) {}void back(int velocity) {}void turboBoost() {} } // reuse/DerivedSpaceShip.javapublic class DerivedSpaceShip extends SpaceShipControls {private String name;public DerivedSpaceShip(String name) {this.name = name;}@Override public String toString() {return name;}public static void main(String[] args) {DerivedSpaceShip protector =new DerivedSpaceShip("NSEA Protector");protector.forward(100);} }

    看了半天他這個(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í)可以讓人更加清晰的了解

  • // reuse/Orc.java // protected關(guān)鍵字class Villain { private String name; protected void set(String nm) { name = nm; } Villain(String name) { this.name = name; } @Override public String toString() {return "I'm a Villain and my name is " + name; } }public class Orc extends Villain { private int orcNumber; public Orc(String name, int orcNumber) {super(name);this.orcNumber = orcNumber; } public void change(String name, int orcNumber) {set(name); // 方法可用,因?yàn)槭莗rotected的this.orcNumber = orcNumber; } @Override public String toString() {return "Orc " + orcNumber + ": " + super.toString(); } public static void main(String[] args) {Orc orc = new Orc("Limburger", 12);System.out.println(orc);orc.change("Bob", 19);System.out.println(orc); } } /* 輸出: Orc 12: I'm a Villain and my name is Limburger Orc 19: I'm a Villain and my name is Bob */

    還有這里學(xué)習(xí)一下調(diào)用基類的toString();

  • 常量之所以有用,有兩個(gè)原因:

  • 它可以是一個(gè)永遠(yuǎn)不會(huì)改變的編譯時(shí)常量
  • 它可以是在運(yùn)行時(shí)初始化的值,而你不希望它被更改。
  • 對(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ì)象。

  • public class one {String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){System.out.println(new two("second"));} } public class two extends one{public two (String s1){super(s1);this.s1="other";}@Overridepublic String toString() {return s1;} }

    最后輸出的是other不過要在是s1前加this,不然就是second

    不加s1,就不是two這個(gè)對(duì)象的字段被賦值,而是s1這個(gè)變量被賦值

  • // reuse/FinalArguments.java // 在方法參數(shù)中使用finalclass Gizmo {public void spin() {} }public class FinalArguments {void with(final Gizmo g) {//- g = new Gizmo(); // Illegal -- g is final}void without(Gizmo g) {g = new Gizmo(); // OK -- g not finalg.spin();}// void f(final int i) { i++; } // 不能更改// 對(duì)一個(gè)final基本類型只能執(zhí)行讀操作int g(final int i) { return i + 1; }public static void main(String[] args) {FinalArguments bf = new FinalArguments();bf.without(null);bf.with(null);} }
  • “重寫”只有在方法是基類接口的一部分時(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"; }
  • // reuse/Beetle.java // 初始化的全過程class Insect {private int i = 9;protected int j;Insect() {System.out.println("i = " + i + ", j = " + j);j = 39;}private static int x1 =printInit("static Insect.x1 initialized");static int printInit(String s) {System.out.println(s);return 47;} }public class Beetle extends Insect {private int k = printInit("Beetle.k initialized");public Beetle() {System.out.println("k = " + k);System.out.println("j = " + j);}private static int x2 =printInit("static Beetle.x2 initialized");public static void main(String[] args) {System.out.println("Beetle constructor");Beetle b = new Beetle();} } /* 輸出: static Insect.x1 initialized static Beetle.x2 initialized Beetle constructor i = 9, j = 0 Beetle.k initialized k = 47 j = 39 */

    當(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)

  • // polymorphism/shape/RandomShapes.java // 一個(gè)隨機(jī)產(chǎn)生形狀的工廠 package polymorphism.shape; import java.util.*;public class RandomShapes {private Random rand = new Random(47);public Shape get() {switch(rand.nextInt(3)) {default:case 0: return new Circle();case 1: return new Square();case 2: return new Triangle();}}public Shape[] array(int sz) {Shape[] shapes = new Shape[sz];// 用各種形狀填滿數(shù)組for(int i = 0; i < shapes.length; i++)shapes[i] = get();return shapes;} } // polymorphism/Shapes.java // Polymorphism in Java import polymorphism.shape.*;public class Shapes {public static void main(String[] args) {RandomShapes gen = new RandomShapes();// 執(zhí)行多態(tài)方法調(diào)用for(Shape shape : gen.array(9))shape.draw();} } /* 輸出: Triangle.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Circle.draw() */
  • // polymorphism/FieldAccess.java // 字段的直接訪問是在編譯時(shí)確定的class Super {public int field = 0;public int getField() { return field; } }class Sub extends Super {public int field = 1;@Override public int getField() { return field; }public int getSuperField() { return super.field; } }public class FieldAccess {public static void main(String[] args) {Super sup = new Sub(); // 向上轉(zhuǎn)型System.out.println("sup.field = " + sup.field +", sup.getField() = " + sup.getField());Sub sub = new Sub();System.out.println("sub.field = " +sub.field + ", sub.getField() = " +sub.getField() +", sub.getSuperField() = " +sub.getSuperField());} } /* 輸出: sup.field = 0, sup.getField() = 1 sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0 */

    當(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)的,比如基類子類,刪了基類子類就殘疾了

  • // polymorphism/PolyConstructors.java // 構(gòu)造器和多態(tài) // 不會(huì)生成你所期望的結(jié)果class Glyph {void draw() { System.out.println("Glyph.draw()"); }Glyph() {System.out.println("Glyph() before draw()");draw();System.out.println("Glyph() after draw()");} }class RoundGlyph extends Glyph {private int radius = 1;RoundGlyph(int r) {radius = r;System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);}@Override void draw() {System.out.println("RoundGlyph.draw(), radius = " + radius);} }public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);} } /* 輸出: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 */

    這個(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í)際的初始化過程如下所示。

  • 在發(fā)生任何其他事情之前,為對(duì)象分配的存儲(chǔ)空間會(huì)先被初始化為二進(jìn)制零。
  • 如前面所述的那樣調(diào)用基類的構(gòu)造器。此時(shí)被重寫的draw()方法會(huì)被調(diào)用(是的,這發(fā)生在RoundGlyph構(gòu)造器被調(diào)用之前),由于第1步的緣故,此時(shí)會(huì)發(fā)現(xiàn)radius值為零。
  • 按聲明的順序來初始化成員。
  • 子類構(gòu)造器的主體代碼被執(zhí)行。
  • 這里我著重理解的是初始化為二進(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í)很有意思

  • // polymorphism/Transmogrify.java // 通過組合動(dòng)態(tài)的改變對(duì)象的行為(狀態(tài)設(shè)計(jì)模式)class Actor { public void act() {} }class HappyActor extends Actor { @Override public void act() {System.out.println("HappyActor"); } }class SadActor extends Actor { @Override public void act() {System.out.println("SadActor"); } }class Stage { private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); } }public class Transmogrify { public static void main(String[] args) {Stage stage = new Stage();stage.performPlay();stage.change();stage.performPlay(); } } /* 輸出: HappyActor SadActor */

    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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。