以下是《瘋狂Java講義》中的一些知識衅胀,如有錯誤液荸,煩請指正拇砰。
Java8增強(qiáng)的包裝類
自動裝箱就是把一個基本類型的變量直接賦給對應(yīng)的包裝類變量食铐,自動拆箱則與之相反匕垫。
把字符串類型轉(zhuǎn)換成基本類型:
- 除了Character之外的所有包裝類都提供了一個parseXxx(String s)靜態(tài)方法。
- 利用包裝類提供的Xxx(String s)構(gòu)造器
String提供了多個重載的valueOf()方法虐呻,用于將基本類型轉(zhuǎn)換成字符串象泵∧海或者將基本類型與""進(jìn)行連接運(yùn)算,String intstr = 5+"";
public class Primitive2String
{
public static void main(String[] args)
{
String intStr = "123";
// 把一個特定字符串轉(zhuǎn)換成int變量
int it1 = Integer.parseInt(intStr);
int it2 = new Integer(intStr);
System.out.println(it2);
String floatStr = "4.56";
// 把一個特定字符串轉(zhuǎn)換成float變量
float ft1 = Float.parseFloat(floatStr);
float ft2 = new Float(floatStr);
System.out.println(ft2);
// 把一個float變量轉(zhuǎn)換成String變量
String ftStr = String.valueOf(2.345f);
System.out.println(ftStr);
// 把一個double變量轉(zhuǎn)換成String變量
String dbStr = String.valueOf(3.344);
System.out.println(dbStr);
// 把一個boolean變量轉(zhuǎn)換成String變量
String boolStr = String.valueOf(true);
System.out.println(boolStr.toUpperCase());
}
}
注意:將-128-127之間的同一個整數(shù)自動裝箱成Integer實例時偶惠,永遠(yuǎn)都是引用cache數(shù)組的同一個元素春寿,所以他們?nèi)肯嗟龋徊辉谶@個范圍的整數(shù)自動裝箱城Integer忽孽,只有兩個包裝類引用指向同一個對象時才相等绑改。
Java 7為所有包裝類增加一個新方法:compare(x,y)。該方法用于比較兩個包裝類實例兄一,當(dāng)x>y厘线,返回大于0的數(shù);當(dāng)x==y出革,返回0造壮;否則返回小于0的數(shù)骂束。還有其他的一些方法不做介紹了。
處理對象
打印對象和toString方法:toString方法是系統(tǒng)將會輸出該對象的“自我描述”信息楞抡,用以告訴外界對象具有的狀態(tài)信息析藕。
Object 類提供的toString方法總是返回該對象實現(xiàn)類的類名+@+hashCode值。這個并不能真正實現(xiàn)自我描述的功能竞慢,因此用戶必須在自定義類中重寫Object類的toString方法治泥。通常可返回類名[filed1=值1,field2=值2,...]
==和equals比較運(yùn)算符
==要求兩個引用變量指向同一個對象才會返回true居夹,不可用于比較類型上沒有父子關(guān)系的兩個對象败潦;對于基本類型變量,只要變量值相等返回true准脂。equals方法則允許用戶提供自定義的相等規(guī)則劫扒。Object類提供的equals方法判斷兩個對象相等的標(biāo)準(zhǔn)與==完全相同。因此開發(fā)者通常需要重寫equals方法狸膏。
字符串直接量與字符串對象
Java程序直接使用"hello"的字符串直接量時沟饥,JVM將會使用常量池管理這些字符串;當(dāng)使用new String("hello"),JVM會先使用常量池管理"hello"的字符串直接量贤旷,在調(diào)用String類的構(gòu)造器創(chuàng)建新的String對象广料,新創(chuàng)建得到String對象被保存在堆內(nèi)存中。
public class StringCompareTest
{
public static void main(String[] args)
{
// s1直接引用常量池中的"瘋狂Java"
String s1 = "瘋狂Java";
String s2 = "瘋狂";
String s3 = "Java";
// s4后面的字符串值可以在編譯時就確定下來
// s4直接引用常量池中的"瘋狂Java"
String s4 = "瘋狂" + "Java";
// s5后面的字符串值可以在編譯時就確定下來
// s5直接引用常量池中的"瘋狂Java"
String s5 = "瘋" + "狂" + "Java";
// s6后面的字符串值不能在編譯時就確定下來幼驶,
// 不能引用常量池中的字符串
String s6 = s2 + s3;
// 使用new調(diào)用構(gòu)造器將會創(chuàng)建一個新的String對象艾杏,
// s7引用堆內(nèi)存中新創(chuàng)建的String對象
String s7 = new String("瘋狂Java");
System.out.println(s1 == s4); // 輸出true
System.out.println(s1 == s5); // 輸出true
System.out.println(s1 == s6); // 輸出false
System.out.println(s1 == s7); // 輸出false
}
}
equals方法重寫
class Person
{
private String name;
private String idStr;
public Person(){}
public Person(String name , String idStr)
{
this.name = name;
this.idStr = idStr;
}
// 此處省略name和idStr的setter和getter方法。
// name的setter和getter方法
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
// idStr的setter和getter方法
public void setIdStr(String idStr)
{
this.idStr = idStr;
}
public String getIdStr()
{
return this.idStr;
}
// 重寫equals()方法盅藻,提供自定義的相等標(biāo)準(zhǔn)
public boolean equals(Object obj)
{
// 如果兩個對象為同一個對象
if (this == obj)
return true;
// 只有當(dāng)obj是Person對象
if (obj != null && obj.getClass() == Person.class)
{
Person personObj = (Person)obj;
// 并且當(dāng)前對象的idStr與obj對象的idStr相等才可判斷兩個對象相等
if (this.getIdStr().equals(personObj.getIdStr()))
{
return true;
}
}
return false;
}
}
public class OverrideEqualsRight
{
public static void main(String[] args)
{
Person p1 = new Person("孫悟空" , "12343433433");
Person p2 = new Person("孫行者" , "12343433433");
Person p3 = new Person("孫悟飯" , "99933433");
// p1和p2的idStr相等糜颠,所以輸出true
System.out.println("p1和p2是否相等?"
+ p1.equals(p2));
// p2和p3的idStr不相等萧求,所以輸出false
System.out.println("p2和p3是否相等其兴?"
+ p2.equals(p3));
}
}
注意instanceof當(dāng)前面對象是后面類的實例或者子類的實例時都將返回true,在重寫equals方法里不適用夸政。
類成員
即使通過null對象來訪問類成員元旬,程序也不會引發(fā)NullPointerException。
靜態(tài)初始化塊也是類成員的一種匀归。
單例類(Singleton)
如果一個類始終只能創(chuàng)建一個對象,稱為單例類体啰。
條件:
- 我們把該類的構(gòu)造器使用Private修飾荒勇,從而把該 類的所有構(gòu)造器隱藏起來沽翔。
- 則需要提供一個public方法作為該類的訪問點,用于創(chuàng)建該類的對象橘沥,且必須使用static修飾
- 該類還必須緩存已經(jīng)創(chuàng)建的對象威恼,必須用static修飾
class Singleton
{
// 使用一個類變量來緩存曾經(jīng)創(chuàng)建的實例
private static Singleton instance;
// 將構(gòu)造器使用private修飾,隱藏該構(gòu)造器
private Singleton(){}
// 提供一個靜態(tài)方法斤蔓,用于返回Singleton實例
// 該方法可以加入自定義的控制弦牡,保證只產(chǎn)生一個Singleton對象
public static Singleton getInstance()
{
// 如果instance為null驾锰,表明還不曾創(chuàng)建Singleton對象
// 如果instance不為null,則表明已經(jīng)創(chuàng)建了Singleton對象赏酥,
// 將不會重新創(chuàng)建新的實例
if (instance == null)
{
// 創(chuàng)建一個Singleton對象裸扶,并將其緩存起來
instance = new Singleton();
}
return instance;
}
}
public class SingletonTest
{
public static void main(String[] args)
{
// 創(chuàng)建Singleton對象不能通過構(gòu)造器,
// 只能通過getInstance方法來得到實例
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // 將輸出true
}
}
final修飾符
用于表示修飾的類何荚、方法、變量不可變餐塘。final修飾的成員變量必須由程序員顯式地指定初始值戒傻。系統(tǒng)不會對final成員進(jìn)行隱式初始化需纳。
類變量:必須在靜態(tài)初始化塊或聲明該變量時指定初始值
實例變量:必須在非靜態(tài)初始化塊或聲明該變量時或構(gòu)造器中指定初始值。
使用final修飾局部變量時既可以在定義時指定默認(rèn)值口蝠,也可以不指定默認(rèn)值妙蔗。
final修飾基本類型和引用變量的區(qū)別
當(dāng)使用final修飾基本數(shù)據(jù)類型變時昙啄,不能對其重新賦值梳凛,不能被改變伶跷。但對引用類型的變量而言叭莫,它僅僅保存的是一個引用,final只能保證他的地址不變靖诗,但不能保證對象刊橘,所以引用類型完全可以改變他的對象促绵。
class Person
{
private int age;
public Person(){}
// 有參數(shù)的構(gòu)造器
public Person(int age)
{
this.age = age;
}
// 省略age的setter和getter方法
// age的setter和getter方法
public void setAge(int age)
{
this.age = age;
}
public int getAge()
{
return this.age;
}
}
public class FinalReferenceTest
{
public static void main(String[] args)
{
// final修飾數(shù)組變量,iArr是一個引用變量
final int[] iArr = {5, 6, 12, 9};
System.out.println(Arrays.toString(iArr));
// 對數(shù)組元素進(jìn)行排序尖坤,合法
Arrays.sort(iArr);
System.out.println(Arrays.toString(iArr));
// 對數(shù)組元素賦值场梆,合法
iArr[2] = -8;
System.out.println(Arrays.toString(iArr));
// 下面語句對iArr重新賦值或油,非法
// iArr = null;
// final修飾Person變量罐脊,p是一個引用變量
final Person p = new Person(45);
// 改變Person對象的age實例變量萍桌,合法
p.setAge(23);
System.out.println(p.getAge());
// 下面語句對p重新賦值上炎,非法
// p = null;
}
}
可執(zhí)行“宏替換”的final變量
對一個final變量來說寇损,不管它是類變量矛市、實例變量浊吏,還是局部變量,只要該變量滿足3個條件墩衙,這個final變量就不再是一個變量底桂,而是相當(dāng)于一個直接量。
- 使用final修飾符修飾氛魁;
- 在定義該final變量時指定了初始值捶码;
- 該初始值可以在編譯時就被確定下來档押。
public class StringJoinTest
{
public static void main(String[] args)
{
String s1 = "瘋狂Java";
// s2變量引用的字符串可以編譯時就確定出來,
// 因此s2直接引用常量池中已有的"瘋狂Java"字符串
String s2 = "瘋狂" + "Java";
System.out.println(s1 == s2);
// 定義2個字符串直接量
String str1 = "瘋狂"; //①
String str2 = "Java"; //②
// 將str1和str2進(jìn)行連接運(yùn)算
String s3 = str1 + str2;
System.out.println(s1 == s3);//false
}
}
str1和str2只是普通變量粒没,編譯器不會執(zhí)行宏替換癞松;只要定義時添加final修飾,結(jié)果為true枫甲。注意對于final實例變量,只有定義該變量時指定初始值才會有宏變量的效果迎捺。
final方法
final 修飾的方法不可以被重寫抄沮。
final 修飾的方法僅僅是不能重寫,但它完全可以被重載蹋订。
public class PrivateFinalMethodTest
{
private final void test(){}
}
class Sub extends PrivateFinalMethodTest
{
// 下面方法定義將不會出現(xiàn)問題
public void test(){}
}
final類
final 修飾的類不可以被繼承
抽象類
抽象方法和類都必須使用abstract來修飾椒功,含有抽象方法的類一定為抽象類丁屎,抽象類里也可以沒有抽象方法。
抽象類不能被實例化,可以通過其子類給他賦值;普通類里有的抽象里也有策肝。
定義抽象方法只需在普通方法上增加abstract修飾符,并把普通方法的方法體(也就是方法后花括號括起來的部分)全部去掉共虑,并在方法后增加分號即可。
注意:static和abstract不能同時修飾某個方法吼鳞,即沒有所謂的類抽象方法看蚜,即使有調(diào)用一個沒有方法體的方法也會引起錯誤赔桌。
抽象類的作用
抽象類代表了一種未完成的類設(shè)計,它體現(xiàn)的是一種模板。
接口
接口定義的是多個類共同的行為規(guī)范港粱,這些行為是與外部交流的通道氮凝,這就意味著接口里通常是定義一組公用的方法袍辞。
接口里不能包含普通方法,所有方法都是抽象方法,Java8允許定義默認(rèn)方法蛉威。
接口定義
[修飾符] interface 接口名 extends 父接口1,父接口2
{
零個到多個常量定義...
零個到多個抽象方法定義...
零個到多個內(nèi)部類、接口、枚舉定義...
零個到多個默認(rèn)方法或類方法定義...
}
修飾符可以是public或者省略拴签。
常量都是:public static final修飾
實例方法都是:public abstract 修飾
類方法用public static修飾
默認(rèn)方法用public default修飾
內(nèi)部的類:public static
接口里面沒有構(gòu)造器和初始化塊稠氮。
類方法可以使用接口直接調(diào)用,默認(rèn)方法要通過使用接口的實例來調(diào)用尝江。
接口繼承
接口的繼承和類繼承不一樣易遣,接口完全支持多繼承,子接口擴(kuò)展某個父接口將會獲得父接口的所有抽像方法,類變量没炒。
使用接口
一個類實現(xiàn)了一個或多個接口之后弃衍,這個類必須完全實現(xiàn)這些接口里所定義的全部抽象方法(也就是重寫這些抽象方法)激涤;
否則功蜓,該類將保留從父接口那里繼承到的抽象方法,該類也必須定義成抽象類
接口和抽象類的相似性
- 接口和抽象類都不能被實例化,它們都位于繼承樹的頂端劈榨,用于被其他類實現(xiàn)和繼承访递。
- 接口和抽象類都可以包含抽象方法踪古,實現(xiàn)接口或繼承抽象類的普通子類都必須實現(xiàn)這些抽象方法辱魁。
接口與抽象類的區(qū)別
- 接口里只能包含抽象方法,不同包含已經(jīng)提供實現(xiàn)的方法;抽象類則完全可以包含普通方法。
- 接口里不能定義靜態(tài)方法瘸味;抽象類里可以定義靜態(tài)方法宫仗。
- 接口里只能定義靜態(tài)常量屬性,不能定義普通屬性旁仿;抽象類里則既可以定義普通屬性锰什,也可以定義靜態(tài)常量屬性。
- 接口不包含構(gòu)造器丁逝;抽象類里可以包含構(gòu)造器汁胆,抽象類里的構(gòu)造器并不是用于創(chuàng)建對象,而讓其子類調(diào)用這些構(gòu)造器來完成屬于抽象類的初始化操作霜幼。
- 接口里不能包含初始化塊嫩码,但抽象類則完全可以包含初始化塊。
- 一個類最多只能有一個直接父類罪既,包括抽象類铸题;但一個類可以直接實現(xiàn)多個接口,通過實現(xiàn)多個接口可以彌補(bǔ)Java單繼承的不足琢感。
內(nèi)部類
我們把一個類放在另一個類的內(nèi)部定義丢间,這個定義在其他類內(nèi)部的類就被稱為內(nèi)部類,有的也叫嵌套類驹针,包含內(nèi)部類的類也被稱為外部類有的也叫宿主類烘挫。內(nèi)部類提供了更好的封裝,內(nèi)部類成員可以直接訪問外部類的私有數(shù)據(jù)柬甥,因為內(nèi)部類被當(dāng)成其他外部類成員饮六。匿名內(nèi)部類適合用于創(chuàng)建那些僅需要一次使用的類。
區(qū)別:
- 可以多使用修飾符private苛蒲、protected卤橄、static
- 非靜態(tài)內(nèi)部類不能擁有靜態(tài)成員
非靜態(tài)內(nèi)部類
非靜態(tài)內(nèi)部類中可以訪問外部類的private成員,是因為在非靜態(tài)內(nèi)部類中保存了外部類對象的引用臂外。
public class DiscernVariable
{
private String prop = "外部類的實例變量";
private class InClass
{
private String prop = "內(nèi)部類的實例變量";
public void info()
{
String prop = "局部變量";
// 通過 外部類類名.this.varName 訪問外部類實例變量
System.out.println("外部類的實例變量值:"
+ DiscernVariable.this.prop);
// 通過 this.varName 訪問內(nèi)部類實例的變量
System.out.println("內(nèi)部類的實例變量值:" + this.prop);
// 直接訪問局部變量
System.out.println("局部變量的值:" + prop);
}
}
public void test()
{
InClass in = new InClass();
in.info();
}
public static void main(String[] args)
{
new DiscernVariable().test();
}
}
非靜態(tài)內(nèi)部類的成員只在內(nèi)部類范圍是可知的窟扑,并不能直接被外部類調(diào)用喇颁,如果外部類需要訪問,則必須顯示創(chuàng)建非靜態(tài)內(nèi)部類對象來調(diào)用其實例成員嚎货。非靜態(tài)內(nèi)部類不可以定義靜態(tài)成員橘霎。
靜態(tài)內(nèi)部類
如果用static修飾一個內(nèi)部類,稱為靜態(tài)內(nèi)部類厂抖。
靜態(tài)內(nèi)部類可以包含靜態(tài)成員茎毁,也可以包含非靜態(tài)成員。所以靜態(tài)內(nèi)部類不能訪問外部類的實例成員忱辅,只能訪問外部類的類成員七蜘。
靜態(tài)內(nèi)部類的對象寄存在外部類里,非靜態(tài)內(nèi)部類的對象寄存在外部類實例里
外部類依然不能直接訪問靜態(tài)內(nèi)部類的成員墙懂,但可以使用靜態(tài)內(nèi)部類的類名訪問靜態(tài)內(nèi)部類的類成員或者靜態(tài)內(nèi)部類對象作為調(diào)用者訪問靜態(tài)內(nèi)部類的實例成員橡卤。
使用內(nèi)部類
- 在外部類內(nèi)部使用內(nèi)部類-不要在外部類的靜態(tài)成員中使用非靜態(tài)內(nèi)部類,因為靜態(tài)成員不能訪問非靜態(tài)成員损搬。
- 在外部類以外使用非靜態(tài)內(nèi)部類碧库。
- private 修飾的內(nèi)部類只能在外部類內(nèi)部使用。
- 在外部類以外的地方使用內(nèi)部類巧勤,內(nèi)部類完整的類名應(yīng)該OuterClass.InnerClass.
- 在外部類以外的地方使用非靜態(tài)內(nèi)部類創(chuàng)建對象的語法如下:OuterInstance.new InnerConstructor()嵌灰,創(chuàng)建非靜態(tài)內(nèi)部類實例以來于外部類實例。
class Out
{
// 定義一個內(nèi)部類颅悉,不使用訪問控制符沽瞭,
// 即只有同一個包中其他類可訪問該內(nèi)部類
class In
{
public In(String msg)
{
System.out.println(msg);
}
}
}
public class CreateInnerInstance
{
public static void main(String[] args)
{
Out.In in = new Out().new In("測試信息");
/*
上面代碼可改為如下三行代碼:
使用OutterClass.InnerClass的形式定義內(nèi)部類變量
Out.In in;
創(chuàng)建外部類實例,非靜態(tài)內(nèi)部類實例將寄存在該實例中
Out out = new Out();
通過外部類實例和new來調(diào)用內(nèi)部類構(gòu)造器創(chuàng)建非靜態(tài)內(nèi)部類實例
in = out.new In("測試信息");
*/
}
}
創(chuàng)建靜態(tài)內(nèi)部類的子類時剩瓶,必須存在一個外部類對象驹溃,然后才能調(diào)用非靜態(tài)內(nèi)部類的構(gòu)造器。
public class SubClass extends Out.In
{
//顯示定義SubClass的構(gòu)造器
public SubClass(Out out)
{
//通過傳入的Out對象顯式調(diào)用In的構(gòu)造器
out.super("hello");
}
}
注意:非靜態(tài)內(nèi)部類的子類不一定是內(nèi)部類延曙,可以是外部類豌鹤。但其實例必須保留一個引用,指向父類所在外部類的對象枝缔。
- 在外部類以外使用靜態(tài)內(nèi)部類
在外部類以外的地方使用靜態(tài)內(nèi)部類創(chuàng)建對象的語法如下:new OuterClass.InnerConstructer();
使用靜態(tài)內(nèi)部類相對容易布疙,只要把外部類當(dāng)成靜態(tài)內(nèi)部類的包空間即可。所以魂仍,一般優(yōu)先考慮靜態(tài)內(nèi)部類拐辽。
注意:子類的內(nèi)部類不可能重寫父類的內(nèi)部類,因為即使內(nèi)部類類名相同擦酌,外部類空間不同,就不可能完全同名菠劝。
局部內(nèi)部類
如果把一個內(nèi)部類放在方法里定義赊舶,這就是局部內(nèi)部類,僅僅在這個方法里有效。局部內(nèi)部類不能在外部類以外的地方使用笼平,那么局部內(nèi)部類也不能使用訪部控制符和static修飾园骆。
局部內(nèi)部類使用很有限。
匿名內(nèi)部類
匿名內(nèi)部類適合創(chuàng)建那種只需要一次使用的類寓调,定義匿名內(nèi)部類的語法格式如下:
new 父類構(gòu)造器(實例列表) |實現(xiàn)接口)
{
//匿名內(nèi)部類的 類體部分
}
必須繼承一個父類或者實現(xiàn)一個接口锌唾,但最多繼承一個父類或者實現(xiàn)一個接口。
兩個限制:匿名內(nèi)部類不能是抽象類夺英;匿名內(nèi)部類不能定義構(gòu)造器晌涕。
interface Product
{
public double getPrice();
public String getName();
}
public class AnonymousTest
{
public void test(Product p)
{
System.out.println("購買了一個" + p.getName()
+ ",花掉了" + p.getPrice());
}
public static void main(String[] args)
{
AnonymousTest ta = new AnonymousTest();
// 調(diào)用test()方法時痛悯,需要傳入一個Product參數(shù)余黎,
// 此處傳入其匿名實現(xiàn)類的實例
ta.test(new Product()
{
public double getPrice()
{
return 567.8;
}
public String getName()
{
return "AGP顯卡";
}
});
}
}
如果局部變量被匿名內(nèi)部類訪問,該變量相當(dāng)于自動使用final修飾载萌。也就是effective final:對于內(nèi)部類訪問的局部變量可以用final修飾惧财,也可以不用,但是一次賦值后不能再次賦值扭仁。
interface A
{
void test();
}
public class ATest
{
public static void main(String[] args)
{
int age = 8; // ①
// 下面代碼將會導(dǎo)致編譯錯誤
// 由于age局部變量被匿名內(nèi)部類訪問了垮衷,因此age相當(dāng)于被final修飾了
age = 2;
A a = new A()
{
public void test()
{
// 在Java 8以前下面語句將提示錯誤:age必須使用final修飾
// 從Java 8開始,匿名內(nèi)部類乖坠、局部內(nèi)部類允許訪問非final的局部變量
System.out.println(age);
}
};
a.test();
}
}
Lambda表達(dá)式
Lambda表達(dá)式主要作用就是代替匿名內(nèi)部類的繁瑣語法搀突。 它由三部分組成:
- 形參列表。形參列表允許省略形參類型瓤帚。如果形參列表中只有一個參數(shù)描姚,甚至連形參列表的圓括號也可以省略。
- 箭頭(->)戈次。
- 代碼塊轩勘。如果代碼塊只有包含一條語句,Lambda表達(dá)式允許省略代碼塊的花括號怯邪,如果省略了代碼塊的花括號绊寻,這條語句不要用花括號表示語句結(jié)束。Lambda代碼塊只有一條return語句悬秉,甚至可以省略return關(guān)鍵字澄步。Lambda表達(dá)式需要返回值,而它的代碼塊中僅有一條省略了return的語句和泌,Lambda表達(dá)式會自動返回這條語句的值
interface Eatable
{
void taste();
}
interface Flyable
{
void fly(String weather);
}
interface Addable
{
int add(int a , int b);
}
public class LambdaQs
{
// 調(diào)用該方法需要Eatable對象
public void eat(Eatable e)
{
System.out.println(e);
e.taste();
}
// 調(diào)用該方法需要Flyable對象
public void drive(Flyable f)
{
System.out.println("我正在駕駛:" + f);
f.fly("【碧空如洗的晴日】");
}
// 調(diào)用該方法需要Addable對象
public void test(Addable add)
{
System.out.println("5與3的和為:" + add.add(5, 3));
}
public static void main(String[] args)
{
LambdaQs lq = new LambdaQs();
// Lambda表達(dá)式的代碼塊只有一條語句村缸,可以省略花括號。
lq.eat(()-> System.out.println("蘋果的味道不錯武氓!"));
// Lambda表達(dá)式的形參列表只有一個形參梯皿,省略圓括號
lq.drive(weather ->
{
System.out.println("今天天氣是:" + weather);
System.out.println("直升機(jī)飛行平穩(wěn)");
});
// Lambda表達(dá)式的代碼塊只有一條語句仇箱,省略花括號
// 代碼塊中只有一條語句,即使該表達(dá)式需要返回值东羹,也可以省略return關(guān)鍵字剂桥。
lq.test((a , b)->a + b);
}
}
Lambda表達(dá)式與函數(shù)式接口
如果采用匿名內(nèi)部類語法來創(chuàng)建函數(shù)式接口的實例,只要實現(xiàn)一個抽象方法即可属提,在這種情況下即可采用Lambda表達(dá)式來創(chuàng)建對象权逗,該表達(dá)式創(chuàng)建出來的對象的目標(biāo)類型就是這個函數(shù)式接口。
Lambda表達(dá)式有如下兩個限制:
- Lambda表達(dá)式的目標(biāo)類型必須是明確的函數(shù)式接口冤议。
- Lambda表達(dá)式只能為函數(shù)式接口創(chuàng)建對象斟薇。Lambda表達(dá)式只能實現(xiàn)一個方法,因此它只能為只有一個抽象方法的接口(函數(shù)式接口)創(chuàng)建對象求类。
為了保證Lambda表達(dá)式的目標(biāo)類型是一個明確的函數(shù)式接口奔垦,可以有如下三種常見方式:
- 將Lambda表達(dá)式賦值給函數(shù)式接口類型的變量。
- 將Lambda表達(dá)式作為函數(shù)式接口類型的參數(shù)傳給某個方法尸疆。
- 使用函數(shù)式接口對Lambda表達(dá)式進(jìn)行強(qiáng)制類型轉(zhuǎn)換椿猎。
方法引用與構(gòu)造器引用
如果Lambda表達(dá)式的代碼塊只有一條代碼,可以省略表達(dá)式中代碼塊的花括號寿弱,還可以在代碼塊中使用方法引用和構(gòu)造器引用犯眠。
種類 | 示例 | 說明 | Lambda表達(dá)式 |
---|---|---|---|
引用類方法 | 類名::類方法 | 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該類方法作為參數(shù)。 | (a,b,...) -> 類名.類方法(a,b, ...) |
引用特定對象的實例方法 | 特定對象::實例方法 | 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該實例方法作為參數(shù)症革。 | (a,b, ...) -> 特定對象.實例方法(a,b, ...) |
引用某類對象的實例方法 | 類名::實例方法 | 函數(shù)式接口中被實現(xiàn)方法的第一個參數(shù)作為調(diào)用者筐咧,后面的參數(shù)全部傳給該方法作為參數(shù)。 | (a,b, ...) ->a.實例方法(b, ...) |
引用構(gòu)造器 | 類名::new | 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該構(gòu)造器作為參數(shù) | (a,b, ...) ->new 類的構(gòu)造器(a,b, ...) |
import javax.swing.*;
@FunctionalInterface
interface Converter{
Integer convert(String from);
}
@FunctionalInterface
interface MyTest
{
String test(String a , int b , int c);
}
@FunctionalInterface
interface YourTest
{
JFrame win(String title);
}
public class MethodRefer
{
public static void main(String[] args)
{
// 下面代碼使用Lambda表達(dá)式創(chuàng)建Converter對象
// Converter converter1 = from -> Integer.valueOf(from);
// // 方法引用代替Lambda表達(dá)式:引用類方法噪矛。
// // 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該類方法作為參數(shù)量蕊。
// Converter converter1 = Integer::valueOf;
// Integer val = converter1.convert("99");
// System.out.println(val); // 輸出整數(shù)99
// 下面代碼使用Lambda表達(dá)式創(chuàng)建Converter對象
// Converter converter2 = from -> "fkit.org".indexOf(from);
// // 方法引用代替Lambda表達(dá)式:引用特定對象的實例方法。
// // 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該方法作為參數(shù)艇挨。
// Converter converter2 = "fkit.org"::indexOf;
// Integer value = converter2.convert("it");
// System.out.println(value); // 輸出2
// 下面代碼使用Lambda表達(dá)式創(chuàng)建MyTest對象
// MyTest mt = (a , b , c) -> a.substring(b , c);
// 方法引用代替Lambda表達(dá)式:引用某類對象的實例方法残炮。
// 函數(shù)式接口中被實現(xiàn)方法的第一個參數(shù)作為調(diào)用者,
// 后面的參數(shù)全部傳給該方法作為參數(shù)缩滨。
// MyTest mt = String::substring;
// String str = mt.test("Java I Love you" , 2 , 9);
// System.out.println(str); // 輸出:va I Lo
// 下面代碼使用Lambda表達(dá)式創(chuàng)建YourTest對象
// YourTest yt = (String a) -> new JFrame(a);
// 構(gòu)造器引用代替Lambda表達(dá)式势就。
// 函數(shù)式接口中被實現(xiàn)方法的全部參數(shù)傳給該構(gòu)造器作為參數(shù)。
YourTest yt = JFrame::new;
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
}
}
Lambda表達(dá)式與匿名內(nèi)部類
相同點:
- Lambda表達(dá)式與匿名內(nèi)部類一樣脉漏,都可以直接訪問“effectively final”的局部變量苞冯,以及外部類的成員變量(包括實例變量和類變量)。
- Lambda表達(dá)式創(chuàng)建的對象與匿名內(nèi)部類生成的對象一樣侧巨,都可以直接調(diào)用從接口繼承得到的默認(rèn)方法舅锄。
區(qū)別:
- 匿名內(nèi)部類可以為任意接口創(chuàng)建實例——不管接口包含多少個抽象方法,只要匿名內(nèi)部類實現(xiàn)所有的抽象方法即可司忱。但Lambda表達(dá)式只能為函數(shù)式接口創(chuàng)建實例巧娱。
- 匿名內(nèi)部類可以為抽象類碉怔、甚至普通類創(chuàng)建實例烘贴,但Lambda表達(dá)式只能為函數(shù)式接口創(chuàng)建實例禁添。
- 匿名內(nèi)部類實現(xiàn)的抽象方法的方法體允許調(diào)用接口中定義的默認(rèn)方法;但Lambda表達(dá)式的代碼塊不允許調(diào)用接口中定義的默認(rèn)方法桨踪。
使用Lambda表達(dá)式調(diào)用Arrays的類方法
Arrays類的有些方法需要Comparator老翘、XxxOperator、XxxFuncton等接口實例锻离,這些接口都是函數(shù)式接口铺峭,因此可以使用Lambda表達(dá)式調(diào)用Arrays方法。
import java.util.Arrays;
public class LambdaArrays
{
public static void main(String[] args)
{
String[] arr1 = new String[]{"java" , "fkava" , "fkit", "ios" , "android"};
Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
System.out.println(Arrays.toString(arr1));
int[] arr2 = new int[]{3, -4 , 25, 16, 30, 18};
// left代表數(shù)組中前一個所索引處的元素汽纠,計算第一個元素時卫键,left為1
// right代表數(shù)組中當(dāng)前索引處的元素
Arrays.parallelPrefix(arr2, (left, right)-> left * right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
// operand代表正在計算的元素索引
Arrays.parallelSetAll(arr3 , operand -> operand * 5);
System.out.println(Arrays.toString(arr3));
}
}
枚舉類
實例有限而且固定的類被稱為枚舉類
枚舉類是一種特殊的類,它一樣可以有自己的方法和屬性虱朵,可以實現(xiàn)一個或者多個接口莉炉,也可以定義自己的構(gòu)造器。一個Java源文件中最多只能定義一個public訪問權(quán)限的枚舉類碴犬,且該Java源文件也必須和該枚舉類的類名相同絮宁。
與普通類的區(qū)別
- 枚舉類可以實現(xiàn)一個或多個接口,使用enum定義的枚舉類默認(rèn)繼承了java.lang.Enum類服协,而不是繼承Object類绍昂。其中java.lang.Enum類實現(xiàn)了java.lang.Serializable和java.lang. Comparable兩個接口。
- 枚舉類的構(gòu)造器只能使用private訪問控制符偿荷,如果省略了其構(gòu)造器的訪問控制符窘游,則默認(rèn)使用private修飾;如果強(qiáng)制指定訪問控制符跳纳,則只能指定private修飾符忍饰。
- 枚舉類的所有實例必須在枚舉類中顯式列出,否則這個枚舉類將永遠(yuǎn)都不能產(chǎn)生實例棒旗。列出這些實例時系統(tǒng)會自動添加public static final修飾喘批,無需程序員顯式添加。
- 所有枚舉類都提供了一個values方法铣揉,該方法可以很方便地遍歷所有的枚舉值饶深。
public enum SeasonEnum
{
// 在第一行列出4個枚舉實例
SPRING,SUMMER,FALL,WINTER;
}
public class EnumTest
{
public void judge(SeasonEnum s)
{
// switch語句里的表達(dá)式可以是枚舉值
switch (s)
{
case SPRING:
System.out.println("春暖花開,正好踏青");
break;
case SUMMER:
System.out.println("夏日炎炎逛拱,適合游泳");
break;
case FALL:
System.out.println("秋高氣爽敌厘,進(jìn)補(bǔ)及時");
break;
case WINTER:
System.out.println("冬日雪飄,圍爐賞雪");
break;
}
}
public static void main(String[] args)
{
// 枚舉類默認(rèn)有一個values方法朽合,返回該枚舉類的所有實例
for (SeasonEnum s : SeasonEnum.values())
{
System.out.println(s);
}
// 使用枚舉實例時俱两,可通過EnumClass.variable形式來訪問
new EnumTest().judge(SeasonEnum.SPRING);
}
}
枚舉類的屬性饱狂、方法和構(gòu)造器
枚舉類也是一種類,只是它是一種比較特殊的類宪彩,因此它一樣可以使用屬性和方法休讳。
枚舉類通常應(yīng)該設(shè)計成不可變類,也就說它的屬性值不應(yīng)該允許改變尿孔,這樣會更安全俊柔,而且代碼更加簡潔。為此活合,我們應(yīng)該將枚舉類的屬性都使用private final修飾雏婶。
一旦為枚舉類顯式定義了帶參數(shù)的構(gòu)造器,則列出枚舉值時也必須對應(yīng)地傳入?yún)?shù)白指。
public enum Gender
{
// 此處的枚舉值必須調(diào)用對應(yīng)構(gòu)造器來創(chuàng)建
MALE("男"),FEMALE("女");
private final String name;
// 枚舉類的構(gòu)造器只能使用private修飾
private Gender(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
}
實現(xiàn)接口的枚舉類
枚舉類也可以實現(xiàn)一個或多個接口留晚。與普通類實現(xiàn)一個或多個接口完全一樣,枚舉類實現(xiàn)一個或多個接口時告嘲,也需要實現(xiàn)該接口所包含的方法错维。
如果需要每個枚舉值在調(diào)用同一個方法時呈現(xiàn)出不同的行為方式,則可以讓每個枚舉值分別來實現(xiàn)該方法状蜗,每個枚舉值提供不同的實現(xiàn)方式需五,從而讓不同枚舉值調(diào)用同一個方法時具有不同的行為方式。
public enum Gender implements GenderDesc{
//調(diào)用構(gòu)造器創(chuàng)建枚舉值
MALE("男")
//下面是類體
{
public void info(){
System.out.println("枚舉值代表男性");
}
},
FEMALE("女"){
public void info(){
System.out.println("枚舉值代表男性");
}
};
private final String name;
private Gender(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
}
創(chuàng)建MALE轧坎、FEMALE枚舉值時不是直接創(chuàng)建Gender實例宏邮,而是創(chuàng)建Gender匿名子類的實例,缸血。注意并不是所有的枚舉類都是用了final修飾蜜氨,非抽象的枚舉類才默認(rèn)使用final修飾。
包含抽象方法的枚舉類
可以在枚舉類里定義一個抽象方法捎泻,然后把這個抽象方法交給各枚舉值去實現(xiàn)即可飒炎。
枚舉類里定義抽象方法時無需顯式使用abstract關(guān)鍵字將枚舉類定義成抽象類,但因為枚舉類需要顯式創(chuàng)建枚舉值笆豁,而不是作為父類郎汪,所以定義每個枚舉值時必須為抽象方法提供實現(xiàn),否則將出現(xiàn)編譯錯誤闯狱。
public enum Operation
{
PLUS
{
public double eval(double x , double y)
{
return x + y;
}
},
MINUS
{
public double eval(double x , double y)
{
return x - y;
}
},
TIMES
{
public double eval(double x , double y)
{
return x * y;
}
},
DIVIDE
{
public double eval(double x , double y)
{
return x / y;
}
};
// 為枚舉類定義一個抽象方法
// 這個抽象方法由不同的枚舉值提供不同的實現(xiàn)
public abstract double eval(double x, double y);
public static void main(String[] args)
{
System.out.println(Operation.PLUS.eval(3, 4));
System.out.println(Operation.MINUS.eval(5, 4));
System.out.println(Operation.TIMES.eval(5, 4));
System.out.println(Operation.DIVIDE.eval(5, 4));
}
}