Java學(xué)習(xí)總結(jié)之抽象類苞轿、接口茅诱、lambda表達(dá)式與內(nèi)部類

抽象類

在繼承的層次結(jié)構(gòu)中逗物,每個(gè)新子類都使類變得越來越明確具體。如果從一個(gè)子類追溯到父類瑟俭,類就會(huì)變得更通用和抽象翎卓。類的設(shè)計(jì)應(yīng)該確保父類包含它子類的共同特征。如果一個(gè)父類設(shè)計(jì)得非常抽象摆寄,以至于它沒有任何具體的實(shí)例失暴,這樣的類稱為抽象類,使用abstract關(guān)鍵字修飾微饥。抽象類定義了相關(guān)子類的共同行為逗扒。

抽象方法

如果一個(gè)方法非常抽象,只定義了方法畜号,沒有提供方法的具體實(shí)現(xiàn)缴阎,那么我們把它定義為一個(gè)抽象方法,它的具體實(shí)現(xiàn)由子類提供简软,即子類覆蓋抽象方法提供方法體蛮拔。
抽象方法由abstract關(guān)鍵字修飾,只有方法頭痹升,沒有花括號(hào)和方法體建炫,以分號(hào)結(jié)尾。比如一個(gè)GeometricObject類定義了一個(gè)名為getArea的抽象方法疼蛾,即public abstract double getArea();

幾點(diǎn)說明

1.抽象方法應(yīng)該定義為public肛跌,以便子類進(jìn)行重寫。
2.抽象類的構(gòu)造器應(yīng)該定義為protected察郁,因?yàn)槌橄箢惒荒芡ㄟ^new直接創(chuàng)建實(shí)例衍慎,其構(gòu)造器只被子類調(diào)用。創(chuàng)建一個(gè)具體子類的實(shí)例時(shí)皮钠,它的父類的構(gòu)造器被調(diào)用以初始化父類中定義的數(shù)據(jù)域稳捆。
3.一個(gè)包含抽象方法的類必須定義為抽象類,一個(gè)不包含抽象方法的類也可以定義為抽象類(如果不想讓某類創(chuàng)建實(shí)例麦轰,可以把它定義為抽象類)
4.如果子類繼承抽象類時(shí)沒有覆蓋其所有的抽象方法乔夯,即子類中仍有抽象方法,子類也應(yīng)該定義為抽象的
5.抽象方法是非靜態(tài)的
6.子類可以覆蓋父類的方法并將它定義為abstract,這種情況很少見款侵,但它在當(dāng)父類方法實(shí)現(xiàn)在子類中變得無效時(shí)是很有用的末荐,在這種情況下,子類必須定義為abstract
7.即使子類的父類是具體的新锈,這個(gè)子類也可以是抽象的甲脏。例如,Object是具體的,但它的子類GeometricObject是抽象的剃幌。
8.不能使用new操作符從一個(gè)抽象類創(chuàng)建一個(gè)實(shí)例聋涨,但是抽象類可以用作一種數(shù)據(jù)類型晾浴。下面的語句創(chuàng)建一個(gè)GeometricObject類型的數(shù)組是正確的:GeometricObject[] objects = new GeometricObject[10];然后可以創(chuàng)建一個(gè)具體子類的實(shí)例并把它的引用賦給數(shù)組负乡,如:objects[0] = new Circle();

接口

接口在很多方面都與抽象類很相似,但它的目的是指明相關(guān)或者不相關(guān)類的多個(gè)對(duì)象的共同行為脊凰,屬性成員都是公共靜態(tài)常量抖棘,成員方法都是公共抽象非靜態(tài)方法。例如狸涌,使用正確的接口切省,可以指明這些對(duì)象是可比較的、可克隆的帕胆。為了區(qū)分接口和類朝捆,Java采用Interface關(guān)鍵字定義接口。在一個(gè)java文件內(nèi)懒豹,只能有一個(gè)public類或一個(gè)public接口芙盘,即public類和public接口不能同文件共存。接口沒有構(gòu)造器脸秽,沒有實(shí)例域儒老,也不能使用new操作符創(chuàng)建實(shí)例。接口沒有構(gòu)造器的原因有三點(diǎn):
1.構(gòu)造器用于初始化成員變量记餐,接口沒有成員變量驮樊,不需要構(gòu)造器
2.類可以實(shí)現(xiàn)多個(gè)接口,如果多個(gè)接口都有構(gòu)造方法片酝,不好確定構(gòu)造方法鏈的調(diào)用次序
3.作為高度抽象的概念囚衔,接口不能實(shí)例化對(duì)象,也就不需要構(gòu)造器

像常規(guī)類一樣雕沿,每個(gè)接口都被編譯為獨(dú)立的字節(jié)碼文件练湿,可以作為引用變量的數(shù)據(jù)類型和類型轉(zhuǎn)換的結(jié)果,可以使用instanceof關(guān)鍵字等晦炊。
類實(shí)現(xiàn)接口用implements關(guān)鍵字鞠鲜,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,用逗號(hào)隔開即可断国,一個(gè)類必須實(shí)現(xiàn)它實(shí)現(xiàn)接口的所有方法贤姆,否則要定義為抽象類。一個(gè)接口可以繼承多個(gè)接口稳衬,用extends關(guān)鍵字霞捡,此時(shí)實(shí)現(xiàn)類需要重寫接口繼承鏈上所有接口的所有抽象方法。如果接口在繼承在多個(gè)父接口時(shí)薄疚,父接口中出現(xiàn)了重名的默認(rèn)方法沖突碧信,就要在該接口中提供一個(gè)同名默認(rèn)方法來解決沖突赊琳。
在定義接口中的數(shù)據(jù)域和方法時(shí)可以簡寫,例如:

public interface T{
    public static final int K = 1;
    public abstract void p();
 }

可簡寫成

public interface T{
    int K = 1;
    void p();
}

要注意接口中所有的數(shù)據(jù)域都是public static final砰碴,所有的方法都是public abstract躏筏,在定義接口中允許省略修飾符,但在子類重寫方法時(shí)不可缺省public修飾符呈枉,否則方法的可見性會(huì)縮小為包內(nèi)可見趁尼。
接口只能使用public修飾符或缺省訪問控制修飾符。
如果在具體實(shí)現(xiàn)類中定義了和接口中常量同名的常量猖辫,那么用接口變量指向?qū)崿F(xiàn)類引用時(shí)變量調(diào)用的常量仍然是接口中定義的常量酥泞。這是因?yàn)槌A繜o法被子類覆蓋。

靜態(tài)方法

從Java SE 8開始啃憎,允許在接口中增加靜態(tài)方法芝囤,并給靜態(tài)方法提供方法體實(shí)現(xiàn),該靜態(tài)方法只能通過接口名.靜態(tài)方法來調(diào)用辛萍。實(shí)現(xiàn)語法只要在方法前面加static關(guān)鍵字即可悯姊,這理論上講是可以的,但這有違于接口作為抽象規(guī)范的初衷叹阔。靜態(tài)方法只能被具體實(shí)現(xiàn)類繼承挠轴,不能在實(shí)現(xiàn)類中重寫。

默認(rèn)方法

可以為接口方法提供一個(gè)默認(rèn)方法體實(shí)現(xiàn)耳幢,在方法前加default修飾符即可岸晦,這樣子類無需重寫這個(gè)方法也能得到一個(gè)接口的默認(rèn)實(shí)現(xiàn)。例如:

public interface Collection
{
    int size();
    default boolean isEmpty()
    {
        return size() == 0;
    }
}

這樣實(shí)現(xiàn)Collection的程序員就不用操心實(shí)現(xiàn)isEmpty方法了睛藻。
當(dāng)然启上,默認(rèn)方法也可以被具體實(shí)現(xiàn)類重寫。在實(shí)現(xiàn)類中調(diào)用默認(rèn)方法要使用接口名.super.默認(rèn)方法來調(diào)用店印。
默認(rèn)方法的一個(gè)重要用法是“接口演化”冈在。以Collection接口為例,這個(gè)接口作為Java的一部分已經(jīng)很多年了按摘,假設(shè)很久以前定義了一個(gè)實(shí)現(xiàn)Collection接口的類Bag包券。后來在Collection接口中增加了一個(gè)stream方法,假設(shè)stream方法不是一個(gè)默認(rèn)方法炫贤,那么Bag類將不能編譯溅固,因?yàn)樗鼪]有實(shí)現(xiàn)這個(gè)新方法。如果不重新編譯這個(gè)類兰珍,而是使用原先包含這個(gè)類的JAR文件侍郭,這個(gè)類仍能正常加載,正常構(gòu)造實(shí)例,但如果在一個(gè)Bag實(shí)例上調(diào)用stream方法亮元,會(huì)出現(xiàn)一個(gè)AbstractMethodError募书。但如果把stream方法定義為默認(rèn)方法就可以解決這個(gè)問題痹愚,既可以重新編譯也可以使用JAR文件加載類并調(diào)用stream方法祟滴。

解決默認(rèn)方法的沖突

如果先在一個(gè)接口中將一個(gè)方法定義為默認(rèn)方法窃判,然后又在超類或另一個(gè)接口中定義了同樣的方法涣脚,會(huì)發(fā)生沖突次乓。解決沖突規(guī)則如下:

  1. 超類和接口沖突蚣驼。如果超類提供了一個(gè)具體方法偿警,那么根據(jù)超類優(yōu)先原則,同名而且有相同參數(shù)類型的默認(rèn)方法會(huì)被忽略苛秕。
  2. 多接口之間沖突。如果一個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)了多個(gè)接口找默,一個(gè)接口提供了一個(gè)默認(rèn)方法艇劫,另一個(gè)接口提供了一個(gè)同名而且參數(shù)類型(不論是否是默認(rèn)參數(shù))相同的方法,此時(shí)就發(fā)生了接口沖突惩激,必須在實(shí)現(xiàn)類中重寫這個(gè)方法來解決沖突店煞。

解決重名常量的沖突

1)超類和接口沖突。如果一個(gè)類繼承了一個(gè)超類和實(shí)現(xiàn)了若干接口风钻,此時(shí)不像默認(rèn)方法沖突一樣有超類優(yōu)先原則顷蟀。只能通過在實(shí)現(xiàn)類中覆蓋該常量來解決沖突。
2)多接口之間沖突骡技。如果一個(gè)類實(shí)現(xiàn)了多個(gè)接口鸣个,而這些接口又有重名常量,此時(shí)會(huì)發(fā)生沖突布朦。必須用接口名.常量的方式來精確指明要使用的常量囤萤。

Comparable接口

Comparable接口定義了compareTo方法,用于比較對(duì)象是趴。當(dāng)想使用Arrays類的sort方法對(duì)對(duì)象數(shù)組進(jìn)行排序時(shí)涛舍,對(duì)象所屬的類必須實(shí)現(xiàn)了Comparable接口。
Comparable接口是一個(gè)帶泛型的接口唆途,定義為:

public interface Comparable<E>{
    public int compareTo(E o);
 }

compareTo應(yīng)該與equals保持一致富雅,即當(dāng)且僅當(dāng)o1.equals(o2)為true時(shí),o1.compareTo(o2) == 0成立肛搬。以下是compareTo方法的實(shí)現(xiàn):

class Employee implements Comparable<Employee>{
public int compareTo(Employee other){
   return Double.compare(salary,other.salary);
   }
}

在比較浮點(diǎn)數(shù)時(shí)可以使用Double的靜態(tài)方法compare,這樣就不必?fù)?dān)心溢出或精度損失没佑,類似的還有Integer.compare方法等
繼承過程中的compareTo,如果由子類決定相等的概念滚婉,每個(gè)compare方法都應(yīng)該在開始時(shí)檢測:if(getClass() != other.getClass()) throw new ClassCastException()如果父類決定相等的概念图筹,應(yīng)該在超類中提供一個(gè)compareTo方法,并將這個(gè)方法聲明為final。

Comparator接口

Comparator接口意為"比較器"接口远剩,是一個(gè)泛型接口扣溺,可用于自定義排序規(guī)則和大小比較等。要進(jìn)行自定義排序瓜晤,Arrays.sort方法有一個(gè)重載版本锥余,需要提供一個(gè)數(shù)組和一個(gè)比較器作為參數(shù),比較器是實(shí)現(xiàn)了Comparator接口的類的實(shí)例痢掠。接口定義為:

public interface Comparator<T>
{
   int compare(T first,T second);
}

如果要按長度比較字符串驱犹,由于String是按字典序比較字符串,肯定不能讓String類用兩種方法實(shí)現(xiàn)compareTo方法 —— 況且String類也不由我們修改足画。此時(shí)可以定義如下實(shí)現(xiàn)Comparator<String>的類:

class lengthComparator implements Comparator<String>
{
   public int compare(String first,String second){
       return first.length() - second.length();
   }
}

因?yàn)橐{(diào)用compare方法雄驹,所以具體比較大小和排序時(shí)都要?jiǎng)?chuàng)建一個(gè)lengthComparator的實(shí)例:
大小比較

Comparator<String> comp = new LengthComparator();
if(comp.compare(words[i],words[j]) > 0) ...

自定義排序

String[] friends = {"Peter","Paul","Mary"};
Arrays.sort(friends,new LengthComparator());

Comparable接口和Comparator接口都可以用于自定義排序。比較如下:
1淹辞、Comparable接口需要在定義待比較的類的同時(shí)實(shí)現(xiàn)医舆,比如自定義的類,使用sort的不帶比較器的方法排序象缀。如果類設(shè)計(jì)者沒有考慮到比較問題而沒有實(shí)現(xiàn) Comparable 接口蔬将,此時(shí)我們無法修改類的定義,可以在外部定義一個(gè)實(shí)現(xiàn)了Comparator的比較器央星,并使用sort帶比較器的方法排序霞怀。這種情況下,我們是不需要改變類的莉给。當(dāng)然也可以在類設(shè)計(jì)時(shí)就實(shí)現(xiàn)Comparator接口毙石。
2、在集合中禁谦,我們可能需要有多重的排序標(biāo)準(zhǔn)胁黑,并在不同情況下靈活切換排序規(guī)則,這時(shí)候如果使用 Comparable 就有些捉襟見肘了州泊,可以自己繼承 Comparator 提供多種標(biāo)準(zhǔn)的比較器進(jìn)行排序丧蘸。

下面對(duì)于一個(gè)學(xué)生類的兩個(gè)關(guān)鍵字進(jìn)行排序,先按分?jǐn)?shù)從高到低排序遥皂,分?jǐn)?shù)相同按年齡從小到大排序力喷。
方法一:實(shí)現(xiàn)Comparable接口
重寫的compareTo方法為:

public int compareTo(Student stu){    
        if(this.score>stu.score){
            return -1 ;
        }else if(this.score < stu.score){
            return 1 ;
        }else{
            if(this.age>stu.age){
                return 1 ;
            }else if(this.age < stu.age){
                return -1 ;
            }else{
                return 0 ;
            }
        }    
    }

方法二:實(shí)現(xiàn)Comparator接口
重寫的compare方法為:

 public int compare(Student stu1,Student stu2){    
        if(stu1.score>stu2.score){
            return -1 ;
        }else if(stu1.score<stu2.score){
            return 1 ;
        }else{
            if(stu1.age>stu2.age){
                return 1 ;
            }else if(stu1.age<stu2.age){
                return -1 ;
            }else{
                return 0 ;
            }
        }    
    }

自定義排序總結(jié):無論是重寫compare方法還是compareTo方法,對(duì)大于演训、小于弟孟、等于三種情況都要有返回值,否則無法通過編譯样悟。在compareTo方法中拂募,規(guī)定 this.xxx > o.xxx 返回 1,this.xxx == o.xxx 返回0,this.xxx < o.xxx 返回-1是升序排列庭猩,反之就是降序排列。在compare方法中,規(guī)定o1.xxx > o2.xxx返回1,o1.xxx == o2.xxx返回0,o1.xxx < o2.xxx返回 -1是升序排列陈症,反之就是降序排列蔼水。
技巧:如果要比較的屬性也實(shí)現(xiàn)了Comparable接口,就可以調(diào)用它的compareTo方法录肯。如果要降序排列趴腋,就交換compareTo的參數(shù)順序即可。如果要比較的類是基本數(shù)據(jù)類型论咏,可以返回差值优炬,如果差值不是int類型,就轉(zhuǎn)換為int類型厅贪。

Cloneable接口

首先蠢护,我們考慮為一個(gè)包含對(duì)象引用的變量建立副本會(huì)發(fā)生什么,例如:

Employee original = new Employee("John Public",50000);
Employee copy = original;
copy.ratseSalary(10); //original的salary也被改變

原變量和副本都會(huì)指向同一個(gè)對(duì)象卦溢,這說明糊余,任何一個(gè)變量的改變都會(huì)影響到另一個(gè)變量。如果有一個(gè)對(duì)象original单寂,希望創(chuàng)建一個(gè)對(duì)象copy使得其初始狀態(tài)與original相同,但是之后它們各自回有自己不同的狀態(tài)吐辙,這種情況下就可以使用克隆宣决,例如:

Employee copy = original.clone();
copy.raiseSalary(10); //original的salary不會(huì)被改變

Object類中的clone方法將原始對(duì)象的每個(gè)數(shù)據(jù)域復(fù)制給目標(biāo)對(duì)象,如果一個(gè)數(shù)據(jù)域是基本數(shù)據(jù)類型昏苏,復(fù)制的就是它的值尊沸,如果是引用類型,復(fù)制的就是它的引用贤惯,這種克隆稱為淺復(fù)制,即original != copy,但original.hireDay == copy.hireDay洼专。這有時(shí)是不符合我們要求的,我們不希望在改變某個(gè)對(duì)象的引用類型的數(shù)據(jù)域時(shí)影響到另一個(gè)對(duì)象孵构,這時(shí)我們需要深復(fù)制,即如果數(shù)據(jù)域是引用類型屁商,復(fù)制的是對(duì)象的內(nèi)容而不是引用。
Object類中提供的原始clone方法的方法頭是protected native Object clone() throws CloneNotSupportedException,關(guān)鍵字native表明這個(gè)方法不是用Java寫的颈墅,但它是JVM針對(duì)自身平臺(tái)實(shí)現(xiàn)的蜡镶。關(guān)鍵字protected限定方法只能在同一個(gè)包內(nèi)或在其子類中訪問。由于這個(gè)原因:必須在要實(shí)現(xiàn)克隆的子類中覆蓋這個(gè)方法并把可見性改為public恤筛。
無論是淺復(fù)制還是深復(fù)制官还,我們都需要實(shí)現(xiàn)Cloneable接口,否則即使已經(jīng)重寫了clone()方法將類的可見性從protected擴(kuò)大到了public毒坛,仍然會(huì)拋出一個(gè)必檢異常CloneNotSupportedException望伦。Cloneable接口的定義是:

public interface Cloneable{

}

我們發(fā)現(xiàn)這個(gè)接口是空的林说,一個(gè)帶空體的接口稱為標(biāo)記接口。一個(gè)標(biāo)記接口既不包括常量也不包括方法屯伞,它用來表示一個(gè)類擁有的某些特定的屬性腿箩,其惟一的作用是允許在類型查詢中使用instanceof關(guān)鍵字。但如果一個(gè)請(qǐng)求克隆的對(duì)象不實(shí)現(xiàn)這個(gè)接口愕掏,會(huì)產(chǎn)生CloneNotSupportedException度秘,即使clone的默認(rèn)(淺拷貝)實(shí)現(xiàn)能夠滿足要求,還是要實(shí)現(xiàn)這一接口饵撑。應(yīng)該注意的是剑梳,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一個(gè) protected 方法滑潘。Cloneable 接口只是規(guī)定垢乙,如果一個(gè)類沒有實(shí)現(xiàn) Cloneable 接口又調(diào)用了 clone() 方法,就會(huì)拋出 CloneNotSupportedException语卤。

下面給出一個(gè)淺復(fù)制的例子:

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

下面給出一個(gè)深復(fù)制的例子:

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

我們注意到Object類的clone方法的返回值類型是Object追逮,而Employee類的clone方法返回值類型是Employee,這叫做協(xié)變返回類型粹舵,即子類在重寫父類方法時(shí)可以返回父類返回值類型的子類型钮孵。clone方法聲明異常也可以改成捕獲異常,如:

public Employee clone()
{
    try
    {
        Employee cloned = (Employee) super.clone();
        . . .
    }
    catch(CloneNotSupportedException e){ return null;}
 }

復(fù)制數(shù)組的四種方法

1.申請(qǐng)一個(gè)新數(shù)組眼滤,遍歷原數(shù)組逐一復(fù)制元素
2.使用System類的靜態(tài)方法arraycopy
3.使用數(shù)組對(duì)象.clone()返回一個(gè)數(shù)組克隆的引用
4.使用Arrays類的copyOf方法

接口和抽象類

區(qū)別:
1.接口所有的變量必須是public static final;抽象類的變量無限制
2.接口沒有構(gòu)造方法巴席,不能用new操作符實(shí)例化;抽象類有構(gòu)造方法,由子類通過構(gòu)造方法鏈調(diào)用诅需,不能用new操作符實(shí)例化
3.接口所有方法必須是公共抽象實(shí)例方法(Java SE 8開始允許定義靜態(tài)方法)漾唉,抽象類無限制
4.一個(gè)類只可以繼承一個(gè)父類,但可以實(shí)現(xiàn)多個(gè)接口
5.所有的類有一個(gè)共同的根Object類堰塌,接口沒有共同的根
6.抽象類和子類的關(guān)系應(yīng)該是強(qiáng)的“是一種”關(guān)系(strong is-a relationship),而接口和子類的關(guān)系是弱的"是一種"關(guān)系(weak is-a relationship)赵刑。接口比抽象類更靈活,因?yàn)閷?shí)現(xiàn)接口的子類只需要具有統(tǒng)一的行為即可场刑,不需要都屬于同一個(gè)類型的類般此。

接口與回調(diào)

回調(diào)是一種常見的程序設(shè)計(jì)模式∫“睿回調(diào)是一種雙向調(diào)用模式恤煞,也就是說,被調(diào)用方在接口被調(diào)用時(shí)也會(huì)調(diào)用對(duì)方的接口施籍。
見博客:Java回調(diào)機(jī)制(CallBack)詳解

內(nèi)部類

內(nèi)部類居扒,或者稱為嵌套類,是一個(gè)定義在另一個(gè)類范圍中的類丑慎。一個(gè)內(nèi)部類可以如常規(guī)類一樣使用喜喂。通常瓤摧,在一個(gè)類只被它的外部類所使用的時(shí)候,才將它定義為內(nèi)部類玉吁,內(nèi)部類機(jī)制主要用于設(shè)計(jì)具有互相協(xié)作關(guān)系的類集合照弥。比如:

//OuterClass.java: inner class demo
public class OuterClass {
   private int data;
   /** A method in the outer class */
   public void m(){
   //Do something
   }
   // An inner class
   class InnerClass {
   /** A method in the inner class */
   public void mi(){
       data++;
       m();
     }
   }
 }

使用內(nèi)部類的好處:
1.內(nèi)部類可以很好地實(shí)現(xiàn)隱藏:一般的非內(nèi)部類,是不允許有 private 與protected權(quán)限的进副,但內(nèi)部類可以这揣。
2.成員內(nèi)部類擁有外圍類的所有元素的訪問權(quán)限:內(nèi)部類可以訪問包含它的外部類的所有數(shù)據(jù)域(包括私有數(shù)據(jù)域)和方法,沒有必要將外部類對(duì)象的引用傳遞給內(nèi)部類的構(gòu)造方法影斑,內(nèi)部類有一個(gè)指向外部類對(duì)象的隱式引用给赞,如果顯式寫出,外部類的引用是OuterClass.this矫户。
3.可以間接實(shí)現(xiàn)多重繼承:比如在A類中定義兩個(gè)內(nèi)部類innerClass1和innerClass2分別繼承B類和C類片迅,則A類就具有了B類和C類的屬性和方法,間接實(shí)現(xiàn)了多重繼承皆辽。
4.減小了類文件編譯后的產(chǎn)生的字節(jié)碼文件的大小

內(nèi)部類具有一下特征:

  1. 一個(gè)內(nèi)部類被編譯成一個(gè)名為OuterClassName$InnerClassName的類柑蛇。例如,一個(gè)定義在Test類中的成員內(nèi)部類A被編譯成Test$A.class 驱闷。
  2. 內(nèi)部類對(duì)象通常在外部類中創(chuàng)建耻台,但是你也可以從另外一個(gè)類中來創(chuàng)建一個(gè)內(nèi)部類的對(duì)象。如果是成員內(nèi)部類空另,你必須先創(chuàng)建一個(gè)外部類的實(shí)例粘我,然后使用下面的語法創(chuàng)建一個(gè)內(nèi)部類對(duì)象:OuterClass.InnerClass innerObject = outerObject.new InnerClass(); 如果是靜態(tài)內(nèi)部類的,使用下面語法來創(chuàng)建一個(gè)內(nèi)部類對(duì)象:OuterClass.InnerClass innerObject = new OuterClass.InnerClass();痹换。

一般建議在外部類中定義一個(gè)用于獲取內(nèi)部類對(duì)象的方法,以便于從外部類外獲取內(nèi)部類對(duì)象都弹,比如:

public InnerClass getInnerClass(){
   return new InnerClass();
}

一個(gè)簡單的內(nèi)部類的用途是將相互依賴的類結(jié)合到一個(gè)主類中娇豫,這樣做減少了源文件的數(shù)量(因?yàn)榉莾?nèi)部類如果用public修飾必須放在不同的源文件中,而內(nèi)部類可放在同一源文件中)畅厢,這樣也使得類文件容易組織冯痢,因?yàn)樗鼈兌紝⒅黝惷鳛榍熬Y。另外一個(gè)內(nèi)部類的實(shí)際用途是避免類名沖突框杜。

內(nèi)部類對(duì)于定義處理器類非常有用浦楣,一個(gè)處理器類被設(shè)計(jì)為針對(duì)一個(gè)GUI組件創(chuàng)建一個(gè)處理器對(duì)象(比如,一個(gè)按鈕)咪辱。處理器類不會(huì)被其他應(yīng)用所共享振劳,所以將它定義在主類里面作為一個(gè)內(nèi)部類使用是恰如其分的。

廣泛意義上的內(nèi)部類一般來說包括四種:成員內(nèi)部類油狂、局部內(nèi)部類历恐、匿名內(nèi)部類靜態(tài)內(nèi)部類寸癌。下面就先來了解一下這四種內(nèi)部類的用法。

成員內(nèi)部類

成員內(nèi)部類是最普通的內(nèi)部類弱贼,一個(gè)成員內(nèi)部類可以使用可見性修飾符(public蒸苇、private、protected吮旅、default)所定義溪烤,和應(yīng)用于一個(gè)類中成員的可見性規(guī)則一樣。
形如下面的形式:

class Circle {
  private double radius = 0;
  public static int count =1;
  public Circle(double radius) {
      this.radius = radius;
  }
   
  class Draw {     //內(nèi)部類
      public void drawSahpe() {
          System.out.println(radius);  //外部類的private成員
          System.out.println(count);   //外部類的靜態(tài)成員
      }
  }
}

這樣看起來庇勃,類Draw像是類Circle的一個(gè)成員檬嘀,Circle稱為外部類。成員內(nèi)部類隱式持有外部類的引用 OuterClass.this 匪凉,可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態(tài)成員)枪眉,但外部類想要訪問內(nèi)部類的成員屬性和方法時(shí)必須先實(shí)例化內(nèi)部類對(duì)象。
 不過要注意的是再层,當(dāng)成員內(nèi)部類擁有和外部類同名的成員變量或者方法時(shí)贸铜,會(huì)發(fā)生隱藏現(xiàn)象,即默認(rèn)情況下訪問的是成員內(nèi)部類的成員聂受。如果要訪問外部類的同名成員蒿秦,需要顯式通過外部類的引用進(jìn)行訪問:外部類.this.成員變量 外部類.this.成員方法
如果要在非外部類的其他類中實(shí)例化成員內(nèi)部類對(duì)象蛋济,則需要先實(shí)例化外部類對(duì)象棍鳖。
即:

OuterClass outerObject = new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

注意:成員內(nèi)部類中不能定義靜態(tài)變量和靜態(tài)方法,只能定義靜態(tài)常量碗旅。

靜態(tài)內(nèi)部類

有時(shí)候渡处,使用內(nèi)部類只是為了把一個(gè)類隱藏在另外一個(gè)類的內(nèi)部,并不需要內(nèi)部類引用外圍類的元素祟辟。為此医瘫,可以為內(nèi)部類加上static關(guān)鍵字聲明為靜態(tài)內(nèi)部類。
靜態(tài)內(nèi)部類不持有外部類的引用旧困,只能訪問外部類的靜態(tài)成員變量和方法醇份,而不能訪問外部類的非靜態(tài)成員屬性和非靜態(tài)方法,如果要調(diào)用和訪問吼具,必須實(shí)例化外部類對(duì)象僚纷。當(dāng)靜態(tài)內(nèi)部類擁有和外部類同名的成員變量或者方法時(shí),會(huì)發(fā)生隱藏現(xiàn)象拗盒,即默認(rèn)情況下訪問的是靜態(tài)內(nèi)部類的成員怖竭。如果要訪問外部類的非靜態(tài)同名成員,不能再使用外部類.this.成員的形式锣咒,而是要實(shí)例化外部類對(duì)象侵状。如果要訪問外部類的靜態(tài)同名成員赞弥,可以通過外部類.成員的方式來訪問。
與常規(guī)內(nèi)部類不同趣兄,靜態(tài)內(nèi)部類可以有靜態(tài)變量靜態(tài)方法绽左。可以通過外部類.內(nèi)部類.靜態(tài)成員方式來訪問艇潭。
如果要在非外部類的其他類中實(shí)例化靜態(tài)內(nèi)部類對(duì)象拼窥,不需要先實(shí)例化外部類對(duì)象:

OuterClass.InnerClass innerObject = new OuterClass.InnerClass();

下面是一個(gè)使用靜態(tài)內(nèi)部類的典型例子√D考慮一下計(jì)算一個(gè)數(shù)組中最大值和最小值的問題鲁纠,當(dāng)然,可以編寫兩個(gè)方法鳍寂,一個(gè)計(jì)算最大值改含,一個(gè)計(jì)算最小值,在調(diào)用這兩個(gè)方法的時(shí)候迄汛,數(shù)組被遍歷兩次捍壤,而如果數(shù)組只被遍歷一次就可以計(jì)算出最大值和最小值,那么效率就大大提高了鞍爱。通過一個(gè)方法就計(jì)算出最大值和最小值:這個(gè)方法需要返回兩個(gè)數(shù)(max 和 min)鹃觉,為此可以定義一個(gè)Pair類來封裝這種數(shù)據(jù)結(jié)構(gòu),但是Pair是個(gè)非常大眾的名字睹逃,可能在其他地方定義過盗扇,會(huì)發(fā)生名字沖突,此時(shí)可以將Pair定義為ArrayAlg類的內(nèi)部類ArrayAlg.Pair沉填。又因?yàn)镻air沒有必要訪問外圍類ArrayAlg的數(shù)據(jù)域或方法疗隶,應(yīng)該定義為靜態(tài)內(nèi)部類。
下面給出代碼:


public class ArrayAlg{
   //Pair類翼闹,起數(shù)據(jù)封裝的作用
   public static class Pair{
       private double first;
       private double second;

       public Pair(double f, double s){
           first = f;
           second = s;
       }

       public double getFirst(){
           return first;
       }

       public double getSecond(){
           return second;
       }
   }

   public static Pair maxmin(double[] values){
       double min = Double.POSITIVE_INFNITY;
       double max = Double.NEGATIVE_INFNITY;

       for(double x : values){
           if(x<min) min = x;
           if(x>max) max = x;
       }
       return new Pair(max,min);
   }

   public static void main(String[] args){
       Test te = new Test();
       double[] teArgs = new double[]{2.13,100.0,11.2,34.5,67.1,88.9};
       Pair res = te.maxmin(teArgs);
       System.out.println("max = "+res.getFirst());
       System.out.println("min = "+res.getSecond());
   }
}

特別注意:代碼中的Pair類如果沒有聲明為static抽减,就不能在靜態(tài)方法minmax中構(gòu)造Pair的實(shí)例,編譯器會(huì)給出錯(cuò)誤報(bào)告:沒有可用的隱式ArrayAlg類型對(duì)象初始化內(nèi)部類對(duì)象

局部內(nèi)部類

可以把內(nèi)部類定義在一個(gè)方法中橄碾,稱為局部內(nèi)部類,也叫方法內(nèi)部類颠锉。局部內(nèi)部類就像是方法里面的一個(gè)局部變量一樣法牲,不能有public、protected琼掠、private以及static修飾符拒垃。它的作用域被限定在聲明這個(gè)局部類的塊中。局部類有一個(gè)優(yōu)勢瓷蛙,即對(duì)外部世界完全隱藏起來悼瓮。即使外部類中的其他代碼也不能訪問它戈毒。除了其所在的方法之外,沒有任何方法知道該局部類的存在横堡。局部內(nèi)部類只能訪問被final修飾的局部變量埋市。
局部內(nèi)部類被編譯器編譯成一個(gè)OuterClassName$1InnerClassName的類。序號(hào)逐漸遞增命贴。

class People{
   public People() {
        
   }
}

class Man{
   public Man(){
        
   }
    
   public People getWoman(){
       class Woman extends People{   //局部內(nèi)部類
           int age =0;
       }
       return new Woman();
   }
}

注意:上述代碼中通過調(diào)用getWoman()獲取了局部內(nèi)部類Woman的引用道宅,不能通過局部內(nèi)部類引用.屬性的方式來直接訪問局部內(nèi)部類的成員,所以我們一般會(huì)在該方法中直接調(diào)用局部內(nèi)部類的方法進(jìn)行某種操作胸蛛,然后返回操作結(jié)果污茵。

匿名內(nèi)部類

有時(shí)我們?cè)诔绦蛑袑?duì)一個(gè)類只使用一次,此時(shí)就可以把類的定義和實(shí)例化對(duì)象整合在一起葬项,來簡化對(duì)于抽象類和接口實(shí)現(xiàn)的操作泞当,這就是匿名內(nèi)部類
一個(gè)匿名內(nèi)部類是一個(gè)沒有名字的內(nèi)部類民珍,其語法如下:

new SuperClassName/InterfaceName(){
   //implement or override methods in superclass or interface
   
    //Other methods if necessary
 }

其含義是創(chuàng)建一個(gè)繼承自SuperClass或?qū)崿F(xiàn)Interface的類的實(shí)例襟士,并在類塊內(nèi)重寫父類或接口的抽象方法,應(yīng)該將匿名內(nèi)部類理解成一個(gè)匿名子類的匿名對(duì)象,而不是理解成一個(gè)類穷缤。

匿名內(nèi)部類有如下特征:
1.沒有可見性修飾符
2.沒有構(gòu)造方法(因?yàn)闆]有名字,無法命名構(gòu)造方法),但可以有構(gòu)造代碼塊敌蜂,也可以調(diào)用父類的構(gòu)造方法,即new SuperClassName()調(diào)用父類無參構(gòu)造方法津肛,new SuperClassName(args1,...)調(diào)用父類有參構(gòu)造方法章喉。如果實(shí)現(xiàn)的是接口,則不能有任何參數(shù)身坐,但是小括號(hào)仍然不可缺省
3.必須總是從一個(gè)父類繼承或者實(shí)現(xiàn)一個(gè)接口秸脱,但是它不能有顯式的extends或者implements子句
4.必須實(shí)現(xiàn)父類或接口中的所有抽象方法
5.一個(gè)匿名內(nèi)部類被編譯成一個(gè)名為OuterClassName$n.class的類,例如:如果外部類Test有兩個(gè)匿名內(nèi)部類部蛇,分別被編譯成Test$1.classTest$2.class
6.匿名內(nèi)部類不能訪問外部類方法中的局部變量摊唇,除非該變量被聲明為final類型。從jdk1.8開始涯鲁,如果局部變量被匿名內(nèi)部類訪問巷查,那么該局部變量相當(dāng)于自動(dòng)使用了final修飾。
這樣設(shè)計(jì)的具體原因見分析:JAVA中匿名內(nèi)部類訪問的局部變量為什么要用final修飾抹腿?

應(yīng)用一
下面的技巧稱為"雙括號(hào)初始化"岛请,這里利用了內(nèi)部類語法。假設(shè)你想構(gòu)造一個(gè)數(shù)組列表警绩,并將它傳遞到一個(gè)方法崇败。

ArrayList<String> friends = new ArrayList<String>();
friends.add("Harry");
friends.add("Tony");
invite(friends);

如果不再需要這個(gè)數(shù)組列表,最好讓它作為一個(gè)匿名列表。語法如下:

invite(new ArrayList<String> 
{
   {
       add("Harry");
       add("Tony");
    }
});

注意這里的雙括號(hào)后室,外括號(hào)建立了一個(gè)ArrayList的匿名子表缩膝,內(nèi)括號(hào)則是一個(gè)對(duì)象構(gòu)造塊。

應(yīng)用二
生成日志或調(diào)試消息時(shí)岸霹,通常希望包含當(dāng)前類的類名疾层,如:
System.err.println("Something awful happened in " + getClass());
不過這對(duì)于靜態(tài)方法并不湊效,因?yàn)檎{(diào)用getClass()調(diào)用的是this.getClass(),但靜態(tài)方法里沒有this松申,所以應(yīng)該使用下面的表達(dá)式:new Object(){}.getClass().getEnclosingClass(),在這里云芦,new Object(){} 會(huì)建立Object的一個(gè)匿名子類的匿名對(duì)象,getEnclosingClass則得到其外圍類贸桶,也就是包含這個(gè)靜態(tài)方法的類

lambda表達(dá)式

Lambda表達(dá)式(也叫做閉包)是Java 8中最大的也是期待已久的變化舅逸。它允許我們將一個(gè)函數(shù)當(dāng)作方法的參數(shù)(傳遞函數(shù)),或者說把代碼當(dāng)作數(shù)據(jù)皇筛,這是每個(gè)函數(shù)式編程者熟悉的概念琉历。它是一種表示可以在將來某個(gè)時(shí)間點(diǎn)執(zhí)行的代碼塊的簡潔方法。使用lambda表達(dá)式水醋,可以用一種精簡的方式表示使用回調(diào)或變量行為的代碼旗笔。如果要編譯器理解lambda表達(dá)式,其代替的匿名內(nèi)部類實(shí)現(xiàn)的接口必須有且僅有一個(gè)抽象方法拄踪,但是可以有多個(gè)非抽象方法蝇恶,這樣的接口被稱為函數(shù)式接口(功能接口、單抽象方法接口)惶桐。在底層撮弧,接受lambda表達(dá)式的方法會(huì)接受實(shí)現(xiàn)某函數(shù)式接口的類的對(duì)象,并在這個(gè)對(duì)象上調(diào)用接口的方法姚糊,所以可以把lambda表達(dá)式賦給函數(shù)式接口(lambda表達(dá)式實(shí)際是一個(gè)實(shí)現(xiàn)了該函數(shù)式接口的類的類型贿衍,這里用到了多態(tài)),不能把lambda表達(dá)式賦給Object變量救恨,因?yàn)镺bject不是一個(gè)函數(shù)式接口贸辈。
一個(gè)lambda表達(dá)式就是一個(gè)代碼塊,以及必須傳入代碼的變量規(guī)范肠槽。其基礎(chǔ)語法是(expression只有一條語句擎淤,不用花括號(hào),也不用分號(hào)結(jié)尾)

(type1 param1, type2 param2, ...) -> expression

或者(statements是多條語句秸仙,要花括號(hào)揉燃,每條語句之后要分號(hào)結(jié)尾)

 (type1 param1, type2 param2, ...) -> {statements;}

一個(gè)參數(shù)的數(shù)據(jù)類型既可以顯式聲明,也可以由編譯器隱式推斷筋栋。如果只有一個(gè)參數(shù),并且沒有顯式的數(shù)據(jù)類型正驻,圓括號(hào)可以被省略弊攘。如:

e -> {
// Code for processing event e
}

即使lambda表達(dá)式?jīng)]有參數(shù)抢腐,也要提供空括號(hào),就像無參數(shù)方法一樣:

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

無需指定lambda表達(dá)式的返回類型襟交,編譯器會(huì)由上下文推斷迈倍,例如:

(String first,String second) -> first.length() - second.length()

可以在需要int類型結(jié)果的上下文中使用

如果一個(gè)lambda表達(dá)式只在某些分支上返回一個(gè)值,而在另外一些分支不返回值捣域,是不合法的啼染。例如:

(int x) -> {if(x >= 0) return 1;}

Comparator接口是一個(gè)函數(shù)式接口,可以用lambda表達(dá)式實(shí)現(xiàn)自定義排序的簡化:

Arrays.sort(words,(first,second) 
-> first.length() - second.length());

函數(shù)式接口

對(duì)于只有一個(gè)抽象方法的接口焕梅,需要這種接口的對(duì)象時(shí)迹鹅,就可以提供一個(gè)lambda表達(dá)式,這種接口稱為函數(shù)式接口贞言。
如果自己設(shè)計(jì)了一個(gè)函數(shù)式接口斜棚,可以用@FunctionalInterface注解來標(biāo)記這個(gè)接口,這樣做有兩個(gè)好處:
1.可以在你無意中增加一個(gè)非抽象方法時(shí)產(chǎn)生編譯錯(cuò)誤
2.javadoc頁里會(huì)指出你的接口是一個(gè)函數(shù)式接口

方法引用

方法引用提供了非常有用的語法该窗,可以直接引用已有Java類或?qū)ο螅▽?shí)例)的方法或構(gòu)造器弟蚀。與lambda聯(lián)合使用,方法引用可以使語言的構(gòu)造更緊湊簡潔酗失,減少冗余代碼义钉。例如,假設(shè)你希望只要出現(xiàn)一個(gè)定時(shí)器事件就打印這個(gè)事件對(duì)象规肴,可以調(diào)用:

Timer t = new Timer(1000,event -> System.out.println(event));

可以直接把println方法傳遞到Timer的構(gòu)造器:

Timer t = new Timer(1000,System.out::println);

表達(dá)式System.out::println是一個(gè)方法引用捶闸,它等價(jià)于lambda表達(dá)式x -> System.out.println(x)
我們?cè)倏匆粋€(gè)例子,假設(shè)要對(duì)字符串排序奏纪,而不考慮字母的大小寫鉴嗤,可以調(diào)用Arrays.sort(strings,String::compareToIgnoreCase);

方法引用主要有三種情況:

  • object::instanceMethod
  • Class::staticMethod
  • Class::instanceMethod

對(duì)于前兩種情況,方法引用等價(jià)于提供方法參數(shù)的lambda表達(dá)式序调。比如:System.out::println等價(jià)于x -> System.out.println(x)醉锅,Math::pow等價(jià)于(x,y) -> Math.pow(x,y)。第三種情況的第一個(gè)參數(shù)會(huì)稱成為調(diào)用方法的目標(biāo)對(duì)象发绢,其余參數(shù)成為方法參數(shù)硬耍,比如:String::compareToIgnoreCase等價(jià)于(x,y) -> x.compareToIgnoreCase(y)
可以在方法里使用this和super,this::equals等同于x -> this.equals(x),super::greet等同于() -> super.greet()

類似于lambda表達(dá)式,方法引用不能獨(dú)立存在边酒,總是會(huì)轉(zhuǎn)換為函數(shù)式接口的實(shí)例经柴。

構(gòu)造器引用

構(gòu)造器引用與方法引用類似,只不過方法名為new墩朦。例如Employee::new是Employee構(gòu)造器的一個(gè)引用坯认。至于是哪一個(gè)構(gòu)造器取決于上下文,比如Function<Integer,Employee> func1 = Employee :: new;就相當(dāng)于Function<Integer,Employee> func = x -> new Employee(x);
數(shù)組類型也有構(gòu)造器引用,如int[]::new等價(jià)于lambda表達(dá)式x -> new int[x]

處理lambda表達(dá)式

我們之前提到牛哺,lambda表達(dá)式的重點(diǎn)是延遲執(zhí)行陋气,之所以希望以后再執(zhí)行代碼,有很多原因引润,如:

  • 在一個(gè)單獨(dú)的線程中運(yùn)行代碼
  • 多次運(yùn)行代碼
  • 在算法的恰當(dāng)位置運(yùn)行代碼(例如巩趁,排序中的比較操作)
  • 發(fā)生某種情況時(shí)執(zhí)行代碼(如,點(diǎn)擊了一個(gè)按鈕淳附、數(shù)據(jù)到達(dá)等)
  • 只在必要時(shí)才運(yùn)行代碼

下面是常用的函數(shù)式接口和基本類型的函數(shù)式接口:

1.png
2.png

下面來看一個(gè)簡單的例子议慰。假設(shè)你想要重復(fù)一個(gè)動(dòng)作n次。將這個(gè)動(dòng)作和重復(fù)次數(shù)傳遞給一個(gè)repeat方法:

repeat(10,() -> System.out.println("Hello world"));

要接受這個(gè)lambda表達(dá)式奴曙,需要選擇一個(gè)函數(shù)式接口别凹。在這里,我們可以使用Runnable接口:

public static void repeat(int n,Runnable action)
{
    for(int i = 0;i < n;i++) 
    action.run();
}

現(xiàn)在讓這個(gè)例子更復(fù)雜一點(diǎn)缆毁,我們希望告訴這個(gè)動(dòng)作它出現(xiàn)在那一次迭代中番川。為此需要選擇一個(gè)合適的函數(shù)式接口,其中要包含一個(gè)方法脊框。這個(gè)方法有一個(gè)int參數(shù)而且返回類型為void颁督。處理int值的標(biāo)準(zhǔn)接口如下:

public interface IntConsumer
{
    void accept(int value);
}

下面給出repeat方法的改進(jìn)版本:

public static void repeat(int n,IntConsumer action)
{
    for(int i = 0;i < n;i++)  action.accept(i);
}

可以如下調(diào)用它:

repeat(10,i -> System.out.println("Countdown: " + (9 - i)));

大多數(shù)函數(shù)標(biāo)準(zhǔn)函數(shù)式接口都提供了非抽象方法來生成或合并函數(shù)。例如,Predicate.isEqual(a)等同于a::equals,不過如果a為null也能正常工作浇雹。已經(jīng)提供了默認(rèn)方法and沉御、or和negate來合并謂詞。例如,Predicate.isEqual(a).or(Predicate.isEqual(b))就等同于x -> a.equals(x) || b.equals(x)

通過三種方式實(shí)現(xiàn)事件處理器

1.內(nèi)部類

public class HandleEvent extends Application {

   @Override
   public void start(Stage primaryStage) throws Exception {
       HBox pane = new HBox(10);
       pane.setAlignment(Pos.CENTER);
       Button btOK = new Button("OK");
       OKHandlerClass handler1 = new OKHandlerClass();
       btOK.setOnAction(handler1);
       Button btCancel = new Button("Cancel");
       CancelHandlerClass handler2 = new CancelHandlerClass();
       btCancel.setOnAction(handler2);
       pane.getChildren().addAll(btOK,btCancel);
       
       Scene scene = new Scene(pane,100,50);
       primaryStage.setTitle("HandleEvent");
       primaryStage.setScene(scene);
       primaryStage.show();
   }

   
   class OKHandlerClass implements EventHandler<ActionEvent>{
       @Override
       public void handle(ActionEvent e) {
           System.out.println("OK button clicked");
       }
   }
   
   class CancelHandlerClass implements EventHandler<ActionEvent>{
       @Override
       public void handle(ActionEvent e) {
           System.out.println("Cancel button clicked");
       }
   }
   public static void main(String[] args) {
       Application.launch(args);

   }

}

2.匿名內(nèi)部類

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class AnonymousHandlerDemo extends Application {

   @Override
   public void start(Stage primaryStage) throws Exception {
       HBox hBox = new HBox();
       hBox.setSpacing(10);
       hBox.setAlignment(Pos.CENTER);
       Button btNew = new Button("New");
       Button btOpen = new Button("Open");
       Button btSave= new Button("Save");
       Button btPrint = new Button("Print");
       hBox.getChildren().addAll(btNew,btOpen,btSave,btPrint);
       
       btNew.setOnAction(new EventHandler<ActionEvent>() {
           @Override
           public void handle(ActionEvent e) {
               System.out.println("Process New");
           }
       });
       
       btOpen.setOnAction(new EventHandler<ActionEvent>() {
           @Override
           public void handle(ActionEvent e) {
               System.out.println("Process Open");
           }
       });
       
       btSave.setOnAction(new EventHandler<ActionEvent>() {
           @Override
           public void handle(ActionEvent e) {
               System.out.println("Process Save");
           }
       });
       
       btPrint.setOnAction(new EventHandler<ActionEvent>() {
           @Override
           public void handle(ActionEvent e) {
               System.out.println("Process Print");
           }
       });
       
       Scene scene = new Scene(hBox,300,50);
       primaryStage.setTitle("AnonymousHandlerDemo");
       primaryStage.setScene(scene);
       primaryStage.show();
   }

   public static void main(String[] args) {
       Application.launch(args);

   }

}

3.lambda表達(dá)式

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class LambdaHandlerDemo extends Application {

   @Override
   public void start(Stage primaryStage) throws Exception {
       HBox hBox = new HBox();
       hBox.setSpacing(10);
       hBox.setAlignment(Pos.CENTER);
       Button btNew = new Button("New");
       Button btOpen = new Button("Open");
       Button btSave= new Button("Save");
       Button btPrint = new Button("Print");
       hBox.getChildren().addAll(btNew,btOpen,btSave,btPrint);
       
       btNew.setOnAction((ActionEvent e)->{System.out.println("Process New");});
       
       btOpen.setOnAction((e)->{System.out.println("Process Open");});
       
       btSave.setOnAction(e->{System.out.println("Process Save");});
       
       btPrint.setOnAction(e->System.out.println("Process Print"));
       
       Scene scene = new Scene(hBox,300,50);
       primaryStage.setTitle("LambdaHandlerDemo");
       primaryStage.setScene(scene);
       primaryStage.show();
   }

   public static void main(String[] args) {
       Application.launch(args);

   }

}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昭灵,一起剝皮案震驚了整個(gè)濱河市吠裆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烂完,老刑警劉巖试疙,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抠蚣,居然都是意外死亡祝旷,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門嘶窄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀跛,“玉大人,你說我怎么就攤上這事柄冲∥悄保” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵现横,是天一觀的道長漓拾。 經(jīng)常有香客問我阁最,道長,這世上最難降的妖魔是什么骇两? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任闽撤,我火速辦了婚禮,結(jié)果婚禮上脯颜,老公的妹妹穿的比我還像新娘被碗。我一直安慰自己驳阎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布渗柿。 她就那樣靜靜地躺著饱亮,像睡著了一般矾芙。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上近上,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天剔宪,我揣著相機(jī)與錄音,去河邊找鬼壹无。 笑死葱绒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的斗锭。 我是一名探鬼主播地淀,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼岖是!你這毒婦竟也來了帮毁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤豺撑,失蹤者是張志新(化名)和其女友劉穎烈疚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聪轿,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爷肝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屹电。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阶剑。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖危号,靈堂內(nèi)的尸體忽然破棺而出牧愁,到底是詐尸還是另有隱情,我是刑警寧澤外莲,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布猪半,位于F島的核電站兔朦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏磨确。R本人自食惡果不足惜沽甥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乏奥。 院中可真熱鬧摆舟,春花似錦、人聲如沸邓了。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骗炉。三九已至照宝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間句葵,已是汗流浹背厕鹃。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乍丈,地道東北人剂碴。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像诗赌,于是被迫代替她去往敵國和親汗茄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容