201871010132-張瀟瀟-《面向對象程序設計(java)》第八週總結

201871010132-張瀟瀟《面向對象程序設計(java)》第八週學習總結html

項目
內容
這個做業屬於哪一個課程 https://www.cnblogs.com/nwnu-daizh/
這個做業的要求在哪裏 http://www.javashuo.com/article/p-fglwhfdo-hz.html
做業學習目標
  1. 掌握接口定義方法;
  2. 掌握實現接口類的定義要求;
  3. 掌握實現了接口類的使用要求;
  4. 理解程序回調設計模式;
  5. 掌握Comparator接口用法;
  6. 掌握對象淺層拷貝與深層拷貝方法;
  7. 掌握Lambda表達式語法;
  8. 瞭解內部類的用途及語法要求。

 

 

 

 

 

 

 

 

 第一部分:總結第六章理論知識java

6.1 抽象類

抽象類是指定義時有 abstract 修飾的類,例如:程序員

public abstract class Person{ ... public abstract String getDescription();//注意末尾沒有花括號,而有分號 }

在定義中有abstract修飾符的方法是抽象方法。抽象類中能夠包含實例變量和實例方法,甚至能夠沒有抽象方法,可是有抽象方法的類必定要定義爲抽象類。算法

抽象方法充當着佔位的角色,它們的具體實如今子類中。抽象方法不能有方法體,即沒有花括號,但必須有分號,方法定義變成了方法聲明。擴展抽象類能夠有兩種選擇:express

  1. 若子類只實現了父類中部分抽象方法,此時子類也爲抽象類
  2. 若子類中實現了父類中全部抽象方法,此時子類爲抽象類的實現類,子類是一個普通類,再也不是抽象類。

抽象類不能實例化(由於抽象方法沒有具體實現,即便抽象類中不包含抽象方法),能夠定義抽象類的變量,但它只能引用其實現類的對象。編程

抽象類能夠包含實例變量、實例方法、類變量、靜態方法、構造器、靜態初始化塊、普通初始化塊、內部類。抽象類的構造器不能用於建立實例,主要是用於被其子類調用。在抽象類中,實例方法能夠調用抽象方法。如下面的代碼爲例,由於子類CarSpeedMeter實現了抽象方法getRadius,至關於子類覆蓋了該方法,從而具備多態性,子類對象調用getSpeed時,getSpeed中調用的getRadius方法是子類中的getRadius設計模式

public abstract class SpeedMeter{ private double turnRate; public abstract double getRadius(); public double getSpeed(){ return Math.PI * 2 * getRadius(); } } public class CarSpeedMeter extends SpeedMeter{ public double getRadius(){ ... } }

6.2 接口

6.2.1 Java 8中接口的定義

定義接口時再也不使用class關鍵字,而使用interface關鍵字。接口定義的基本語法以下:數組

[修飾符] interface 接口名 extends 父接口1,父接口2,...{ 零個到多個靜態常量定義。。。 零個到多個抽象方法定義... 零個到多個默認方法定義...//僅在java8中容許 零個到多個內部類、接口、枚舉定義 }

因爲接口裏定義的是多個類共同的公共行爲規範,所以接口裏全部成員都是默認public訪問權限。接口裏不包含成員變量,構造器和初始化塊。接口裏只能包含靜態常量、抽象實例方法、類方法、默認方法、內部類、內部接口、內部枚舉。安全

靜態常量能夠省略public static final,系統默認添加。且靜態常量只能在定義時初始化。閉包

對於抽象方法,系統默認添加public abstract修飾,所以,抽象方法不能有方法體。從而,實現類覆蓋這些抽象方法時,訪問權限必須是public

默認方法須要加default修飾符,但不能有static修飾符,不然,和類方法沒有區別。系統默認添加public修飾。以鼠標監聽接口MouseListener來講明默認方法存在的目的,MouseListener包含5個接口:

interface MouseListener{ void mouseClicked(MouseEvent event); void mousePressed(MouseEvent event); void mouseReleased(MouseEvent event); void mouseEntered(MouseEvent event); }

大多數狀況下,只須要關心前兩個接口,在Java8中能夠把全部方法聲明爲默認方法,這些方法什麼都不作:

interface MouseListener{ default void mouseClicked(MouseEvent event){} default void mousePressed(MouseEvent event){} default void mouseReleased(MouseEvent event){} default void mouseEntered(MouseEvent event){} }

從而,實現此接口的程序員只須要重寫他們真正關心的方法。默認方法能夠調用其餘的默認或抽象方法。

靜態方法是java8增長的功能,在此以前,Java多會給接口實現一個伴隨類中,並將靜態方法放在伴隨類中。當容許在接口中定義靜態方法時,這些伴隨類將再也不須要。

接口裏的內部類、內部接口、內部枚舉,系統默認添加public static修飾,由於不能建立接口實例。

定義接口的示例以下:

public interface Output{ int MAX_CACHE_LINE = 50; void out(); static String staticTest(){ return "類方法"; } default void test(){ System.out.println("默認方法"); } }
6.2.2 接口的繼承

接口支持多繼承,一個接口能夠有多個直接父接口,但接口只能繼承接口,不能繼承類。子接口擴展父接口時,將會得到父接口全部的默認方法和抽象方法。

一個接口繼承多個接口時,多個接口排在extends關鍵字以後,多個父接口之間以英文逗號隔開。示例以下:

interface A{ int PRO_A = 5; void testA(); } interface B{ int PRO_B = 6; void testB(); } interface C extends A,B{ int PRO_C = 7; void testC(); }
6.2.3 使用接口

接口不能建立實例,但接口能夠聲明變量。但接口聲明的變量必須引用到其實現類的對象。除此以外,接口的主要功能是被實現類實現。接口的主要用途概括以下:

  • 定義變量,也可用於強制類型轉換。
  • 調用接口中定義的靜態常量和靜態方法。
  • 被其餘類實現。

一個類能夠實現一個或多個接口,繼承使用extends,實現則使用implements。容許一個類實現多個接口,能夠彌補java單繼承的不足,同時避免多繼承的複雜性和低效性。類實現接口的語法格式以下:

[修飾符] class 類名 extends 父類 implements 接口1,接口2...{ 類體部分 }

實現接口與繼承父類類似,同樣能夠得到所實現接口裏定義的靜態常量、方法(抽象方法和默認方法)。

讓類實現接口須要類定義後增長implements部分,當須要實現多個接口時,多個接口之間以英文逗號隔開,一個類能夠繼承父類,並同時實現多個接口,implements部分必須方法extends部分以後。

一個類實現類一個或多個接口以後,這個類必須徹底實現這些接口裏定義的所有抽象方法(即重寫這些抽象方法,包括接口從父接口繼承獲得的抽象方法);不然,該類必須定義爲抽象類。

接口不能顯式繼承任何類,但全部接口類型的變量均可以直接賦給Object類型的變量,由於編譯器知道接口類型變量的運行時類型一定是實現類對象,而任何Java對象都必須是Object或其子類的實例。

6.2.4 接口和抽象類

接口和抽象類很像,他們的共同點有:

  • 接口和抽象類都不能被實例化。
  • 接口和抽象類均可以包含抽象方法,實現接口或繼承抽象類的子類都必須實現這些方法。

但接口和抽象類的差異很大,主要體如今二者的設計目的上:

  • 接口定義了一組規範,要求實現者必須提供哪些服務。當在程序中使用接口時,接口是多個模塊間的耦合標準。
  • 抽象類做爲多個子類的共同父類,所體現的是一種模板式設計。

接口和抽象類在用法上的區別以下:

  • 接口中全部成員的訪問權限默認爲public,而抽象類中各類訪問權限均可以。
  • 接口裏只能包含抽象方法、默認方法和類方法。不能爲普通方法提供實現;抽象類能夠包含實例方法。
  • 接口裏只能定義靜態常量;抽象類能夠定義成員變量、類變量。
  • 接口不能包含構造器和初始化塊;抽象類中能夠包含構造器和初始化塊。
  • 一個類最多隻能有一個直接父類,但一個接口能夠繼承多個直接父接口。
6.2.5 解決默認方法衝突
  • 當一個類繼承了父類,並實現了接口,若接口中的默認方法和父類中的方法,名稱相同且形參列表相同時,父類的方法會覆蓋接口的默認方法。這樣作的目的是與java8以前的代碼兼容。
  • 當一個類繼承了父類,並實現了接口,若接口中的抽象方法和父類中的方法,名稱相同且形參列表相同時,那麼子類至關於已經有了接口抽象方法的默認實現。
  • 當一個類實現了多個接口,若其中兩個及以上的接口有同名且形參列表相同的默認方法,或者一個接口的默認方法和另外一個接口的抽象方法知足覆蓋條件時,編譯器會報錯,解決方法是:在子類中覆蓋此方法,方法體中能夠選擇執行哪一個接口的默認方法。語法格式爲:"接口名.super.方法名"
6.2.6 接口與回調

回調指對象調用某個方法時,此方法須要的實參是一個接口,而這個接口和對象有關,從而,對象調用方法後,方法反過來調用這個接口。

6.2.7 Java中經常使用的接口
- Comparable<T>
  接口中定義了:
  public int comPareTo(T other); - Comparator<T>(比較器) 接口中定義了 public int compare(T first, T second); - Clonable<T> public T clone();

深克隆與淺克隆。

Object類實現了基本的clone()方法,但因爲不瞭解對象的域,因此只能逐個域地進行拷貝,對於引用類型的域,拷貝域就會獲得相同子對象的另外一個引用,從而,原對象和克隆獲得的對象仍會共享一些信息,此爲淺克隆。

深克隆的作法是:對於基本數據類型,直接拷貝,對於引用類型的域,遞歸拷貝引用對象的每一個域。使得原對象和克隆獲得的對象再也不共享任何信息(對於不可變類,可直接複製值,只有可變類,才須要這麼處理)。調用此類型的clone()進行拷貝。

自定義克隆方法時,須要肯定:

  1. 默認的Object類的克隆方法是否知足要求。
  2. 是否能夠在可變的子對象上調用克隆方法來修補默認的克隆方法。
  3. 是否不應使用克隆方法。

若是選擇第1項或第2項,類必須:

  1. 實現Cloneable接口
  2. 從新定義clone(),並指定public訪問修飾符。

因爲Object類中的clone()聲明爲protected,因此子類只能調用此方法來克隆本身的對象,必須從新定義clone()public才能容許全部方法克隆對象。

深克隆示例以下:

class Employee implements Cloneable{ ... public Employee clone() throws CloneNotSupportedException{ Employee cloned = (Employee) super.clone(); cloned.hireDay = (Date) hireDay.clone(); return cloned; } }

全部數組類型都有一個publicclone方法。

6.3 內部類

在一個類內部定義的另外一個類稱爲內部類,此處的「類內部」包括類中的任何位置,甚至在方法中也能夠定義內部類(方法裏定義的內部類稱爲局部內部類和匿名內部類)。包含內部類的類稱爲外部類。內部類的主要做用有:

  • 內部類提供了更好的封裝,能夠把內部類隱藏在外部類以內,不容許同一個包中的其餘類訪問該類。
  • 由於內部類被當成外部類成員,而同一個類的成員之間能夠相互訪問,因此內部類方法能夠直接訪問外部類的私有數據。但外部類要訪問內部類的實現細節,必須先定義內部類對象。
  • 匿名內部類適合用於建立那些僅須要使用一次的抽象類或接口的實現類對象。

定義內部類與定義外部類的語法大體相同,內部類除了須要定義在其餘類裏面以外,還存在以下兩點區別:

  • 內部類比外部類能夠多使用三個修飾符:private、protected、static
  • 非靜態內部類不能有靜態成員。
6.3.1 成員內部類
6.3.1.1 非靜態內部類

​ 多數狀況下,內部類做爲成員內部類定義,而不是做爲局部內部類。成員內部類是一種與成員變量、方法、構造器和初始化塊類似的類成員;局部內部類和匿名內部類不是類成員。

成員內部類分爲兩種:靜態內部類和非靜態內部類,使用static修飾的成員內部類是靜態內部類,沒有使用static修飾的成員內部類是非靜態內部類。

由於內部類是做爲其外部類的成員,因此可使用任意訪問控制符如private,protected 和 public 等修飾。編譯器生成的成員內部類的class文件格式爲:OuterClass$InnerClass.class。

在非靜態內部類裏能夠直接訪問外部類的private成員。這是由於在非靜態內部類對象裏,保存了一個它所寄生的外部類對象的引用,同時編譯器會在外部類中添加對相應私有域的訪問器和更改器(當調用非靜態內部類的實例方法時,必須有一個非靜態內部類實例,非靜態內部類實例必須寄生在外部類實例裏)。

當在非靜態內部類的方法內訪問某個變量時,系統優先在該方法內查找是否存在該名字的局部變量,若是存在就使用改變了;若是不存在,則到該方法所在的內部類中查找是否存在該名字的成員變量,若是存在則使用該成員變量;若是不存在,則到該內部類所在的外部類中查找是否存在該名字的成員變量,若是存在則使用該成員變量,若是依然不存在,系統將出現編譯錯誤,提示找不到該變量。

所以,若是外部類成員變量、內部類成員變量與內部類方法的局部變量同名,則可經過使用外部類名.this,this做爲限定區分。以下程序所示:

public class TestVar{ private String prop = "外部類的實例變量"; private class InClass{ private String prop = "內部類的實例變量"; public void info(){ String prop = "局部變量"; System.out.println("輸出的是外部實例變量" + TestVar.this.prop); System.out.println("輸出的是內部類的實例變量" + this.prop); System.out.println("輸出的是局部變量" + prop); } } public void test(){ Inclass in = new InClass(); in.info(); } public static void main(String[] args){ new TestVar.test(); } }

非靜態內部類的成員能夠訪問外部類的private成員,但反過來不成立。由於外部類對象存在時,非靜態內部類對象不必定存在。若是外部類須要訪問非靜態內部類的成員,必須顯式地建立內部類對象來訪問其實例成員,外部類方法能夠訪問內部類的私有成員。(和普通的一個類的方法訪問另外一個類的方法存在區別,外部類訪問內部類的實例成員,不須要經過內部類的公有方法來訪問,這仍是由於,你們都是外部類的成員,成員之間能夠相互訪問)。以下所示:

public class Outer{ private int outprop = 9; class Inner{ private int inprop = 5; public void accessOuterprop{ System.out.println("外部類的outprop值:" + outprop); } } public void accessInnerProp(){ //System.out.println("內部類的inprop值" + inprop); System.out.println("內部類的inprop值" + new().inprop); } public static void main(String[] args){ Outer out = new Outer();//註釋2 out.accessInnerProp(); } }

第一處註釋試圖在外面類方法裏訪問非靜態內部類的實例變量,將引發編譯錯誤。外部類不容許訪問非靜態內部類的實例成員的緣由是:上面main方法的第二處註釋代碼只建立了一個外部類對象,並調用外部類對象的accessInnerProp方法。此時非靜態內部類對象根本不存在,若是容許accessInnerProp方法訪問非靜態內部類對象,將引發錯誤。

非靜態內部類對象和外部類對象的關係

若是存在一個非靜態內部類對象,則必定存在一個被它寄生的外部類對象。但當外部類對象存在時,外部類對象裏不必定寄生了非靜態內部類對象。因此外部類對象訪問非靜態內部類對象時,可能非靜態內部類對象根本不存在,而非靜態內部類對象訪問外部類對象時,外部類對象必定存在。

根據靜態成員不能訪問非靜態成員的規則,外部類的靜態方法、靜態代碼塊不能訪問非靜態內部類,尤爲是不能使用非靜態內部類建立實例等(緣由是:使用非靜態內部類時,外部類的對象並不存在,此時,非靜態內部類對象無處寄生):

public class StaticTest{ private class In{} piblic static void main(String[] args){ //沒法訪問非靜態成員 In類 new In(); } }

java不容許在非靜態內部類裏定義靜態成員、靜態方法、靜態初始化塊。不然能夠經過OutClass.InClass的方法來調用,此時,外部類對象並未建立。

非靜態內部類裏不能有靜態初始化塊,但能夠有普通初始化塊,非靜態內部類的普通初始化塊的做用與外部類初始化塊的做用相同。

6.3.1.2 靜態內部類

若是使用static來修飾一個內部類,則這個內部類就屬於外部類自己,而不屬於外部類的某個對象,至關於外部類的類成員。所以使用static修飾的內部類被稱爲靜態內部類。

static關鍵字的做用是把類的成員變成類相關,而不是實例相關,即static修飾的成員屬於整個類,而不屬於單個對象。對象類的上一級程序單元是包,因此不可以使用static修飾;而內部類的上一級程序單元是外部類,使用static修飾能夠將內部類變成外部類相關,而不是外部類實例相關。

靜態內部類能夠包含靜態成員,也能夠包含非靜態成員。靜態內部類不能訪問外部類的實例成員,只能訪問外部類的靜態成員。即便是靜態內部類的實例方法也不能訪問外部類的實例成員,只能訪問外部類中的靜態成員。緣由是:靜態內部類不須要寄生在外部類實例中,靜態內部類的實例建立時,外部類的實例不必定存在。

由於靜態內部類是外部類的一個靜態成員,所以外部類的全部方法,全部初始化塊中可使用靜態內部類來定義變量、建立對象等。

外部類依然不能直接訪問靜態內部類的成員,但可使用靜態內部類的類名做爲調用者訪問靜態內部類的靜態成員,也可使用靜態內部類的對象訪問靜態內部類的實例成員。

public lass AccessStaticInnerClass{ static class StaticInnerClass{ private static int prop1 = 5; private int prop2 = 9; } public void accessInnerProp(){ System.out.println(StaticIneerClass.prop1); System.out.println(new StaticIneerClass().prop2); } }
6.3.2 使用內部類
6.3.2.1 在外部類裏面使用非靜態內部類

在外部類裏面能夠調用內部類對象的私有成員,可是須要先建立內部類對象。

6.3.2.2 在外部類的實例方法中使用內部類
  • 定義內部類變量

    內部類名 變量名

  • 建立內部類對象

    new 內部類名(實參列表)

  • 訪問內部類成員

    內部類對象名.成員名

6.3.2.3 在外部類的靜態方法中使用內部類
  • 定義內部類變量

    內部類名 變量名

  • 建立內部類對象

    在外部類靜態方法中建立內部類實例時,須要先有外部類實例。而後:外部類實例名.new 內部類名(實參列表)

  • 訪問內部類成員

    內部類對象名.成員名

6.3.2.4 在外部類外面使用非靜態內部類

若是但願在外部類外面使用內部類,則內部類不能使用private訪問控制權限,private修飾的內部類只能在外部類裏面使用,內部類的訪問權限以下:

  • 省略訪問控制符的內部類,只能被與外部類處於同一個包中的其餘類訪問
  • 使用protected修飾的內部類,可被與外部類處於同一個包中的其餘類和外部類的子類訪問
  • 使用public修飾的內部類,能夠在任何地方被訪問。

在外部類外訪問內部類的格式以下:

  • 定義內部類變量

    外部類名.內部類名 變量名

  • 建立內部類對象

    在外部類外面建立內部類實例時,須要先有外部類實例。而後:外部類實例名.new 內部類名(實參列表)//此處不須要再寫外部類.內部類。定義變量須要這麼作是保證類的惟一性。

  • 訪問內部類成員

    內部類對象名.成員名。此時只能訪問內部類的公有成員

6.3.2.5 在外部類裏面使用靜態內部類

因爲靜態內部類是外部類相關的。因此內部類對象無需寄生於外部類對象。從而在外部類的靜態方法和非靜態方法中使用靜態內部類格式相同,均爲:

  • 定義內部類變量

    內部類名 變量名

  • 建立內部類對象

    new 內部類名(實參列表)

  • 訪問內部類成員

    內部類對象名.成員名

6.3.2.6 在外部類外使用靜態內部類

在外部類外使用靜態內部類的格式以下:

  • 定義內部類變量

    外部類名.內部類名 變量名

  • 建立內部類對象

    new 外部類名.內部類名(實參列表)

  • 訪問內部類成員

    內部類對象名.成員名。此時只能訪問內部類的公有成員

6.3.3 定義內部類的子類

內部類的子類不必定是內部類,能夠是一個外部類。

6.3.3.1 定義非靜態內部類的子類

當建立一個非靜態內部類的子類時,子類構造器總會調用父類的構造器,而調用非靜態內部類的構造器時,必須存在一個外部類對象。所以在建立非靜態內部類的子類時,必須給子類構造器傳一個外部類對象做爲參數。因此定義非靜態內部類子類的格式爲:

class 子類名 extends 外部類名.內部類名{ [修飾符] 子類名(外部類名 外部類實例,實參){ 外部類實例名.super(實參); ... } ... }

示例以下:

public class SubClass extends Out.In{ public SubClass(Out obj){ obj.super("hello"); } }

非靜態內部類對象和其子類對象都必須持有寄生的外部類對象的引用,區別是建立兩種對象時傳入外部類對象的方式不一樣:當建立非靜態內部類對象時,經過外部類對象來調用new關鍵字;當建立內部類子類對象時,將外部類對象做爲子類構造器的參數。

非靜態內部類的子類實例仍然須要保留一個引用,即若是一個非靜態內部類的子類的對象存在,也必定存在一個寄生的外部類對象。

6.3.3.2 定義靜態內部類的子類

由於調用靜態內部類的構造器時無需使用外部類對象,因此建立靜態內部類的子類比較簡單,格式以下:

clsss 子類名 extends 外部類名.內部類名{
  ...
}

能夠看出,當定義一個靜態內部類時,其外部類很是像一個包空間。

相比之下,使用靜態內部類比使用非靜態內部類簡單不少,只要把外部類當成靜態內部類的包空間便可,所以當程序須要使用內部類時,應該優先考慮使用靜態內部類。

外部類的子類中若是定義一個與父類內部類同名的內部類時,子類建立的是子類內部類的對象,父類建立的是父類內部類的對象,若是把子類對象賦給父類引用,再建立內部類對象,此時建立的是父類內部類的對象。能夠把內部類當作事外部類的成員變量,經過靜態分派肯定符號引用。

6.3.4 局部內部類

若是在方法裏定義內部類,則這個內部類是一個局部內部類,局部內部類僅在該方法裏有效。因爲局部內部類不能在此方法之外的地方使用,所以局部內部類不須要訪問控制符和static修飾符修飾。

對於局部成員而言,無論是局部變量仍是局部類,他們的上一級程序單元都是方法,而不是類,使用static修飾他們沒有任何意義;不只如此,由於局部成員的做用域是所在方法,其餘程序單元永遠也不可能訪問一個方法中的局部成員,因此,全部的局部成員不能使用訪問控制符和static修飾符。

若是須要用局部內部類定義變量、建立實例或派生子類,那麼都只能在局部內部類所在的方法內進行。

public class LocalInnerClass{ public static void main(String args){ class InnerBase{ int a; } class InnerSub extends InnerBase{ int b; } InnerSub is = new InnerSub(); is.a = 5;///////////////////////////////方法中能夠直接訪問局部內部類的域。 is.b = 8; System.out.println(is.b + " " + is.a); } } 

編譯程序,生成三個class文件:LocalInnerClass.class、LocalInnerClass$InnerBase.class、LocalInnerClass$InnerSub.class。注意到局部內部類的class文件的文件名比成員內部類的class文件的文件名多了一個數字,這是由於同一個類裏不可能有兩個同名的成員內部類,而同一個類裏可能有兩個以上的同名的局部內部類(處於不一樣的方法中)。

局部內部類在實際開發中不多使用,由於局部內部類的做用域過小了,只能在當前方法中使用。

6.3.5 匿名內部類

匿名內部類適合建立只須要使用一次的類,建立匿名內部類時會當即建立一個該類的實例。定義格式以下:

new 實現接口() |父類構造器(實參列表) { //匿名內部類的類體部分 }

從定義可知,匿名內部類必須繼承一個父類,或實現一個接口,但最多隻能繼承一個父類或實現一個接口。

關於匿名內部類有以下兩條規則:

  • 匿名內部類不能是抽象類,由於系統在建立匿名內部類時,會當即建立匿名內部類的對象。

  • 匿名內部類不能定義構造器。因爲匿名內部類沒有類名,因此沒法定義構造器。取而代之的是,將構造器參數傳遞給父類構造器。同時匿名內部類能夠定義初始化塊,能夠經過實例初始化塊完成構造器須要完成的事情。

最經常使用的建立匿名內部類的方式是須要建立某個接口類型的對象。

interface Product{ public String getName(); } public class AnonyTest{ public void test(Product p){ System.out.println("購買了" + p.getName()); } public static void main(String[] args){ AnonyTest obj = new AnonyTest(); obj.test( new Product(){ public String getName(){ return "tom"; } }); } }

上述程序中的AnonyTest類定義了一個test方法,該方法須要一個Product對象做爲參數,但Product只是一個接口,沒法直接建立對象,所以考慮建立一個Product接口實現類的對象傳入該方法---若是這個Product接口實現須要重複使用,則應該將實現類定義成一個獨立類;若是這個Product接口實現類只須要一次使用,就能夠定義一個匿名內部類。

因爲匿名內部類不能是抽象類,因此匿名內部類必須實現它的抽象父類或接口裏包含的全部抽象方法。

當經過實現接口來建立匿名內部類時,因爲結構沒有構造器。所以new接口名後的括號裏不能傳入參數值。

可是若是經過繼承父類來建立匿名內部類時,匿名內部類將擁有和父類類似的構造器,此處的類似指的是擁有相同的形參列表。

匿名內部類繼承抽象父類的示例:

abstract class Device{ private String name; public abstract double getPrice(); public Device(){} public Device(String name){ this.name = name; } //省略name的訪問器和修改器 } public class AnonyTest{ public void test(Device p){ System.out.println("花費" + p.getPrice()); } public static void main(String[] args){ AnonyTest obj = new AnonyTest(); obj.test(new Device("honey"){ public double getPrice(){ return 56.3; } }); Device p = new Device(){ {//初始化塊 System.out.println("匿名內部類的初始化塊:"); } //實現抽象方法 public double getPrice(){ return 56.3; } //覆蓋父類方法 public String getName(){ return "鍵盤"; } } obj.test(p); } }

當建立匿名內部類時,必須實現接口或抽象父類裏的全部抽象方法,若是有須要,也能夠重寫父類的普通方法。

在java8以前,java要求被局部內部類、匿名內部類訪問的局部變量,在方法中定義時必須用final修飾,從java8開始這個限制被取消了,由編譯器進行處理:若是局部變量被匿名內部類訪問,那麼該局部變量至關於自動使用了final修飾符。此局部變量在第一次賦值後,值不能再修改,不然編譯器將報錯。例如:

public class PairTest{ public static void main(String[] args){ int age = 0 ; age =3; class Device{ void test(){ System.out.println(age); } } Device d = new Device(); d.test(); } }

age在初始化爲0後,被賦值爲3,因此編譯器將會報錯。

java8將這個功能稱爲「effective final」,意思是對於匿名內部類訪問的局部變量,能夠用final修飾,也能夠不一樣final修飾,但必須按照有final修飾的方式來用,也就是一次賦值後,之後不能從新賦值。

內部類是一種編譯器現象,與虛擬機無關。編譯器會把內部類翻譯成用$分隔外部類名與內部類名的常規類文件,而這個操做對虛擬機是透明的。編譯階段,編譯器會對內部類進行處理,轉化爲外部類,內部類對外部類對象的訪問是由於編譯器會在內部類的構造器中添加一個外部類引用的參數。內部類對外部類實例域的訪問:編譯器會在外部類中添加相關實例域的訪問器方法,從而內部類對外部類實例域的訪問將轉化爲調用外部類的訪問器方法來實現。

局部內部類對方法內的局部變量的訪問:因爲方法在建立局部內部類實例後,可能程序執行結束,局部變量會釋放,此時局部內部類中將沒法訪問到局部變量,因此編譯器會在局部內部類中添加實例域,在建立局部內部類實例時,將局部變量的值保存到添加的實例域中。

在內部類不須要訪問外部類對象時,應該使用靜態內部類。

6.4 Lambda表達式

6.4.1 Lambda表達式的定義

函數式接口:只有一個抽象方法的接口。函數式接口能夠包含多個默認方法、類方法,但只能聲明一個抽象方法。

匿名內部類的實現,以及Lambda表達式使用示例:

public class CommandTest{ public static void main(String[] args){ ProcessArray pa = new ProcessArray(); int[] target = {3, -4, 6, 4}; pa.process(target, new Command(){ public int process(int[] target){ int sum = 0; for(int tmp : target){ sum += temp; } return sum; } }); } }

Lambda表達式能夠對上述代碼進行簡化:

public class CommandTest{ public static void main(String[] args){ ProcessArray pa = new ProcessArray(); int[] target = {3, -4, 6, 4}; pa.process(target, (int[] target)->{ int sum = 0; for(int tmp : target){ sum += temp; } return sum; }); } }

能夠看出,Lambda表達式的做用就是簡化匿名內部類的繁瑣語法。它有三部分構成:

  • 形參列表。形參列表容許省略形參類型。若是形參類別中只有一個參數,甚至連形參列表的圓括號均可以省略。
  • 箭頭(->)。必須經過英文中畫線號和大於符號組成。
  • 代碼塊。若是代碼塊只包含一條語句,Lambda表達式容許省略代碼塊的花括號。同時,當Lambda代碼塊只有一條return語句時,甚至能夠省略return關鍵字。此時,若是lambda表達式須要返回值,而它的代碼塊中只有一條省略了return的語句,Lambda表達式會自動返回這條語句的值。

總結起來,lambda共有以下幾種省略狀況:

  • 能夠省略參數類型。
  • 只有一個參數時,能夠省略參數的圓括號
  • 語句體只有一條語句時,能夠省略花括號,此時語句末尾的分號也省略。
  • 只有一條return語句時,能夠省略return關鍵字,語句末尾的分號也省略。

示例以下:

interface Flyable{ void fly(String weather); } public class LambdaQs{ public void drive(Flyable f){ System.out.println("我正在駕駛:" + f); f.fly("晴天"); } public static void main(String[] args){ LambdaQs lq = new LambdaQs(); lq.drive(weather -> { System.out.println("今每天氣是:" + weather); System.out.println("直升機飛行平穩"); }); } }
6.4.2 Lambda表達式與函數式接口

定義Lambda表達式時即建立了一個對象,對象的類型要求是一個函數式接口,具體由賦值號左邊的操做數類型決定。可使用Lambda表達式進行賦值。用Lambda表達式進行賦值的示例以下:

Runnable r = ()->{
  for(int i = 0;i < 100 ; i++){ System.out.println(); } };

Lambda表達式的限制以下:

  • Lambda表達式的目標類型必須是明確的函數式接口,甚至不能賦給Object類型的變量,不然,沒法肯定lambda表達式的運行時類型。
  • Lambda表達式只能實現一個方法,所以它只能爲函數式接口建立對象。

示例以下:

Object obj = ()->{
    for(int i = 0;i < 100 ; i++){ System.out.println(); } };

上述代碼的Lambda表達式賦給的是Object對象而不是函數式接口。因此,編譯器會報錯。

爲了保證Lambda表達式的目標類型是明確的函數式接口,能夠有以下三種常見方式:

  • 將Lambda表達式賦值給函數式接口類型的變量
  • 將Lambda表達式做爲函數式接口類型的參數傳給某個方法。
  • 使用函數式接口對Lambda表達式進行強制類型轉換。

所以上述代碼,可修改

Object obj = (Runnable)()->{
    for(int i = 0;i < 100 ; i++){ System.out.println(); } };

易知,Lambda表達式的目標類型徹底多是變化的(便可能會利用強制類型轉換,將Lambda表達式賦給另外一個抽象方法相同的接口變量),惟一的要求是,Lambda表達式實現的匿名方法與函數式接口中的抽象方法有相同的形參列表和返回值。示例以下:

interface FKTest{ public void run(); } Runnable obj = ()->{ for(int i = 0;i < 100 ; i++){ System.out.println(); } }; FKTest fk = (FKTest)obj;//賦值合法

Java 8在java.util.function包下預約義了大量的函數式接口,典型 地包含以下4類接口。

  • XxxFunction:這類接口中一般包含一個apply()抽象方法,該方法對參數進行處理、轉換,而後返回一個新的值。該函數式接口一般用於對指定的數據進行轉換處理。
  • XxxConsumer:這類接口中一般包含一個accept()抽象方法,該方法也負責對參數進行處理,只是該方法不會返回處理結果。
  • XxxPredicate:這類接口中一般包含一個test()抽象方法,該方法一般用來對參數進行某種判斷,而後返回一個boolean值。該接口一般用於判斷參數是否知足特定條件,常常用於涮選數據。
  • XxxSupplier:這類接口中一般包含一個getAsXxx()抽象方法,該方法不須要輸入參數,該方法會按某種邏輯算法返回一個數據。

綜上所述,不難發現Lambda表達式的本質很簡單,就是使用簡潔的語法來建立函數式接口的實例------這種語法避免了匿名函數類的繁瑣。

6.4.3 方法引用與構造器引用

有時,現有方法能夠完成抽象方法的功能,此時能夠直接調用 現有類的方法或構造器,稱爲方法引用和構造器引用。

方法引用和構造器引用可讓Lambda表達式的代碼塊更加簡潔。方法引用和構造器引用都須要使用兩個英文冒號。Lambda表達式支持以下表所示的幾種引用方式。

種類 示例 說明 對應的Lambda表達式
引用類方法 類名::類方法 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 (a,b,...)->類名.類方法(a,b,...)
引用特定對象的實例方法 特定對象::實例方法 函數式接口中被實現方法的所有參數傳給該類方法做爲參數 (a,b,...)->特定對象.實例方法(a,b,...)
引用某類對象的實例方法 類名::實例方法 函數式接口中被實現方法的第一個參數做爲調用者,後面的參數所有傳給該方法做爲參數 (a,b,...)->a.實例方法(a,b,...)
引用構造器 類名::new 函數式接口中被實現方法的所有參數傳給該構造器做爲參數 (a,b,...)->new 類名(a,b,...)
6.4.3.1 引用類方法
@FunctionInterface interface Converter{ Ingeter convert(String from); } //使用Lambda表達式建立Conveter對象 Converter converter1 = from -> Integer.ValueOf(from); Integer val = converter1.convert("99");

上面代碼調用converter1對象的convert()方法時------因爲converter1對象是由Lambda表達式建立的,convert()方法執行體就是Lambda表達式的代碼部分。

上述的Lambda表達式的代碼塊只有一行調用類的方法的代碼,所以能夠替換爲:

Converter converter1 = Integer::ValueOf;
6.4.3.2 引用特定對象的實例方法
Converter converter2 = from -> "fkit.org".indexOf(from); Integer value = converter1.convert("it");

上述的Lambda表達式的代碼塊只有一行調用"fkit.org"的indexOf()實例方法的代碼,所以能夠替換爲:

Converter converter2 = "fkit.org"::indexOf;
6.4.3.3 引用某類對象的實例方法
@FunctionalInterface interface MyTest{ String test(String a, int b, int c); } MyTest mt = (a, b, c)-> a.subString(b, c); String str = mt.test("Java I love you", 2,9);

上述的Lambda表達式的代碼塊只有一行a.subString(b, c);所以能夠替換爲:

MyTest mt = String::subString;
6.4.3.4 引用構造器
@FunctionInterface interface YourTest{ JFrame win(String title); } YourTest yt = (String a)->new JFrame(a); JFrame jf = yt.win("個人窗口");

上述Lambda表達式的代碼塊只有一行new JFrame(a);所以能夠替換爲:

YourTest yt = JFrame::new;

方法引用和構造器引用中,若是有多個同名的重載方法,編譯器會依據表達式實際轉換的函數式接口中聲明的方法進行選擇。可使用數組類型創建構造器引用,例如int[]::new,它有一個參數,即數組的長度,這等價於lambda表達式x->new int[x]

能夠在方法中使用this參數,例如this::equals等同於x-> this.equals(x),使用super也是合法的。this表示lambda表達式所在方法的對象。例如:

class Greeter{ public void greet(){ System.out.println("hello world!"); } } class TimedGreeter extends Greeter{ public void greet(){ Timer t = new Timer(1000, super::greet); t.start(); } }
6.4.4 Lambda 表達式與匿名內部類的聯繫和區別

Lambda 表達式是匿名內部類的一種簡化,所以它能夠部分取代匿名內部類的做用,Lambda 表達式與匿名內部類存在如下相同點:

  • Lambda 表達式與匿名內部類同樣,均可以直接訪問「effectively final」的局部變量,以及外部類的成員變量(包括實例變量和類變量);
  • Lambda 表達式建立的對象與匿名內部類生成的對象同樣,均可以直接調用從接口中繼承的默認方法。
interface Displayable{ void display(); default int add(int a, int b){ return a + b; } } public class LabdaAndInner{ private int age = 12; private static String name = "i'm here"; public void test(){ String book = "瘋狂java"; Displayable dis = ()->{ System.out.println("book 局部變量爲:" + book); System.out.println("age 局部變量爲:" + age); System.out.println("name 局部變量爲:" + name); } dis.display(); System.out.println(dis.add(3, 5)); } }

上述代碼示範了Lambda表達式分別訪問「effective final」的局部變量、外部類的實例變量和類變量。Lambda表達式訪問局部變量時,編譯器隱式爲Lambda表達式添加一個私有域常量,並將局部變量放入Lambda表達式的默認構造器中以初始化私有域常量。Lambda表達式對於外部類實例域的訪問是編譯器將外部類實例引用做爲參數傳入Lambda表達式的默認構造器,同時在Lambda表達式中定義一個實例域保存外部類實例引用實現的。

Lambda表達式與匿名內部類的主要區別:

  • 匿名內部類能夠爲任意接口創造實例;無論接口有多少個抽象方法們只要匿名內部類實現了全部的抽象方法便可;可是Lambda表達式只能爲函數式接口建立實例。
  • 匿名內部類能夠爲抽象類甚至普通類建立實例;但Lambda表達式只能爲函數式接口建立實例。
  • 匿名內部類實現的抽象方法的方法體容許調用接口中定義的默認方法;但Lambda表達式的代碼塊不容許調用接口中定義的默認方法。由於定義匿名內部類時已知其實現的接口或父類,而定義Lambda表達式時,其目標類型未知,雖然能夠經過賦值號左邊的類型推斷,但因爲此Lambda表達式還能夠賦給定義了相同抽象方法的其餘函數式接口,而那些接口中未必也定義了相同的默認方法。

例如在Lambda表達式的代碼塊中增長以下一行,編譯器將會報錯。

System.out.println(add(3, 5));

java中,lambda表達式就是閉包,若是在lambda表達式中使用了所在方法中的局部變量,稱lambda表達式捕獲了此局部變量。易知被捕獲的局部變量都必須是effectively final(最終變量,即變量初始化以後就不會再爲它賦新值),且在lambda表達式中也不能改變,不然,當多個變量同時引用此lambda表達式時,會出現併發的安全問題。

lambda表達式的體與嵌套塊有相同的做用域。一樣適用命名衝突和遮蔽的規則。所以lambda表達式中不能聲明與局部變量同名的參數或局部變量。

使用lambda表達式的重點是延遲執行。

在設計接口時,若是接口中只有一個抽象方法,就能夠用@FunctionInterface來標記這個接口。這樣,若是無心中增長了另外一個非抽象方法,編譯器會產生一個錯誤的信息。

6.5 垃圾回收

垃圾回收機制具備以下特色:

  • 垃圾回收機制只負責回收堆內存中的對象,不會回收物理資源。
  • 程序沒法精確控制垃圾回收的運行,垃圾回收會在合適的時候進行。當對象永久性地失去引用後,系統就會在合適的時候回收它所佔的內存。
  • 在垃圾回收機制回收任何對象以前,總會調用它的finalize()方法,該方法可能使該對象復活(讓一個引用變量從新引用該對象),從而致使垃圾回收機制取消回收。
6.5.1 對象在內存中的狀態

當一個對象在堆內存中運行時,根據它被引用變量所引用的狀態,能夠把它所處的狀態分紅以下三種:

  • 可達狀態:當一個對象被建立後,如有引用變量引用它,則該對象處於可達狀態。
  • 可恢復狀態:若是某個對象再也不有任何引用變量引用它,就處於可恢復狀態。在這種狀態下,系統的垃圾回收機制準備回收該對象佔用的內存,在回收該對象以前,系統會調用對象的finalize()方法進行資源清理。若是系統在調用全部可恢復對象的finalize()時從新讓一個引用變量引用該對象,則這個對象再次變爲可達狀態;不然該對象將進入不可達狀態。
  • 不可達狀態:當對象與全部引用變量的關聯都被切斷,且系統已經調用全部對象的finalize()方法後,依然沒有使該對象變爲可達狀態,那麼該對象將永久性地失去引用,變成不可達狀態。只有當對象處於不可達狀態時,系統纔會真正回收該對象佔有的資源。
6.5.2 強制垃圾回收

當一個對象失去引用後,系統什麼時候調用它的finalize()對其進行資源清理,它什麼時候變爲不可達狀態,系統什麼時候回收它佔有的內存,對於程序徹底透明。程序只能控制一個對象什麼時候再也不被任何引用變量引用,決不能控制它什麼時候被回收。

程序沒法精確控制Java垃圾回收的時間,但依然能夠強制系統進行垃圾回收--這種強制只是通知系統進行垃圾回收,但系統是否進行垃圾回收依然不肯定。大部分時候,程序強制系統垃圾回收後總會有一些效果。強制系統垃圾回收有以下兩種方式:

  • 調用System類的靜態方法gc():System.gc()
  • 調用Runtime對象的實例方法gc()Runtime.getRuntime().gc()

示例以下:

public class GcTest{ public static void main(String[] args){ for(int i = 0; i < 4; i++){ new GcTest(); //下面兩種方法徹底相同 System.gc(); //Runtime.getRuntime().gc(); } } public void finalize(){ System.out.println("系統正在清理"); } }
6.5.3 finalize()

finalize()是定義在Object類裏的實例方法,方法原型爲:

protected void finalize() throws Throwable

finalize()方法返回後,對象消失,垃圾回收機制開始執行。方法原型中的throws Throwable表示能夠拋出任何異常。

任何java類均可以重寫Object類的finalize()方法,在該方法中清理對象佔用的資源。只有當程序認爲須要更多的額外內存時,垃圾回收機制纔會進行垃圾回收。

finalize()具備以下4個特色:

  • 永遠不要主動調用某個對象的finalize()方法,該方法應交給垃圾回收機制調用。
  • finalize()方法什麼時候被調用,是否被調用具備不肯定性,不要把finalize()當成必定會被執行的方法。
  • JVM執行可恢復對象的finalize()方法時,可能使該對象或系統中其餘對象從新變爲可達狀態。
  • JVM執行finalize()方法出現異常時,垃圾回收機制不會報告異常,程序繼續執行。

示例以下:

public class FinalizeTest{ private static final FinalizeTest ft = null; public void info(){ System.out.println("測試finalize方法"); } public static void main(String[] args){ new FinalizeTest(); System.gc(); System.runFinalization(); ft.info(); } public void finalize(){ ft = this; } }

代碼中的finalize()把須要清理的可恢復對象從新賦給靜態變量,從而讓該可恢復對象從新變成可達狀態。一般finalize()方法的最後一句是調用父類的finalize():super.finalize()

6.5.4 對象的軟、弱和虛引用

對大部分對象而言,程序裏會有一個引用變量引用該對象,這是最多見的引用方式。除此以外,java.lang.ref包下提供了3個類:SoftReference、PhantomReferenceWeakReference,他們分別表明了系統對對象的3種引用方式:軟引用、弱引用和虛引用。所以Java語言對對象的引用有以下4種方式:

  1. 強引用(StrongReference)

    這是Java程序中最多見的引用方法。程序建立一個對象,並把這個對象賦給一個引用變量。當一個對象被引用變量引用時,它處於可達狀態,不可能被垃圾回收機制回收。

  2. 軟引用

    軟引用須要經過SoftReference類來實現,當一個對象只有軟引用時,它可能被垃圾回收機制回收。對於只有軟引用的對象而言,當系統內存足夠時,它不會被回收,程序也可以使用該對象;當系統內存不足時,系統可能會回收它。

  3. 弱引用

    弱引用經過WeakReference類實現,弱引用和軟引用相似,但弱引用的引用級別更低。對於只有弱引用的對象而言,當垃圾回收機制運行時,無論系統內存是否足夠,總會回收該對象的內存。

  4. 虛引用

    虛引用經過PhantomReference類實現,虛引用徹底相似於沒有引用。虛引用對對象自己沒有太大影響,對象甚至感受不到虛引用的存在。若是一個對象只有虛引用時,那麼它和沒有引用效果大體相同。虛引用主要用於跟蹤對象被垃圾回收的狀態,虛引用不能單獨使用,虛引用必須和引用隊列(ReferenceQueue)聯合使用。

上面三個引用類都包含一個get()方法,用於獲取他們引用的

對象。但虛引用太弱了,沒法獲取到引用的對象。

引用隊列ReferenceQueuejava.lang.ref.ReferenceQueue類表示,用於保存被回收後對象的引用。當聯合使用軟引用、弱引用和虛引用時,系統在回收被引用的對象以後,將把被回收對象的引用添加到關聯的引用隊列中。

軟引用和弱引用能夠單獨使用,但虛引用不能單獨使用,單獨使用虛引用沒有意義。虛引用的主要做用就是跟蹤對象被垃圾回收的狀態,程序經過檢查與虛引用關聯的引用隊列是否包含了該虛引用,從而瞭解虛引用所引用的對象是否即將被回收。

弱引用用法示例:

class Test{ public static void main(String[] args){ String str = new String("java"); //建立弱引用,使其引用str對象 WeakReference wr = new WeakReference(str); str = null; //取出弱引用wr所引用的對象 System.out.println(wr.get()); System.gc(); System.runFinalization(); //輸出結果爲null,表示對象已被回收 System.out.println(wr.get()); } }

虛引用和引用隊列用法示例:

class Test{ public static void main(String[] args){ String str = new String("java"); RefenceQueue rq = new RefenceQueue(); //建立虛引用,使其引用str對象 PhantomReference pr = new PhantomReference(str,rq); str = null; //取出虛引用wr所引用的對象,此處並不能獲取虛引用所引用的對象 System.out.println(pr.get()); System.gc(); System.runFinalization(); //垃圾回收後,虛引用將被放入引用隊列 //取出引用隊列中最早進入隊列的引用與pr比較 System.out.println(rq.poll() == pr); } }

使用這些引用類就能夠避免在程序執行期間將對象留在內存中。若是以軟引用、弱引用或虛引用的方式引用對象,垃圾回收器就能隨機地釋放對象。

因爲垃圾回收的不肯定性,當程序但願從軟、弱引用中取出引用對象時,可能這個對象已經被釋放。若是程序須要使用被引用的對象,則必須從新建立該對象。這個過程有以下兩種方式:

obj = wr.get();
if(obj == null){ wr = new WeakRefence(recreatIt());//// 1 obj = wr.get();//////// 2 } //操做對象obj //再次切斷obj與對象的關聯 obj = null //方法二: obj = wr.get(); if(obj == null){ obj = recreatIt(); wr = new WeakRefence(obj); //操做對象obj //再次切斷obj與對象的關聯 obj = null }

第一種方法,若垃圾回收機制在代碼1和2之間回收了弱引用的對象,那麼obj仍可能爲null。而方法二不會出現這種狀況。

 

第二部分:實驗部分

實驗1 導入第6章示例程序,測試程序並進行代碼註釋。

編輯、編譯、調試運行閱讀教材214頁-215頁程序6-一、6-2,理解程序並分析程序運行結果;

l 在程序中相關代碼處添加新知識的註釋。

l 掌握接口的實現用法;

l 掌握內置接口Compareable的用法。

程序以下:

import java.util.*;

/**
 * This program demonstrates the use of the Comparable interface.
 * @version 1.30 2004-02-27
 * @author Cay Horstmann
 */
public class EmployeeSortTest
{
   public static void main(String[] args)
   {
      Employee[] staff = new Employee[3];

      staff[0] = new Employee("Harry Hacker", 35000);
      staff[1] = new Employee("Carl Cracker", 75000);
      staff[2] = new Employee("Tony Tester", 38000);

      Arrays.sort(staff);//調用Arrays類的sort方法(只有被static方法修飾了才能夠這樣調用)

      //輸出全部employee對象的信息
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
   }
}

 

 用戶自定義模塊:

public class Employee implements Comparable<Employee>//實現接口類
{
   private String name;
   private double salary;

   public Employee(String name, double salary)
   {
      this.name = name;
      this.salary = salary;
   }

   public String getName()
   {
      return name;
   }

   public double getSalary()
   {
      return salary;
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   /**
    * Compares employees by salary
    * @param other another Employee object
    * @return a negative value if this employee has a lower salary than
    * otherObject, 0 if the salaries are the same, a positive value otherwise
    */
   public int compareTo(Employee other)//比較Employee與其餘對象的大小
   {
      return Double.compare(salary, other.salary);//調用double的compare方法
   }
}

程序運行結果以下:

 

 

測試程序2:

編輯、編譯、調試如下程序,結合程序運行結果理解程序:

interface  A//接口A
{
  double g=9.8;
  void show( );
}
class C implements A//C實現接口A
{
  public void show( )
  {System.out.println("g="+g);}
}
class InterfaceTest
{
  public static void main(String[ ] args)
  {
       A a=new C( );
       a.show( );
       System.out.println("g="+C.g);//C實現了接口A,因此能夠用C調用A中的變量
  }
}

程序運行結果以下:

 

 

 

測試程序3:

l 在elipse IDE中調試運行教材223頁6-3,結合程序運行結果理解程序;

l 26行、36行代碼參閱224頁,詳細內容涉及教材12章。

l 在程序中相關代碼處添加新知識的註釋。

l 掌握回調程序設計模式;

程序以下:

/**
   @version 1.01 2015-05-12
   @author Cay Horstmann
*/

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer; 
// to resolve conflict with java.util.Timer

public class TimerTest
{  
   public static void main(String[] args)
   {  
      ActionListener listener = new TimePrinter();//實現ActionListener類接口

      //建立一個名爲listener的timer數組
      // 每十秒鐘一次
      Timer t = new Timer(10000, listener);//生成內置類對象
      t.start();//調用t中的start方法

      JOptionPane.showMessageDialog(null, "Quit program?");//窗口顯示信息「Quit program?」
      System.exit(0);
   }
}

class TimePrinter implements ActionListener//用戶自定義類:實現接口
{  
   public void actionPerformed(ActionEvent event)
   {  
      System.out.println("At the tone, the time is " + new Date());
      Toolkit.getDefaultToolkit().beep();//返回Toolkit方法,藉助Toolkit對象控制響鈴
   }
}

程序運行結果以下:

 

 

測試程序4:

 調試運行教材229頁-231頁程序6-四、6-5,結合程序運行結果理解程序;

 在程序中相關代碼處添加新知識的註釋。

 掌握對象克隆實現技術;

 掌握淺拷貝和深拷貝的差異。

程序以下:

import java.util.Date;
import java.util.GregorianCalendar;

public class Employee implements Cloneable
{
   private String name;
   private double salary;
   private Date hireDay;

   public Employee(String name, double salary)
   {
      this.name = name;
      this.salary = salary;
      hireDay = new Date();
   }

   public Employee clone() throws CloneNotSupportedException
   {
      // 調用對象克隆
      Employee cloned = (Employee) super.clone();

      // 克隆易變字段
      cloned.hireDay = (Date) hireDay.clone();

      return cloned;
   }//可能產生異常,放在try子句中

   /**
    * Set the hire day to a given date. 
    * @param year the year of the hire day
    * @param month the month of the hire day
    * @param day the day of the hire day
    */
   public void setHireDay(int year, int month, int day)
   {
      Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
      
      // 實例字段突變
      hireDay.setTime(newHireDay.getTime());
   }

   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }

   public String toString()
   {
      return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
   }
}
/**
 * This program demonstrates cloning.
 * @version 1.10 2002-07-01
 * @author Cay Horstmann
 */
public class CloneTest
{
   public static void main(String[] args)
   {
      try//try子句,後面是有可能會產生錯誤的代碼
      {
         Employee original = new Employee("John Q. Public", 50000);
         original.setHireDay(2000, 1, 1);
         Employee copy = original.clone();
         copy.raiseSalary(10);
         copy.setHireDay(2002, 12, 31);
         System.out.println("original=" + original);
         System.out.println("copy=" + copy);
      }
      catch (CloneNotSupportedException e)//沒有實現cloneable接口,拋出一個異常
      {
         e.printStackTrace();
      }
   }
}

程序運行結果以下:

 

 

 

實驗2 導入第6章示例程序6-6,學習Lambda表達式用法。

l 調試運行教材233頁-234頁程序6-6,結合程序運行結果理解程序;

l 在程序中相關代碼處添加新知識的註釋。

將27-29行代碼與教材223頁程序對比,將27-29行代碼與此程序對比,體會Lambda表達式的優勢。

程序以下:

import java.util.*;

import javax.swing.*;
import javax.swing.Timer;

/**
 * This program demonstrates the use of lambda expressions.
 * @version 1.0 2015-05-12
 * @author Cay Horstmann
 */
public class LambdaTest
{
   public static void main(String[] args)
   {
      String[] planets = new String[] { "Mercury", "Venus", "Earth", "Mars", 
            "Jupiter", "Saturn", "Uranus", "Neptune" };
      System.out.println(Arrays.toString(planets));
      System.out.println("Sorted in dictionary order:");
      Arrays.sort(planets);//調用Arrays類的sort方法
      System.out.println(Arrays.toString(planets));
      System.out.println("Sorted by length:");
      Arrays.sort(planets, (first, second) -> first.length() - second.length());//lambda表達式
      System.out.println(Arrays.toString(planets));
            
      Timer t = new Timer(1000, event ->
         System.out.println("The time is " + new Date()));
      t.start();   
         
      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null, "Quit program?");//窗口顯示信息「Quit program?」
      System.exit(0);         
   }
}

程序運行結果以下:

 

實驗3: 編程練習

l 編制一個程序,將身份證號.txt 中的信息讀入到內存中;

l 按姓名字典序輸出人員信息;

l 查詢最大年齡的人員信息;

l 查詢最小年齡人員信息;

 輸入你的年齡,查詢身份證號.txt中年齡與你最近人的姓名、身份證號、年齡、性別和出生地;

查詢人員中是否有你的同鄉。

程序以下:

import java.io.BufferedReader;
        import java.io.File;
        import java.io.FileInputStream;
        import java.io.FileNotFoundException;
        import java.io.IOException;
        import java.io.InputStreamReader;
        import java.util.ArrayList;
        import java.util.Arrays;
        import java.util.Collections;
        import java.util.Scanner;


public class Test{

      private static ArrayList<Person> Personlist1;
       public static void main(String[] args) {
         
          Personlist1 = new ArrayList<>();
         
          Scanner scanner = new Scanner(System.in);
          File file = new File("C:\\Users\\lenovo\\Documents\\身份證");
   
                try {
                     FileInputStream F = new FileInputStream(file);
                     BufferedReader in = new BufferedReader(new InputStreamReader(F));
                     String temp = null;
                     while ((temp = in.readLine()) != null) {
                        
                        Scanner linescanner = new Scanner(temp);
                        
                        linescanner.useDelimiter(" ");    
                        String name = linescanner.next();
                        String id = linescanner.next();
                        String sex = linescanner.next();
                        String age = linescanner.next();
                        String place =linescanner.nextLine();
                        Person Person = new Person();
                        Person.setname(name);
                        Person.setid(id);
                        Person.setsex(sex);
                        int a = Integer.parseInt(age);
                        Person.setage(a);
                        Person.setbirthplace(place);
                        Personlist1.add(Person);

                    }
                } catch (FileNotFoundException e) {
                    System.out.println("查找不到信息");
                    e.printStackTrace();
                } catch (IOException e) {
                    System.out.println("信息讀取有誤");
                    e.printStackTrace();
                }
                boolean isTrue = true;
                while (isTrue) {
                    System.out.println("1:按姓名字典序輸出人員信息;");
                    System.out.println("2:查詢最大年齡與最小年齡人員信息;");
                    System.out.println("3.輸入你的年齡,查詢身份證號.txt中年齡與你最近人的姓名、身份證號、年齡、性別和出生地");
                    System.out.println("4:按省份找你的同鄉;");
                    System.out.println("5:退出");
                    int type = scanner.nextInt();
                    switch (type) {
                    case 1:
                        Collections.sort(Personlist1);
                        System.out.println(Personlist1.toString());
                        break;
                    case 2:
                        
                        int max=0,min=100;int j,k1 = 0,k2=0;
                        for(int i=1;i<Personlist1.size();i++)
                        {
                            j=Personlist1.get(i).getage();
                           if(j>max)
                           {
                               max=j; 
                               k1=i;
                           }
                           if(j<min)
                           {
                               min=j; 
                               k2=i;
                           }

                        }  
                        System.out.println("年齡最大:"+Personlist1.get(k1));
                        System.out.println("年齡最小:"+Personlist1.get(k2));
                        break;
                    case 3:
                        System.out.println("place?");
                        String find = scanner.next();        
                        String place=find.substring(0,3);
                        String place2=find.substring(0,3);
                        for (int i = 0; i <Personlist1.size(); i++) 
                        {
                            if(Personlist1.get(i).getbirthplace().substring(1,4).equals(place)) 
                            {
                                System.out.println("你的同鄉:"+Personlist1.get(i));
                            }
                        } 

                        break;
                    case 4:
                        System.out.println("年齡:");
                        int yourage = scanner.nextInt();
                        int close=ageclose(yourage);
                        int d_value=yourage-Personlist1.get(close).getage();
                        System.out.println(""+Personlist1.get(close));
                  
                        break;
                    case 5:
                   isTrue = false;
                   System.out.println("再見!");
                        break;
                    default:
                        System.out.println("輸入有誤");
                    }
                }
            }
            public static int ageclose(int age) {
                   int m=0;
                int    max=53;
                int d_value=0;
                int k=0;
                for (int i = 0; i < Personlist1.size(); i++)
                {
                    d_value=Personlist1.get(i).getage()-age;
                    if(d_value<0) d_value=-d_value; 
                    if (d_value<max) 
                    {
                       max=d_value;
                       k=i;
                    }

                 }    return k;
                
             }
}
public class Person implements Comparable<Person> {
            private String name;
            private String id;
            private int age;
            private String sex;
            private String birthplace;

    public String getname() {
        return name;
        }
    public void setname(String name) {
        this.name = name;
    }
    public String getid() {
        return id;
    }
    public void setid(String id) {
        this.id= id;
    }
    public int getage() {
    
        return age;
    }
    public void setage(int age) {
        // int a = Integer.parseInt(age);
        this.age= age;
    }
    public String getsex() {
        return sex;
    }
    public void setsex(String sex) {
        this.sex= sex;
    }
    public String getbirthplace() {
        return birthplace;
    }
    public void setbirthplace(String birthplace) {
        this.birthplace= birthplace;
}

    public int compareTo(Person o) {
        return this.name.compareTo(o.getname());

}

    public String toString() {
        return  name+"\t"+sex+"\t"+age+"\t"+id+"\t";

}
}

程序運行結果以下:

 

實驗4:內部類語法驗證明驗

實驗程序1:

l 編輯、調試運行教材246頁-247頁程序6-7,結合程序運行結果理解程序;

l 瞭解內部類的基本用法。

程序以下:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

/**
 * This program demonstrates the use of inner classes.
 * @version 1.11 2015-05-12
 * @author Cay Horstmann
 */
public class InnerClassTest
{
   public static void main(String[] args)
   {
      TalkingClock clock = new TalkingClock(1000, true);
      clock.start();

      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

/**
 * A clock that prints the time in regular intervals.
 */
class TalkingClock
{
   private int interval;
   private boolean beep;

   /**
    * Constructs a talking clock
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public TalkingClock(int interval, boolean beep)
   {
      this.interval = interval;
      this.beep = beep;
   }

   /**
    * Starts the clock.
    */
   public void start()
   {
      ActionListener listener = new TimePrinter();
      Timer t = new Timer(interval, listener);
      t.start();
   }

   public class TimePrinter implements ActionListener
   {
      public void actionPerformed(ActionEvent event)
      {
         System.out.println("At the tone, the time is " + new Date());
         if (beep) Toolkit.getDefaultToolkit().beep();
      }
   }
}

 程序運行結果以下:

實驗程序2:

l 編輯、調試運行教材254頁程序6-8,結合程序運行結果理解程序;

瞭解匿名內部類的用法。

程序以下:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;

/**
 * This program demonstrates anonymous inner classes.
 * @version 1.11 2015-05-12
 * @author Cay Horstmann
 */
public class AnonymousInnerClassTest
{
   public static void main(String[] args)
   {
      TalkingClock clock = new TalkingClock();
      clock.start(1000, true);

      // keep program running until user selects "Ok"
      JOptionPane.showMessageDialog(null, "Quit program?");
      System.exit(0);
   }
}

/**
 * A clock that prints the time in regular intervals.
 */
class TalkingClock
{
   /**
    * Starts the clock.
    * @param interval the interval between messages (in milliseconds)
    * @param beep true if the clock should beep
    */
   public void start(int interval, boolean beep)
   {
      ActionListener listener = new ActionListener()
         {
            public void actionPerformed(ActionEvent event)
            {
               System.out.println("At the tone, the time is " + new Date());
               if (beep) Toolkit.getDefaultToolkit().beep();
            }
         };
      Timer t = new Timer(interval, listener);
      t.start();
   }
}

程序運行結果以下:

 

 

實驗程序3:

l 在elipse IDE中調試運行教材257頁-258頁程序6-9,結合程序運行結果理解程序;

l 瞭解靜態內部類的用法。

程序以下:

/**
 * This program demonstrates the use of static inner classes.
 * @version 1.02 2015-05-12
 * @author Cay Horstmann
 */
public class StaticInnerClassTest
{
   public static void main(String[] args)
   {
      double[] d = new double[20];
      for (int i = 0; i < d.length; i++)
         d[i] = 100 * Math.random();
      ArrayAlg.Pair p = ArrayAlg.minmax(d);
      System.out.println("min = " + p.getFirst());
      System.out.println("max = " + p.getSecond());
   }
}

class ArrayAlg
{
   /**
    * A pair of floating-point numbers
    */
   public static class Pair
   {
      private double first;
      private double second;

      /**
       * Constructs a pair from two floating-point numbers
       * @param f the first number
       * @param s the second number
       */
      public Pair(double f, double s)
      {
         first = f;
         second = s;
      }

      /**
       * Returns the first number of the pair
       * @return the first number
       */
      public double getFirst()
      {
         return first;
      }

      /**
       * Returns the second number of the pair
       * @return the second number
       */
      public double getSecond()
      {
         return second;
      }
   }

   /**
    * Computes both the minimum and the maximum of an array
    * @param values an array of floating-point numbers
    * @return a pair whose first element is the minimum and whose second element
    * is the maximum
    */
   public static Pair minmax(double[] values)
   {
      double min = Double.POSITIVE_INFINITY;
      double max = Double.NEGATIVE_INFINITY;
      for (double v : values)
      {
         if (min > v) min = v;
         if (max < v) max = v;
      }
      return new Pair(min, max);
   }
}

程序運行結果以下:

 

 

 3、實驗總結:

在本週的學習過程當中,主要了解了接口,接口和繼承在某些方面比較類似,可是接口又在繼承的基礎上發展了一些優勢,克服了java單繼承的缺點。在學習過程當中,多是由於接口並非具體的類,它只是實現,因此感受接口比繼承抽象一些,不太容易理解。但經過這周的學習以及實驗中對具體程序的運行,對接口有了必定的掌握。本身編寫飾演的過程當中,在以前的基礎上有的接口等新內容,本身仍是不能獨立完成,在同窗的幫助下才勉強完成了實驗。在實驗課上老師講的克隆以及函數接口等,本身尚未太掌握,在以後的學習中,必定會繼續深刻學習。

相關文章
相關標籤/搜索