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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

JavaSE 接口与内部类

發布時間:2023/12/15 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JavaSE 接口与内部类 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 第十三章 面向對象高級-抽象
    • 13.1 抽象類
      • 13.1.1 抽象類
      • 13.1.2 注意
    • 13.2 接口
      • 13.2.1 接口
      • 13.2.2 注意
      • 13.2.3 默認方法
      • 13.2.4 接口與抽象類的區別
      • 13.2.5 實現接口與繼承類的區別
      • 13.2.6 什么時候使用接口
      • 13.2.7 類與類之間的關系
    • 13.3 內部類
      • 13.3.1 實例內部類
      • 13.3.2 靜態內部類
      • 13.3.3 局部內部類
      • 13.3.4 匿名內部類
      • 13.3.5 內部類如何訪問到外部類的私有字段
      • 13.3.6 如何繼承實例內部類
    • 13.4 lambda 表達式
    • 13.5 方法引用
      • 13.5.1 方法引用
      • 13.5.2 構造器引用
    • 13.x 總結回顧
    • 13.y 腦海練習
  • 參考答案
    • 第十三章答案

第十三章 面向對象高級-抽象

內容導視:

  • 抽象類
  • 接口
  • 內部類
  • lambda 表達式
  • 方法引用

13.1 抽象類

內容導視:

  • 抽象類
  • 注意

13.1.1 抽象類

父類型的引用指向子類型的對象,使得不同子類型的對象調用重寫后的方法后得到不同的結果。為此父類必須聲明一個實例方法,讓子類重寫,但本身不確定如何實現該方法時,可以將其聲明為抽象方法。設計好后,讓子類繼承,并實現抽象方法的具體步驟。

在繼承這節中,Animal 中的 play 方法就是如此。

class Animal { ...public void play() {System.out.println("...");} }

抽象方法聲明:訪問權限修飾符 abstract 返回值類型 方法名(形參列表);

這是一個沒有方法體(沒有實現)的方法,以分號結尾。

當一個類存在抽象方法時,需要將該類聲明為抽象類。抽象類可以被繼承,由子類實現抽象方法。(重寫此方法)

訪問權限修飾符 abstract class 類名 {...} abstract class Animal {public abstract void play(); } class Cat extends Animal {@Overridepublic void play() {System.out.println("飛天遁地,無所不能!");} }

13.1.2 注意

  • 只有抽象類中才能存在抽象方法
  • 抽象類中可以沒有抽象方法
  • 抽象類不能被實例化
  • 非抽象類如果繼承抽象類,必須實現抽象類的所有抽象方法(抽象類可以不用)
  • 抽象方法沒有方法體,不能使用 private、final、static 修飾,否則無法被子類實現
  • 抽象方法的訪問權限最好不要設置為默認級別,否則當其它包下的類繼承抽象類時,無法被訪問到的父類方法無法被實現。

13.2 接口

內容導視:

  • 接口
  • 注意
  • 默認方法
  • 接口與抽象類的區別
  • 實現接口與繼承類的區別
  • 什么時候使用接口
  • 類與類之間的關系

13.2.1 接口

接口是抽象方法的集合,作用是讓其他類實現,用于擴展功能。接口編譯后生成的是字節碼文件。

語法:接口中只有靜態常量和抽象方法。

訪問權限修飾符 interface 接口名 {// 聲明常量public static final 數據類型 常量名 =;// 聲明抽象方法public abstract 返回值類型 方法名(形參列表); }

聲明常量時的 public static final 可以省略不寫;

聲明方法時的 public 修飾符可以省略不寫;

聲明抽象方法時的 abstract 可以省略不寫。

最終簡化為:

訪問權限修飾符 interface 接口名 {// 聲明常量數據類型 常量名 =;// 聲明抽象方法返回值類型 方法名(形參列表); }

接口中沒有代碼塊、構造器,不能被實例化;接口里的常量、方法的訪問權限默認是公開的,不可更改。

這些抽象方法需要被類實現,使用 implements 關鍵字,語法:訪問權限修飾符 class 類名 implements 接口名 {...}

接口中的方法訪問權限是公開的:

interface I1 {int COUNT = 5;void some(); } // 子類必須實現接口中的所有抽象方法 class A implements I1 {/*com.cqh.arr2.A中的some()無法實現com.cqh.arr2.I1中的some()正在嘗試分配更低的訪問權限; 以前為public*/// protected void some() {}@Override// JDK6 以前不可用于標注實現方法public void some() {} }

在 JDK8 及以后,允許接口包含默認方法和靜態方法,訪問權限也默認為 public。

interface I2 {// 默認方法default void some() {System.out.println("接口的 some 方法執行了");}// 靜態方法static int add() {return 0;} }

區分接口、父類中的同名方法:

class B implements I2 {@Overridepublic void some() {System.out.println("子類重寫了 some 方法");// 訪問接口的 some 方法,父類直接使用 super.some()I2.super.some();} }

this.some()是訪問本類的 some 方法,I2.super.some()是訪問本類從 I2 繼承的 some 方法。

區分接口、父類中的同名字段:

interface I1 {int x = 0; } class A {int x = 2; }class B extends A implements I1 {public void s1() {// 對x的引用不明確,A 中的變量 x 和 I1 中的變量 x 都匹配//System.out.println(x);}// 區分接口與父類的同名變量public void s2() {System.out.println(I1.x + "=" + super.x);} }

13.2.2 注意

  • 接口之間支持多繼承,實現類也要實現接口繼承的其它接口的抽象方法。

    interface I1 {void some1(); } interface I2 extends I1 {void some2(); } class A implements I2 {// 需要實現 some2、some1 方法,當然如果繼承的父類已有此方法,就不用實現 }
  • 一個類可以實現多個接口,class A implements I1, I2, I3。

  • 接口類型的引用可以指向實現類型的對象,也算多態。

    interface I3 {default void some() {System.out.println("默認");}void other(); } class C implements I3 {@Overridepublic void some() {System.out.println("重寫");}public void other() {System.out.println("實現");} } class Test {public static void main(String[] args) {invoke(new C());}public static void invoke(I3 i) {i.some();i.other();} }
  • extends 與 implements 可以同時存在,class A extends Object implements I1。

  • 實現類與接口之間滿足 like a 的邏輯關系,例:魚實現了翅膀這個接口,擁有了飛翔的功能,魚變得可以像鳥一樣飛翔。

    當子類繼承了父類,就自動擁有了父類的功能。如果子類需要擴展功能,可以通過實現接口的方式擴展。

  • 如果繼承的父類與接口存在同一個方法,調用此方法時父類優先。

13.2.3 默認方法

當實現接口時,只需其中一個方法,總是強制重寫所有抽象方法未免有些煩人;可以使用抽象類先實現幾個抽象方法,以后所有需要實現此接口的類,直接繼承抽象方法即可。

interface I1 {void some1();void some2();void some3();void some4(); } abstract class A1 implements I1 {@Overridepublic void some1() {}@Overridepublic void some2() {}@Overridepublic void some3() {} } class C1 extends A1 {@Overridepublic void some4() {} }

default 作用就在于此,將不太重要的方法聲明為 default,而不必讓子類全部實現。

interface I1 {default void some1() {}default void some2() {}default void some3() {}void some4(); } class C1 implements I1 {@Overridepublic void some4() {} }

另一種用法,當使用者實現了一個接口,后來接口又擴充了一個方法,則之前所有實現它的類,則需要實現新增的方法,否則就會報錯;如果擴充的方法聲明為 default,就不必擔心對使用者造成影響。

13.2.4 接口與抽象類的區別

比較接口抽象類
組合可以實現多個接口只能繼承一個類
狀態只有靜態常量任意
構造器
代碼塊
訪問權限public任意

13.2.5 實現接口與繼承類的區別

接口和繼承解決的問題不同

繼承的價值主要在于:解決代碼的復用性和可維護性。

接口的價值主要在于:設計好各種規范(方法),讓其它類去實現這些方法。

接口比繼承更加靈活

接口比繼承更加靈活,繼承是滿足 is-a 的關系,天生決定。而接口只需滿足 like-a 的關系。

13.2.6 什么時候使用接口

  • 類只支持單繼承,當已經繼承了其它類時,只有實現接口才能擴展功能。

  • 制定規范,讓其它人去實現;消費者直接調用,而不必關心提供者。

  • 方法形參使用接口類型,用于解耦合,因為接口類型的引用可以指向實現接口的類的對象

  • 解耦合:降低程序的耦合度,提高程序的擴展力

    例1:解耦合

    修改之前:

    class Person {public void play(Swim swim) {swim.play();}public void play(Run run) {run.play();}// 新增其它運動需要新增方法 }// 如果 Swim 類修改了,Person 中有關 Swim 的部分可能會受到影響 class Swim {public void play() {System.out.println("游泳中...");} } // 同理 class Run {public void play() {System.out.println("跑步中...");} }class Test {public static void main(String[] args) {Person zs = new Person();zs.play(new Run());} }

    修改方案 1,使用關聯,使用接口類型的變量作為實例變量,直接調用它的方法。

    interface Sport {void play(); } class Swim implements Sport {public void play() {System.out.println("游泳中...");} } class Run implements Sport {public void play() {System.out.println("跑步中...");} } class Person {Sport sport;public void setSport(Sport sport) {this.sport = sport;}public void play() {sport.play();} } class Test {public static void main(String[] args) {Person zs = new Person();zs.setSport(new Run());zs.play();} }

    修改方案 2,作為形參,根據傳進的實參不同,調用的 play 方法也不同。

    class Person {public void play(Sport sport) {sport.play();} } class Test {public static void main(String[] args) {Person zs = new Person();// 如果需要其它的運動,可以編寫一個類繼承 Sport 接口// 不用修改 Person 類,只用傳入實參即可zs.play(new Swim());} }

    例2:制定規范

    設計一系列公開的接口:

    interface Driver {Connection connect(String name); } interface Connection {void open();void close(); }class Manager {static Driver driver;public static void register(Driver driver) {Manager.driver = driver;}// 面向接口調用,讓制定規則的人、使用者不用關心實現類是誰public static Connection getConnection(String name) {return driver.connect(name);} }

    這時來個張三實現接口:

    class ZsDriver implements Driver {public Connection connect(String name) {System.out.println("使用張三寫的代碼連接數據庫成功!");return new ZsConnection(name);} } class ZsConnection implements Connection {private String name;public ZsConnection(String name) {this.name = name;}public void open() {System.out.println(name + "打開了通道");}public void close() {System.out.println(name + "關閉了連接");} }

    使用者編寫程序,選擇一個實現者,按接口聲明的規范調用方法。

    class Test{public static void main(String[] args) {Manager.register(new ZsDriver());Connection conn = Manager.getConnection("路人甲");conn.open();conn.close();} }

    如果又來個李四實現接口,只需要改動測試類的一個地方,不需要改 Manager 類和規范。

    Manager.register(new LsDriver());

    以后通過讀取配置文件,自動創建對象并傳入 register 方法,連代碼都不需要改。

    例3:擴展功能

    class Animal {int age;int type; } interface Wing {void fly(); }// 如果貓已經繼承了 Animal 類,想要飛,只能實現接口,而不能再繼承鳥類 class Cat extends Animal implements Wing {public void fly() {System.out.println("本貓也會飛了...");} }

    13.2.7 類與類之間的關系

    多用組合,少用繼承

    依賴:A 類的方法中使用 B 類型的對象,如上學時借助公交車。

    class A {public void some() {B b = new B(數據1, 數據2);b.invoke();} }

    關聯(組合):B 類作為 A 類的成員變量。

    class A {B b; }

    聚合:特殊的關系,B 類組成的集合作為 A 類的成員變量,是部分與整體的關系;每個 b 的生命周期不由 a 決定;如同我與我擁有的書的關系:我沒來時,書在,我不在了,書還在。

    class Person {List<Book> bookList = new ArrayList<>(3);// 人的腦海static int count;// 人的數量String name;// 人的姓名public Person(String name) {this.name = name;}public Book end() {return new Book(name + (++count));}public void selection(Book[] books) {for (int i = 0; i < books.length; i++) {bookList.add(books[i]);}} } class World {public static void main(String[] args) {Book b1 = new Book("月亮是如何練成的");Book b2 = new Book("太陽的燃燒");Book b3 = new Book("語法快速入門");Book b4 = new Book("惡魔法則");Book b5 = beginToEnd("我", b1);// 人不在了,書還在Book b6 = beginToEnd("你", b2, b5);Book b7 = beginToEnd("他", b1, b2, b3, b4, b6);}public static Book beginToEnd(String name, Book... books) {Person p = new Person(name);p.selection(books);// 人收集書籍return p.end();} }

    合成:特殊的聚合,B 類組成的集合作為 A 類的成員變量,部分與整體的關系;每個 b 的生命周期由 a 決定;如同我與我的四肢的關系。

    實現:實現類與接口的關系。

    class A implements B {}

    繼承(泛化):父子類關系,A

    class A extends B {} 關系UML 連接符
    依賴人----->車
    關聯人——>狗
    聚合車?——>輪胎
    合成人?——>四肢
    實現實現類-----?接口
    繼承子類——?父類

    雙向關聯可以雙箭頭,也可以無箭頭。

    class A {B b; } class B {A a; }

    13.3 內部類

    內容導視:

    • 實例內部類
    • 靜態內部類
    • 局部內部類
    • 匿名內部類
    • 內部類如何訪問到外部類的私有字段
    • 如何繼承實例內部類

    定義在類中的類稱為內部類,外面的類稱為外部類。如同正常類使用即可,編譯也會生成 .class 文件。

    class OutClass {// 外部類static interface InnerInterface2 {// 內部接口(static 可以省略,接口默認是靜態成員)} }

    內部類可以訪問外部類的私有變量,對同一個包下的類隱藏。

    13.3.1 實例內部類

    實例內部類定義在外部類的成員位置,地位等同于實例變量,由于靜態方法不能直接訪問實例相關的,需要通過外部類的實例.new InnerClass()訪問。

    訪問權限修飾符 class 外部類類名 {訪問權限修飾符 class 內部類類名 {...} }

    實例內部類可以直接訪問外部類的所有成員,包括私有的;作用域為整個類體。

    創建內部類對象:

    // 外部類中 class OutClass {class InnerClass {}// 實例方法中public void some() {InnerClass inner = new InnerClass();}// 靜態方法中public static void some2() {OutClass out = new OutClass();InnerClass inner = out.new InnerClass();// new OutClass().new InnerClass();} } // 外部其它類中 class B {public static void main(String[] args) {OutClass out = new OutClass();OutClass.InnerClass inner = out.new InnerClass();} }

    實例內部類不支持靜態相關的聲明,除了靜態常量(以字面量的方式賦值)。編譯后生成 OutClass$InnerClass.class 文件。

    最好不要使用 $ 作為標識符,$ 是用于編譯器自定義類名、變量名,以免沖突。

    實例內部類不能被繼承外部類的類重寫。

    class Out {class Inner {public void some() {System.out.println("最開始的內部類");}} } class Out2 extends Out {class Inner {public void some() {System.out.println("重寫后的內部類");}} } class Test {public static void main(String[] args) {Out out = new Out2();Out.Inner inner = out.new Inner();inner.some();// 最開始的內部類} }

    13.3.2 靜態內部類

    靜態內部類定義在外部類的成員位置,有 static 修飾,地位等同于靜態變量,使用 new 外部類類名.內部類類名()訪問。

    當不需要直接訪問外部類的實例變量或方法時可以定義為靜態內部類。

    訪問權限修飾符 class 外部類類名 {訪問權限修飾符 static class 內部類類名 {...} }

    靜態內部類可以直接訪問外部類所有的靜態成員,包括私有的;作用域為整個類體。

    創建靜態內部類對象:

    // 外部類中 class OutClass {static class InnerClass {public static int i = 3;}// 方法中public static void some() {InnerClass inner = new InnerClass();} } // 外部其它類中 class B {public static void main(String[] args) {OutClass.InnerClass inner = new OutClass.InnerClass();int i = OutClass.InnerClass.i;} }

    編譯后生成 OutClass$InnerClass.class 文件。

    13.3.3 局部內部類

    局部內部類定義在外部類的局部位置,地位等同于局部變量,可以使用 final 修飾。

    訪問權限修飾符 class 外部類類名 {方法中、代碼塊中 {class 內部類類名 {...}} }

    在實例方法、實例代碼塊、構造器內聲明的內部類可以直接訪問外部類的所有成員,包括私有的;在靜態方法、靜態代碼塊內聲明的內部類只能訪問靜態成員;

    局部內部類只在定義內部類時的方法或代碼塊內有效。

    創建內部類對象:

    class OutClass {public void some() {// 僅在此方法內有效class InnerClass {}InnerClass inner = new InnerClass();} }

    局部內部類不支持靜態相關的聲明,除了靜態常量(以字面量的方式賦值)。

    所在方法內,局部內部類只能使用值不會改變的局部變量。(使用的變量本質是 final 變量,使得局部變量與在局部類內建立的拷貝保持一致)

    public static void main(String[] args) {int i = 3;abstract class InnerClass {// 閉包public void some() {// i++;從內部類引用的本地變量必須是最終變量或實際上的最終變量System.out.println(i);}} }

    通過反編譯可以看出,引用的本地變量 i 存儲在內部類的成員變量 final int val$i;,且在構造器中賦的值。

    abstract class com.cqh.arr1.Out$1InnerClass {final int val$i;com.cqh.arr1.Out$1InnerClass();Code:0: aload_01: iload_12: putfield #1 // Field val$i:I5: aload_06: invokespecial #2 // Method java/lang/Object."<init>":()V9: return

    在計算機科學中,閉包(Closure),又稱詞法閉包(Lexical Closure)或函數閉包(function closures),是引用了自由變量的函數。

    簡單來說就是當一個方法引用了方法局部變量外的變量時,它就是一個閉包。閉包是由方法和與其相關的引用環境(方法外變量)組合而成的實體。

    13.3.4 匿名內部類

    匿名內部類是一種局部內部類,類名是隱匿的,所以沒有構造器,作為子類或實現類,定義匿名內部類的同時也會創建該類的實例,一次性用品。

    作為子類:

    父類類名 引用名 = new 父類類名(實參列表) {子類類體... };

    作為實現類:

    接口名 引用名 = new 接口名() {實現類類體... };

    例:

    interface I1 {void some(); } class Test {public static void main(String[] args) {// 生成了一個實現 I1 接口的匿名類,創建了此匿名類的對象后調用 some 方法new I1() {@Overridepublic void some() {System.out.println("實現 some 方法...");}}.some();// 局部內部類class A implements I1 {@Overridepublic void some() {System.out.println("實現 some 方法 2...");}};new A().some();} }

    編譯后生成 OutClass$1.class 文件,從 1 開始,每遇見匿名內部類就加一。

    當不想編寫大量代碼時,可以使用匿名內部類更加方便快捷:

    // 之前 class ArrayList2<String> extends ArrayList {{add("zs");add("ls");} } System.out.println(new ArrayList2());// 之后 System.out.println(new ArrayList<String>(){{add("zs"); add("ls");}});

    13.3.5 內部類如何訪問到外部類的私有字段

    class Out {private String name;class Inner {public void some() {name = "g";System.out.println(name);}private Inner() {}}public static void main(String[] args) {new Out().new Inner().some();} }

    編譯后生成 Out.class,Out$Inner.class,反編譯后如下:

    class Out {private java.lang.String name;Out();public static void main(java.lang.String[]);static java.lang.String access$002(Out, java.lang.String);static java.lang.String access$000(Out); } class Out$Inner {final Out this$0;public void some();private Out$Inner(Out);Out$Inner(Out, Out$1); }

    還原真實的代碼:

    class Out {private String name;public static void main(String[] args) {new Inner(new Out(), null).some();}// 此方法用于賦值static String access$002(Out out, String str) {out.name = str;return str;}// 此方法用于訪問私有字段static String access$000(Out out) {return out.name;} } class Inner {// 需要外部類的引用final Out this$0;public void some() {// 給外部類引用的字段賦值Out.access$002(this$0, "g");System.out.println(Out.access$000(this$0));}// 外部類引用通過構造器賦值private Inner(Out out) {this$0 = out;}Inner(Out out, Out out2) {this(out);} }

    可以看見外部類生成了默認訪問權限的 access$xxx 方法供內部類訪問。如果想要修改本類的 private 字段,代碼需要與 Out 類放置在同一個包中。

    假如我現在已有 Out 類源碼,正好此類的內部類訪問了外部類的私有變量,我可以將復制此類,將源碼中的內部類去掉,手動添加 access$xxx 方法。

    package com.cqh; // 想要訪問 Out.class 中的私有字段 class Out {private boolean flag = true;class Inner {public void other() {if (flag) {System.out.println("hello");flag = false;}}} }

    在任意地方新建 com/cqh,復制此類到 cqh 目錄下,去掉內部類后編譯生成 class 文件。

    package com.cqh;// 與原 Out.class 在同一個包下 // 另一個地方的 Out.java class Out {private boolean flag = true;static boolean access$000(Out out) {return out.flag;}static boolean access$002(Out out, boolean flag) {out.flag = flag;return out.flag;} } // 攻擊代碼 class Invade {public static void main(String[] args) {Out out = new Out();out.access$002(out, false);System.out.println(out.access$000(out));} }

    將編譯后生成的 Invade.class 放置在 Out.class 所在目錄下(實際目錄);就可以訪問、修改 Out 類的私有字段。

    有人說,那我豈不是把類放在 java.xxx 包下,就可以修改 JDK 源碼的私有字段嗎?

    不可以,因為有安全機制,包名不允許以 java. 開頭:

    Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.awt 安全異常,禁止的包名稱

    13.3.6 如何繼承實例內部類

    通過上節的還原真實的代碼,可以看出實例內部類必須傳入外部類的引用,在構造器中完成賦值。

    所以繼承實例內部類時,需要使用特殊的語法,將外部類引用傳遞進去。

    class Out {class Inner {// 調用的是此構造public Inner() {}} } class Other extends Out.Inner {public Other(Out out) {// 調用 Out$Inner 類的無參構造,將外部類引用作為參數傳遞out.super();} }

    13.4 lambda 表達式

    JDK8 推出的新特性,目的是簡化創建函數式接口的實現類對象的代碼,一般作為實參使用。可以看作是簡化的匿名內部類。

    函數式接口(functional interface):只包含一個抽象方法的接口(可以有默認方法和靜態方法)

    函數式接口可以加 @FunctionalInterface 注解,如果無意增加了另一個抽象方法會報錯。

    @FunctionalInterface interface I1 {void some(); }

    lambda 語法:接口名 引用名 = (形參列表) -> {方法體}

    規則

    • 可以省略形參的數據類型

    • 當形參只有一個時,可以省略小括號

    • 當方法體只有一條語句時,可以省略花括號

      • 當這條語句為 return xxx 時,可以省略 return
      interface Math {int add(int n1, int n2); }Math math = (n1, n2) -> n1 + n2; Math math2 = (n1, n2) -> {return n1 + n2;} Math math3 = new Math() {@Overridepublic int add(int n1, int n2) {return n1 + n2;} };

    例:

    interface I1 {void some(); } class Test {public static void main(String[] args) {// lambda 表達式I1 i1 = () -> {System.out.println("實現 some 方法");};// 匿名內部類I1 i2 = new I1() {@Overridepublic void some() {System.out.println("實現 some 方法 2...");}};// 作為實參invoke(() -> System.out.println("實現 some 方法 3..."));}public static void invoke(I1 i) {i.some();} }

    13.5 方法引用

    內容導視:

    • 方法引用
    • 構造器引用

    JDK 8 新特性,當 lambda 表達式中只有一條語句時,可以使用此特性,例:

    interface I1 {void some(int num); } class Test {public static void main(String[] args) {I1 i = System.out::println;I1 i2 = num -> System.out.println(num);} }

    13.5.1 方法引用

    只有一條調用方法的語句時。

    有三種形式:

    • 引用::實例方法名
    • 類名::靜態方法名
    • 類名::實例方法名

    前兩種方式,形參繼續傳遞給調用的方法:

    interface I1 {int add(int n1, int n2); } class A {public static int add(int n1, int n2) {return n1 + n2;} } class Test {public static void main(String[] args) {I1 i1 = (n1, n2) -> A.add(n1, n2);// 等價于下面的語句I1 i2 = A::add;} }

    后一種方式,第一個形參作為對象引用,其它參數傳遞給調用的方法:

    interface I1 {void init(A a, int age); } class A {int age;public void setAge(int age) {this.age = age;} } class Test {public static void main(String[] args) {I1 i1 = (a, age) -> a.setAge(age);I1 i2 = A::setAge;} }

    13.5.2 構造器引用

    只有一條返回對象的語句時。

    語法:類型::new

    傳進來的實參作為構造器的實參。

    interface I1 {A init(int age); } class A {int age;public A(int age) {this.age = age;} } class Test {public static void main(String[] args) {I1 i1 = age -> new A(age);I1 i2 = A::new;} }

    13.x 總結回顧

    某實例方法只是為了讓子類重寫,自身不確定如何實現,可以聲明為抽象方法。

    接口里的常量、方法都是 public 修飾的;當不想強迫子類實現某方法時,可以使用 default。

    接口作為規范讓子類實現,用于擴展功能。

    匿名內部類很常見,用于簡化創建對象,Lambda 表達式對此簡化更近一步,用于函數式接口。

    13.y 腦海練習

    13.1 控制臺上輸出?

    1)

    class Student {final String name;Vehicle vehicle;public Student(String name) {this.name = name;}public void goToSchool() {if (!(vehicle instanceof Bike)) {this.vehicle = VehiclesFactory.getBike();}System.out.print(this.name);vehicle.work("學校");}public void flying() {this.vehicle = VehiclesFactory.getBambooDragonfly();System.out.print(this.name); vehicle.work("飛天");} }interface Vehicle {void work(String goal); } class Bike implements Vehicle {@Overridepublic void work(String goal) {System.out.println("騎著老舊的小自行車嘎吱嘎吱緩緩地駛向了" + goal);} } class VehiclesFactory {private static final Bike bike = new Bike();public static Vehicle getBike() {return bike;}public static Vehicle getBambooDragonfly() {return goal -> System.out.println("轉著竹蜻蜓" + goal + "~~~");} } class Test {public static void main(String[] args) {Student zs = new Student("張三");zs.goToSchool();zs.flying();} }

    2)

    abstract class World {public final void life() {goToSchool();goToWork();toGetMarried();toHaveChildren();die();}protected abstract void goToSchool();protected abstract void goToWork();protected abstract void toGetMarried();protected abstract void toHaveChildren();protected abstract void die(); } class Loser extends World {String name;public Loser(String name) {this.name = name;}@Overridepublic void goToSchool() {System.out.println(name + "滿懷疲憊地上學了,成績倒數...");}@Overridepublic void goToWork() {System.out.print("末尾淘汰制,因業績墊底," + name +"被辭退了;");System.out.println("但" + name + "絲毫不慌,因為窮不過三代。");}@Overridepublic void toGetMarried() {System.out.println("父母催著結婚,于是" + name + "急著結婚了" +",盡管不知道如何撫養孩子,想著給錢就行了...");}@Overridepublic void toHaveChildren() {System.out.print(name + "在外地辛苦工作,妻子半輩子守在農村;似乎孩子不怎么認" + name + "了...");System.out.println("哦,原來半生只是一場夢...");}@Overridepublic void die() {System.out.print(name + "在陽臺上安詳地曬著太陽,突然就沒了生息...");System.out.print("可惜高樓大廈,每層人家門戶緊閉,沒有人注意到" + name + "的逝去,");System.out.println("而" + name + "家里除他以外再沒有人了...");} } class Test {public static void main(String[] args) {new Loser("三代目").life();} }

    3)

    class Arr {int[] arr;public void foreach(I2 i2) {Objects.requireNonNull(i2);for (int i = 0; i < arr.length; i++) {i2.some(arr[i]);}}public Arr(int[] arr) {Objects.requireNonNull(arr);this.arr = arr;} } interface I2 {void some(int element); } class Test {public static void main(String[] args) {Arr a = new Arr(new int[]{4, 6, 8});a.foreach((element) -> {System.out.println("遍歷的元素為:" + element);});} }

    4)

    interface Math {int invoke(int n1, int n2); }static Math math = (n1, n2) -> {Math math1 = (a1, b1) -> a1 + b1;Math math2 = (x1, y1) -> x1 * y1 - x1;return math1.invoke(n1, n2) - math2.invoke(n1, n2); };public static void main(String[] args) {System.out.println(math.invoke(5, 6)); }

    5)

    interface IntConsumer {void run(int value); } public static void repeat(int n, IntConsumer action) {for (int i = 0; i < n; i++) {action.run(i);} } public static void main(String[] args) {repeat(10, i -> System.out.println("down:" + (9 - i))); }

    6)

    interface TypeC {void work(); } interface MicroUSB {void work2(); }class Phone implements MicroUSB {@Overridepublic void work2() {System.out.println("充電中...");} } class Adapter implements TypeC {Phone phone;public Adapter(Phone phone) {Objects.requireNonNull(phone);this.phone = phone;}@Overridepublic void work() {phone.work2();} } class Test {public static void main(String[] args) {Phone phone = new Phone();charge(new Adapter(phone));}public static void charge(TypeC typeC) {typeC.work();} }

    參考答案

    第十三章答案

    13.1 控制臺上輸出?

    1)

    class Student {final String name;Vehicle vehicle;public Student(String name) {this.name = name;}public void goToSchool() {if (!(vehicle instanceof Bike)) {this.vehicle = VehiclesFactory.getBike();}System.out.print(this.name);vehicle.work("學校");}public void flying() {this.vehicle = VehiclesFactory.getBambooDragonfly();System.out.print(this.name); vehicle.work("飛天");} }interface Vehicle {void work(String goal); } class Bike implements Vehicle {@Overridepublic void work(String goal) {System.out.println("騎著老舊的小自行車嘎吱嘎吱緩緩地駛向了" + goal);} } class VehiclesFactory {private static final Bike bike = new Bike();public static Vehicle getBike() {return bike;}public static Vehicle getBambooDragonfly() {return goal -> System.out.println("轉著竹蜻蜓" + goal + "~~~");} } class Test {public static void main(String[] args) {Student zs = new Student("張三");zs.goToSchool();zs.flying();} }

    zs 調用 goToSchool 方法,vehicle 默認為 null,if 條件為 true,進入;調用 getBike 方法,返回 Bike 類型的對象,賦給了 zs.vehicle;

    vehicle.work(),vehicle 實際類型為 Bike,調用的是 Bike 類中的 work 方法,輸出 張三騎著老舊的小自行車嘎吱嘎吱緩緩地駛向了學校

    zs 調用 flying 方法,getBambooDragonfly 返回的是實現 Vehicle 接口的匿名內部類的對象,賦給了 zs.vehicle;

    調用 vehicle.work 方法,輸出 張三轉著竹蜻蜓飛天~~~


    2)在接口或抽象類中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

    主要解決:一些方法通用,卻在每一個子類都重新寫了這一方法。

    關鍵代碼:在抽象類實現,其他步驟在子類實現。

    優點:1、封裝不變部分,擴展可變部分。 2、提取公共代碼,便于維護。 3、行為由父類控制,子類實現。

    缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。

    使用場景:1、有多個子類共有的方法,且邏輯相同。 2、重要的、復雜的方法,可以考慮作為模板方法。

    注意事項:為防止惡意操作,一般模板方法都加上 final 關鍵詞。

    如果子類重寫的方法,邏輯大致相同,那沒必要每個類都寫一遍,可以提取公共部分,在父類中定義一個骨架,某個步驟延遲到子類實現。

    abstract class World {public final void life() {goToSchool();goToWork();toGetMarried();toHaveChildren();die();}protected abstract void goToSchool();protected abstract void goToWork();protected abstract void toGetMarried();protected abstract void toHaveChildren();protected abstract void die(); } class Loser extends World {String name;public Loser(String name) {this.name = name;}@Overridepublic void goToSchool() {System.out.println(name + "滿懷疲憊地上學了,成績倒數...");}@Overridepublic void goToWork() {System.out.print("末尾淘汰制,因業績墊底," + name +"被辭退了;");System.out.println("但" + name + "絲毫不慌,因為窮不過三代。");}@Overridepublic void toGetMarried() {System.out.println("父母催著結婚,于是" + name + "急著結婚了" +",盡管不知道如何撫養孩子,想著給錢就行了...");}@Overridepublic void toHaveChildren() {System.out.print(name + "在外地辛苦工作,妻子半輩子守在農村;似乎孩子不怎么認" + name + "了...");System.out.println("哦,原來半生只是一場夢...");}@Overridepublic void die() {System.out.print(name + "在陽臺上安詳地曬著太陽,突然就沒了生息...");System.out.print("可惜高樓大廈,每層人家門戶緊閉,沒有人注意到" + name + "的逝去,");System.out.println("而" + name + "家里除他以外再沒有人了...");} } class Test {public static void main(String[] args) {new Loser("三代目").life();} }

    Loser 類中沒有 life 方法,所以調用的是 World 接口中的 life 方法,內部的這些方法已被 Loser 重寫,所以依次調用 Loser 類中的 goToSchool、goToWork、toGetMarried … 等方法。


    3)

    class Arr {int[] arr;public void foreach(I2 i2) {Objects.requireNonNull(i2);for (int i = 0; i < arr.length; i++) {i2.some(arr[i]);}}public Arr(int[] arr) {Objects.requireNonNull(arr);this.arr = arr;} } interface I2 {void some(int element); } class Test {public static void main(String[] args) {Arr a = new Arr(new int[]{4, 6, 8});a.foreach((element) -> {System.out.println("遍歷的元素為:" + element);});} }

    有參構造賦值 a.arr = {4,6,8}

    調用 foreach,遍歷數組,每次遍歷調用 i2.some 方法,輸出 遍歷的元素為:4、遍歷的元素為:6、遍歷的元素為:8


    4)

    interface Math {int invoke(int n1, int n2); }static Math math = (n1, n2) -> {Math math1 = (a1, b1) -> a1 + b1;Math math2 = (x1, y1) -> x1 * y1 - x1;return math1.invoke(n1, n2) - math2.invoke(n1, n2); };public static void main(String[] args) {System.out.println(math.invoke(5, 6)); } math.invoke(5, 6) 返回值為 math1.invoke(5, 6) - math2.invoke(5, 6);math1.invoke(5, 6) 返回值為 5 + 6 = 11; math2.invoke(5, 6) 返回值為 5 * 6 - 5 = 25;所以返回值為 11 - 25 = -14

    5)

    interface IntConsumer {void run(int value); } public static void repeat(int n, IntConsumer action) {for (int i = 0; i < n; i++) {action.run(i);} } public static void main(String[] args) {repeat(10, i -> System.out.println("down:" + (9 - i))); }

    repeat 方法中每次遍歷調用 action 的 run 方法,傳入 0、1、2、…、9

    輸出 down:9、down:8、… down:0


    6)

    interface TypeC {void work(); } interface MicroUSB {void work2(); }class Phone implements MicroUSB {@Overridepublic void work2() {System.out.println("充電中...");} } // 優先使用組合,而不是繼承 class Adapter implements TypeC {Phone phone;public Adapter(Phone phone) {Objects.requireNonNull(phone);this.phone = phone;}@Overridepublic void work() {phone.work2();} }/*class Adapter extends Phone implements TypeC {@Overridepublic void work() {super.work2();} }*/ class Test {public static void main(String[] args) {Phone phone = new Phone();charge(new Adapter(phone));// 充電中...}public static void charge(TypeC typeC) {typeC.work();} }

    由于 Phone 不是 TypeC 類型,無法作為 charge 方法的實參;要想充電,必須借助實現 Typec 接口的適配器 Adapter,通過適配器調用 Phone 的方法。

    總結

    以上是生活随笔為你收集整理的JavaSE 接口与内部类的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。