on java8学习笔记2022.2.19-2022.2.20
2022.2.19
第十章 接口
如果一個類并不需要包含抽象方法,但同時還想阻止對它的任何實例化,這時將其定義為抽象類就很有用了。
接口和抽象類之間最顯著的區別可能是兩者的慣用方式。接口通常暗示“類的類型”或作為形容詞來使用,例如Runnable或Serializable,而抽象類通常是類層次結構的一部分,并且是“事物的類型”,例如String或Instrument。
接口也可以包含字段,但這些字段是隱式的static和final。
接口(該接口的修飾符是默認)如果只有包訪問權限的話,那么包外的類是無法實現這個接口的,即便這個接口里是public方法
你可以選擇將接口中的方法顯式聲明為public,但即使不顯式聲明,它們也是public的。所以當實現一個接口時,來自接口的方法必須被定義為public。否則,它們將默認為包訪問權限,導致在繼承期間降低了方法的可訪問性,而這是Java編譯器不允許的。
其實這里我是沒太理解作者的意思的,不過就我目前測試的結果,其實接口直接默認確實是最好的選擇,因為接口沒有private這種東西,也沒有protected這種東西,實際上就是默認和public,而當接口是public時,它的方法本身就全部默認是public的,而默認的時候就全部內置默認,非常和諧,而接口本身就是為了描述類所共有的特征的,所有類的方法的這么個東西,所以訪問權限一致我倒是覺得挺合理的
package Test;import example.a; import example.one;public class test implements a {public static void main(String[] args){new test().function();}public void function(){System.out.println("hello");} } package example;public interface a {void function(); }添加默認方法的一個令人信服的原因是,它允許向現有接口中添加方法,而不會破壞已經在使用該接口的所有代碼。默認方法有時也稱為防御方法(defender method)或虛擬擴展方法(virtual extension method)。
在JDK 9中,接口里的default和static方法都可以是private的。
講道理,第二句話就使得接口有了類的特征了,而且很詭異,這么搞有什么意義,如果設為了private,接口外的都無法訪問這個方法,那我設置這個方法是要干嘛?注意,我的意思是這個接口對實現類是直接隱藏的,因為不是有默認final這個東西,真的讓人頭大,比如下面這個
package example;public interface a {private void b(){System.out.println("hello");}} package example; public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){System.out.println(new two("second"));a b = new one();b.}@Overrideprivate void b(){} }關注下@Override,會發現,它提示你未從超類重寫方法,而下面的代碼顯示了你在包外無法調用接口a的私有靜態方法,這導致我看不懂這個私有靜態方法有什么用,當然,同時編譯器也會告訴你接口中的方法多余.
package example; public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){a.c();} } package example;public interface a {private static void b(){System.out.println("hello");}public static void c(){System.out.println("hi");} }這里要解決的話就只能重寫
返回類型不是方法簽名的一部分
不是很清楚為什么這里一定要加個super,如果不加的話,會報錯,當然,也可以這么寫
package example; // interfaces/Jim.java import java.util.*;interface Jim1 {default void jim() {System.out.println("Jim1::jim");} }interface Jim2 {default void jim() {System.out.println("Jim2::jim");} }public class Jim implements Jim1, Jim2 {@Override public void jim() {System.out.println("hello");}public static void main(String[] args) {new Jim().jim();} }有可能那個交給它基類寫去了?誰知道呢
這個看了挺久的,除了最后一個lambda沒學過以外,其他大概看懂了,第一個是上面已經定義好的類,第二個是以接口定義的類,第三個是方法引用.
這里讓我比較新奇的就是匿名的接口類的定義方式了
怎么看這個匿名接口類的定義方法呢?首先將new先隔開,那么后面Operation就是定義接口中需要我們定義的方法,而那個new則是創建這個對象
Operation() {public void execute() {Operation.show("Twist");}}這里沒有構造器,應該調用的默認構造器
{public void execute() {Operation.show("Twist");}}在使用適配器的實現方式里,FilterAdapter構造器通過你擁有的Filter接口,來生成一個你需要的Processor接口的對象。你可能還會注意到FilterAdapter類中使用了委托。
協變允許我們從process()里產生一個Waveform,而不僅僅是一個Object。
接口與實現的解耦允許我們將一個接口應用于多個不同的實現,因此代碼更具可復用性。
挺騷的,我現在看委托越來越感覺像是在玩代理
發現我搞混了一個東西,如果是下面這種形式
public void fun(){}上面這種表示的是一個默認方法,也就是他不是abstract的,下面這種才是抽象方法的寫法
public void fun();這就帶來了一個問題:應該使用接口還是抽象類?如果可以在沒有任何方法定義或成員變量的情況下創建基類,那么就使用接口而非抽象類。事實上,如果你認為某個類可以作為基類的話,也就可以考慮把它設計成接口(在10.11節中會重新討論這個主題)。\
注意下,這里Vampire一次性繼承了大量的接口,但是,這是接口特有的屬性,類不能這樣
之所以出現問題,是因為重寫、實現和重載令人不快地混合在一起。此外,重載方法不能只有返回類型不同。當最后兩行取消注釋時,錯誤消息說明了一切:
error: C5 is not abstract and does not override abstract method f() in I1 class C5 extends C implements I1 {}error: types I3 and I1 are incompatible; both define f(), but with unrelated return types interface I4 extends I1, I3 {}這里我還是有點意外的,原來重載方法只有返回類型不同真會報錯,我把這個改成了下面這個就不會報錯了
// interfaces/InterfaceCollision.javainterface I1 { void f(); } interface I2 { int f(int i); } interface I3 { int f(); } class C { public int f() { return 1; } }class C2 implements I1, I2 {@Overridepublic void f() {}@Overridepublic int f(int i) { return 1; } // 重載 }class C3 extends C implements I2 {@Overridepublic int f(int i) { return 1; } // 重載 }class C4 extends C implements I3 {// 完全相同,沒有問題:@Override public int f() { return 1; } }// 方法只有返回類型不同 //class C5 extends C implements I1 {} interface I4 extends I1, I2 {}然后我這里思考了一下,參數全部一樣,返回類型居然不一樣確實很離譜,這就不就變成抽獎了,而且你這樣實現也會很為難,讓人無法理解
下面代碼展示了final修飾的變量必須被初始化,否則報錯
package example; public final class one implements a{public final int i ;public String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){one a = new one("fhdjahj");a.s1="hello world";System.out.println(a.i);} }只要將final去掉,那么輸出結果就是0,否則你編譯都過不去
2022.2.20
第十章 接口
接口中的任何字段都自動是static和final的,因此接口是創建一組常量值的便捷工具。
首先要想這么用得先導包,我提前用idea,怎么導Months都不對,所以把他放進了我自己創建的包里面去了,然后我實驗了一下,確實已經被默認修改成了final
package Test;import example.a; import example.one; import example.*; public class test {public static void main(String[] args){//new test().function();Months.JANUARY=2;}public void function(){System.out.println("hello");} }這些字段不是接口的一部分。這些值存儲在該接口的靜態存儲區中。
接口可以嵌套在類和其他接口中。這呈現了幾個有趣的特性:
// interfaces/nesting/NestingInterfaces.java // {java interfaces.nesting.NestingInterfaces} package interfaces.nesting;class A { interface B {void f(); } public class BImp implements B {@Override public void f() {} } private class BImp2 implements B {@Override public void f() {} } public interface C {void f(); } class CImp implements C {@Override public void f() {} } private class CImp2 implements C {@Override public void f() {} } private interface D {void f(); } private class DImp implements D {@Override public void f() {} } public class DImp2 implements D {@Override public void f() {} } public D getD() { return new DImp2(); } private D dRef; public void receiveD(D d) {dRef = d;dRef.f(); } }interface E { interface G {void f(); } // 多余的public: public interface H {void f(); } void g(); // 接口內不能用private //- private interface I {} }public class NestingInterfaces { public class BImp implements A.B {@Override public void f() {} } class CImp implements A.C {@Override public void f() {} } // private的接口只能在定義的類里實現: //- class DImp implements A.D { //- public void f() {} //- } class EImp implements E {@Override public void g() {} } class EGImp implements E.G {@Override public void f() {} } class EImp2 implements E {@Override public void g() {}class EG implements E.G {@Override public void f() {}} } public static void main(String[] args) {A a = new A();// 無法訪問A.D://- A.D ad = a.getD();// 只能返回A.D://- A.DImp2 di2 = a.getD();// 無法訪問該接口的方法//- a.getD().f();// 另一個A才能處理getD():A a2 = new A();a2.receiveD(a.getD()); } }在類中嵌套接口的語法相當明顯。就像非嵌套接口一樣,它們可以具有public或包訪問權限的可見性。
另外這里有個不易理解之處:接口也可以是private的,如A.D中所示(嵌套接口和嵌套類使用相同的限定語法)。private的嵌套接口有什么好處?你可能會猜測,它只能作為DImp中的私有內部類來實現,但是A.DImp2表明它也可以作為public類來實現。不過A.DImp2在使用時只能被視為自身的類型,你不能提及它實現了private的接口D。所以實現了private接口的話,可以在不添加任何類型信息的情況下,限定該接口中的方法定義(也就是說,不允許任何向上轉型)。
方法getD()讓我們進一步陷入困境,這與private接口有關:它是一個public方法,但返回了一個private接口的引用。可以對這個方法的返回值做些什么?在main()里我們多次嘗試使用該返回值,但都失敗了。該返回值必須傳遞給一個有權使用它的對象——這里是另一個A,它可以通過receiveD()方法使用這個返回值。
接口E表明,接口之間也可以嵌套。然而,關于接口的規則——特別是所有接口元素必須是public的——在這里是嚴格執行的,所以嵌套在另一個接口中的接口自動為public的,不能設為private。
NestingInterfaces展示了實現嵌套接口的各種方式。特別要注意的是,當實現一個接口時,并不需要實現嵌套在其中的接口。此外,private接口不能在它們的定義類之外實現。
初看起來,這些特性好像僅僅是為了語法一致性而添加的,但我通常發現,一旦了解了一個特性,你就會經常發現它的有用之處。
看了很久,D那里應該說的是因為接口D是private的,也就是只在類A中可見,那么你就不能寫成這個形式了
A.D a = new Dimp();很有意思的一點,一個默認類里面居然定義了一個公共類,不過我試了下,這個公共類實際上還是出不了包
package Test;import example.a; import example.one; import example.*; public class test {public static void main(String[] args){//new test().function();System.out.println(new three.four());}public void function(){System.out.println("hello");} } package example; public final class one implements a{public int i ;public String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){one a = new one("fhdjahj");a.s1="hello world";System.out.println(a.i);} } class three {public class four {String s1 = "hello world";static{System.out.println("hello");}public String toString(){return s1;}} } package example; public final class one implements a{public int i ;public String s1 = "hello";public one (String s1){this.s1 = s1;}public static void main(String[] args){one a = new one("fhdjahj");a.s1="hello world";System.out.println(a.i);} } class three {public class four {String s1 = "hello world";static{System.out.println("hello");}public String toString(){return s1;}} }看了這么多接口的內容,現在就感覺接口就像游戲中的法師,如果要調用火元素來進行攻擊,就必須動用火元素魔法的咒語,而這個咒語就是接口,而再打比方,就是你想要用特效為暴擊的魔法攻擊,而不同元素實現暴擊的方式可以不一樣,但表現出來的結果就是暴擊,而這個特效為暴擊的魔法攻擊實際上也就是接口.
接口是通向多個實現的網關,如果想生成適合某個接口的對象,一種典型的方式是工廠方法(Factory Method)設計模式。你不是直接調用構造器,而是在工廠對象上調用創建方法,它可以產生接口實現。這樣,理論上,你的代碼與接口實現完全隔離,從而可以透明地將一種實現替換為另一種實現。下面是一個示例,展示了工廠方法的結構:
// interfaces/Factories.javainterface Service { void method1(); void method2(); }interface ServiceFactory { Service getService(); }class Service1 implements Service { Service1() {} // 包訪問權限 @Override public void method1() {System.out.println("Service1 method1"); } @Override public void method2() {System.out.println("Service1 method2"); } }class Service1Factory implements ServiceFactory { @Override public Service getService() {return new Service1(); } }class Service2 implements Service { Service2() {} // 包訪問權限 @Override public void method1() {System.out.println("Service2 method1"); } @Override public void method2() {System.out.println("Service2 method2"); } }class Service2Factory implements ServiceFactory { @Override public Service getService() {return new Service2(); } }public class Factories { public static void serviceConsumer(ServiceFactory fact) {Service s = fact.getService();s.method1();s.method2(); } public static void main(String[] args) {serviceConsumer(new Service1Factory());// 服務是完全可以互換的:serviceConsumer(new Service2Factory()); } } /* 輸出: Service1 method1 Service1 method2 Service2 method1 Service2 method2 */如果沒有工廠方法,你的代碼必須在某處指定創建Service的確切類型,以調用相應的構造器。
為什么要添加這種額外的間接層?一個常見的原因是創建框架。假設你正在創建一個系統來玩游戲,例如想要在同一個棋盤上玩國際象棋和跳棋:
// interfaces/Games.java // 一個使用了工廠方法的游戲框架interface Game { boolean move(); } interface GameFactory { Game getGame(); }class Checkers implements Game { private int moves = 0; private static final int MOVES = 3; @Override public boolean move() {System.out.println("Checkers move " + moves);return ++moves != MOVES; } }class CheckersFactory implements GameFactory { @Override public Game getGame() { return new Checkers(); } }class Chess implements Game { private int moves = 0; private static final int MOVES = 4; @Override public boolean move() {System.out.println("Chess move " + moves);return ++moves != MOVES; } }class ChessFactory implements GameFactory { @Override public Game getGame() { return new Chess(); } }public class Games { public static void playGame(GameFactory factory) {Game s = factory.getGame();while(s.move()); } public static void main(String[] args) {playGame(new CheckersFactory());playGame(new ChessFactory()); } } /* 輸出: Checkers move 0 Checkers move 1 Checkers move 2 Chess move 0 Chess move 1 Chess move 2 Chess move 3 */如果Games類代表了一段復雜的代碼,這種方式意味著你可以在不同類型的游戲中復用該代碼。可以想象,更加復雜的游戲可以從這種模式中受益。
在下一章中,你將看到實現工廠方法的更優雅的方式,那就是使用匿名內部類。
我看不出來為什么工廠會有這種作用,境界還是太低了
我自己只能理解到工廠將接口和實現代碼完全的分離了,這里的分離指的僅僅只是Game接口和實現Game接口的類,因為這里的Game接口簡單,但我用來實現的那個類不一定簡單,他可能遠比這個復雜得多,而工廠化給出接口類,我自己更便偏向于易讀性
sealed類的子類只能通過下面的某個修飾符來定義。
- final:不允許有進一步的子類。
- sealed:允許有一組密封子類。
- non-sealed:一個新關鍵字,允許未知的子類來繼承它。
注意,一個sealed類必須至少有一個子類。
這個結構還挺有意思,雖然感覺沒啥軟用
一個sealed的基類無法阻止non-sealed的子類的使用,因此可以隨時放開限制:
// interfaces/NonSealed.java // {NewFeature} 從JDK 17開始sealed class Super permits Sub1, Sub2 {} final class Sub1 extends Super {} non-sealed class Sub2 extends Super {} class Any1 extends Sub2 {} class Any2 extends Sub2 {}Sub2允許任意數量的子類,因此它似乎放開了對可以創建的類型的控制。但是,我們還是嚴格限制了sealed類Super的直接子類。也就是說,Super仍然只能有直接子類Sub1和Sub2。
忽然感覺這玩意是專門為了針對向上轉型嗎?
package example; public class one implements a{String s1 = "hello";public one (String s1){this.s1 = s1;}@Overridepublic void cd() {;}public static void main(String[] args){a.c();a.main(args);Super first = new Any1();} } sealed class Super permits Sub1, Sub2 {} final class Sub1 extends Super {} non-sealed class Sub2 extends Super {} class Any1 extends Sub2 {} class Any2 extends Sub2 {}不過我試了下,編譯是能過的,看不懂為什么要有這種新特性
重看了一遍,這個應該是為了畫圖的時候好畫,因為它的修飾符就那么幾個,這樣就可以使類的層次結構更加鮮明,不過不可否認的是,這樣的可拓展性我更感覺還是不太行
我們可能很容易就會覺得接口是好的,因此總是選擇接口而不是具體的類。幾乎任何要創建一個類的場景,都可以創建一個接口和一個工廠來代替。
許多人受到了這種誘惑,只要有可能就創建接口和工廠。這里的邏輯似乎是,你可能會用到不同的實現,因此應該始終添加這層抽象。這是一種過早的設計優化。
任何抽象都應該由真正的需求來驅動。接口應該是在必要時用來重構的東西,而不是在任何地方都多加一個間接層級,進而帶來額外的復雜性。這種額外的復雜性影響很大,如果你讓某人在克服這種復雜性上花費時間,而他最終卻發現你添加接口只不過是為了“以防萬一”,而非出于什么令人信服的其他理由——那好吧,如果我看到這樣的設計,就會開始質疑這個人做過的其他所有設計。
一個比較恰當的指導方針是“優先使用類而不是接口”。從類開始設計,如果很明顯接口是必要的,那么就重構。接口是一個很好的工具,但它很容易被濫用。
第十一章 內部類
更普遍的情況是,外部類有一個方法,該方法返回一個指向內部類的引用,正如在to()和contents()方法中看到的那樣。
// innerclasses/Parcel2.java // 返回一個指向內部類的引用public class Parcel2 { class Contents {private int i = 11;public int value() { return i; } } class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() { return label; } } public Destination to(String s) {return new Destination(s); } public Contents contents() {return new Contents(); } public void ship(String dest) {Contents c = contents();Destination d = to(dest);System.out.println(d.readLabel()); } public static void main(String[] args) {Parcel2 p = new Parcel2();p.ship("Tasmania");Parcel2 q = new Parcel2();// 定義指向內部類的引用:Parcel2.Contents c = q.contents();Parcel2.Destination d = q.to("Borneo"); } } /* 輸出: Tasmania */要在外部類的非靜態方法之外的任何地方創建內部類的對象,必須像在main()中看到的那樣,將對象的類型指定為OuterClassName.InnerClassName。
這里如果不在包外的話,可以直接這么寫
public class Parcel2 {class Contents {private int i = 11;public int value() { return i; }}class Destination {private String label;Destination(String whereTo) {label = whereTo;}String readLabel() { return label; }}public Destination to(String s) {return new Destination(s);}public Contents contents() {return new Contents();}public void ship(String dest) {Contents c = contents();Destination d = to(dest);System.out.println(d.readLabel());}public static void main(String[] args) {Parcel2 p = new Parcel2();p.ship("Tasmania");Parcel2 q = new Parcel2();// 定義指向內部類的引用:Contents c = q.contents();Destination d = q.to("Borneo");} }Sequence是以類的形式包裝起來的定長Object數組。可以調用add()向序列末尾增加一個新的Object(如果還有空間)。要取得 Sequence中的每一個對象,可以使用名為Selector的接口。這是迭代器(Iterator)設計模式的一個例子,我們會在第12章進一步學習。通過Selector,可以檢查是否到了Sequence的末尾(end()),訪問當前Object(current()),以及移動到下一個Object(next())。因為Selector是一個接口,所以其他類可以用自己的方式實現該接口,而且其他方法可以以該接口為參數,來創建更通用的代碼。
人看麻了,有點工廠化方法的味道
內部類的對象在構造時,需要一個指向外圍類對象的引用,如果編譯器無法訪問這個引用,它就會報錯。不過這種情況大多不需要程序員干預。
有一點味道了,因為內部類的對象是從屬于他的外部對象的
學習如何通過內部類返回外部類
要直接創建內部類的對象,你可能會以為,要遵循和前面同樣的形式,使用外部類的名字DotNew,然而事實并非如此。我們要使用外部類的對象來創建內部類的對象,正如我們在示例代碼中所看到的那樣。這也解決了內部類的名字作用域問題,所以我們不用dn.new DotNew.Inner()(確實也不能用)。
看了半天,才發現我看錯了,作者下面解釋的那個是用了內部類創建了外部類,再用外部類對應的方法去生成內部類,而這種創建方法本身邏輯就有點不自洽
其他
總結
以上是生活随笔為你收集整理的on java8学习笔记2022.2.19-2022.2.20的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: on java 8 学习笔记 2022.
- 下一篇: 2020.3.4