抽象類
在繼承的層次結(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ī)則如下:
- 超類和接口沖突蚣驼。如果超類提供了一個(gè)具體方法偿警,那么根據(jù)超類優(yōu)先原則,同名而且有相同參數(shù)類型的默認(rèn)方法會(huì)被忽略苛秕。
- 多接口之間沖突。如果一個(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)部類具有一下特征:
- 一個(gè)內(nèi)部類被編譯成一個(gè)名為
OuterClassName$InnerClassName
的類柑蛇。例如,一個(gè)定義在Test類中的成員內(nèi)部類A被編譯成Test$A.class
驱闷。 - 內(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.class
和Test$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ù)式接口:
下面來看一個(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);
}
}