你要知道的Java8 匿名內(nèi)部類、函數(shù)式接口烤低、lambda表達(dá)式與Stream API都在這里
轉(zhuǎn)載請(qǐng)注明出處 http://www.reibang.com/p/c832d2c6dd81
本文主要講解Java8 Stream API
懈费,但是要講解這一部分需要匿名內(nèi)部類、lambda表達(dá)式以及函數(shù)式接口的相關(guān)知識(shí),本文將分為兩篇文章來(lái)講解上述內(nèi)容尊惰,讀者可以按需查閱。
本文是該系列博文的第一篇Java 匿名內(nèi)部類弄屡、lambda表達(dá)式與函數(shù)式接口,主要講解Java
的匿名內(nèi)部類
逾条、lambda表達(dá)式
以及函數(shù)式接口
琢岩,第二篇文章Java Stream API主要講解Java Stream API
。
匿名內(nèi)部類
匿名內(nèi)部類
適用于那些只需要使用一次的類师脂,比如設(shè)計(jì)模式下的命令模式担孔,往往通過(guò)定義一系列接口進(jìn)行調(diào)用,有時(shí)有的命令只會(huì)執(zhí)行一次就不再執(zhí)行吃警,這個(gè)時(shí)候如果單獨(dú)定義一個(gè)類就顯得過(guò)于復(fù)雜并且編譯會(huì)生成這個(gè)類的.class
文件糕篇,不利于管理,這種情況下使用匿名內(nèi)部類
就能夠很好的簡(jiǎn)化編程并且不會(huì)編譯生成單獨(dú)的.class
文件酌心。
接下來(lái)看一個(gè)例子:
interface Programmer
{
void listLanguages();
void introduceMyself();
}
class MyProgrammer implements Programmer
{
public void listLanguages() {
System.out.println("Objective-C Swift Python Go Java");
}
public void introduceMyself() {
System.out.println("My Name is Jiaming Chen");
}
}
public class HelloWorld
{
static void info(Programmer programmer)
{
programmer.listLanguages();
programmer.introduceMyself();
}
public static void main(String[] args)
{
info(new MyProgrammer());
}
}
上面這個(gè)例子為了執(zhí)行info
函數(shù)定義了一個(gè)實(shí)現(xiàn)了Programmer
接口的類MyProgrammer
拌消,如果它只執(zhí)行一次這樣就顯得過(guò)于復(fù)雜,如果采用匿名內(nèi)部類
就會(huì)在很大程度上簡(jiǎn)化編程安券,首先介紹一下匿名內(nèi)部類
的基礎(chǔ)語(yǔ)法:
new 需要實(shí)現(xiàn)的接口() | 父類構(gòu)造器()
{
//需要實(shí)現(xiàn)的方法或重載父類的方法
}
匿名內(nèi)部類
的語(yǔ)法很簡(jiǎn)單墩崩,必須要實(shí)現(xiàn)一個(gè)接口或者繼承一個(gè)類,可以看到使用了new
關(guān)鍵詞侯勉,因此在創(chuàng)建匿名內(nèi)部類
的同時(shí)會(huì)創(chuàng)建一個(gè)該類的實(shí)例鹦筹,并且只能創(chuàng)建一個(gè)實(shí)例,創(chuàng)建完成后這個(gè)匿名內(nèi)部類
就不能再使用址貌,因此铐拐,匿名內(nèi)部類
不能是抽象類,由于匿名內(nèi)部類
沒(méi)有類名所以也不能定義構(gòu)造函數(shù)练对,但是可以在定義匿名內(nèi)部類
的時(shí)候調(diào)用父類的有參構(gòu)造器也可以定義初始化塊用于初始化父類的成員變量遍蟋。下面這個(gè)栗子是將上述代碼修改為匿名內(nèi)部類
的實(shí)現(xiàn)方式:
class MyProgrammer implements Programmer
{
public void listLanguages() {
System.out.println("Objective-C Swift Python Go Java");
}
public void introduceMyself() {
System.out.println("My Name is Jiaming Chen");
}
}
public class HelloWorld
{
static void info(Programmer programmer)
{
programmer.listLanguages();
programmer.introduceMyself();
}
public static void main(String[] args)
{
info(new Programmer(){
public void listLanguages() {
System.out.println("Objective-C Swift Python Go Java");
}
public void introduceMyself() {
System.out.println("My Name is Jiaming Chen");
}
});
}
}
通過(guò)對(duì)比發(fā)現(xiàn),使用匿名內(nèi)部類
比重新定義一個(gè)新類更加簡(jiǎn)潔螟凭,在創(chuàng)建匿名內(nèi)部類
的時(shí)候可以調(diào)用父類的有參構(gòu)造函數(shù)虚青,栗子如下:
abstract class Programmer
{
protected String name;
public Programmer(String name)
{
this.name = name;
}
public abstract void listLanguages();
public abstract void introduceMyself();
}
public class HelloWorld
{
static void info(Programmer programmer)
{
programmer.listLanguages();
programmer.introduceMyself();
}
public static void main(String[] args)
{
int age = 22;
Programmer p = new Programmer("Jiaming Chen"){
public void listLanguages()
{
System.out.println("Objective-C Swift Python Go Java");
}
public void introduceMyself()
{
System.out.println("My Name is " + this.name + " and I'm " + age + " years old.");
//age = 2;
}
};
info(p);
//age = 23;
}
}
上述栗子首先定義了一個(gè)抽象父類,并且該抽象父類只有一個(gè)構(gòu)造函數(shù)螺男,因此在創(chuàng)建匿名內(nèi)部類
的時(shí)候需要顯示調(diào)用該構(gòu)造函數(shù)挟憔,這樣就可以在匿名內(nèi)部類
內(nèi)部使用父類定義的成員變量了钟些,匿名內(nèi)部類
也可以使用外部變量,在Java8
中上述栗子中的age
會(huì)自動(dòng)聲明為final
類型绊谭,這稱為effectively final
政恍,只要匿名內(nèi)部類
訪問(wèn)了一個(gè)局部變量,這個(gè)局部變量無(wú)論是否被final
修飾它都會(huì)自動(dòng)被聲明為final
類型达传,不允許任何地方進(jìn)行修改篙耗,Java
與其他語(yǔ)言相比在閉包內(nèi)訪問(wèn)外部變量的局限更大,因?yàn)橹荒苁?code>final類型宪赶,比如OC
在block
內(nèi)部也可以捕獲外部變量宗弯,swift
也有一套閉包值捕獲機(jī)制,都可以對(duì)捕獲的值進(jìn)行修改或權(quán)限的設(shè)置搂妻,Java
的局限性太大蒙保。
lambda表達(dá)式
Java8
引入了lambda表達(dá)式
,在其他語(yǔ)言中欲主,比如python
邓厕、swift
都支持lambda表達(dá)式
,這個(gè)特性用起來(lái)也非常方便和簡(jiǎn)潔扁瓢。接下來(lái)舉一個(gè)常見(jiàn)的對(duì)一個(gè)列表進(jìn)行排序的例子:
class MyComparator implements Comparator<String>
{
public int compare(String o1, String o2)
{
return o1.compareTo(o2);
}
}
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
list.sort(new MyComparator());
list.forEach(System.out::println);
/*
輸出:(下面的代碼的輸出結(jié)果是一致的详恼,不再贅述)
Golang
Java
Objective-C
Python
Swift
*/
}
}
在開(kāi)發(fā)中經(jīng)常會(huì)對(duì)一個(gè)集合類型進(jìn)行排序操作,調(diào)用集合的sort
方法時(shí)需要傳入一個(gè)實(shí)現(xiàn)了Comparator
接口的參數(shù)引几,因此上述栗子就定義了一個(gè)類MyComparator
并且這個(gè)類實(shí)現(xiàn)了Comparator
接口昧互,因此可以創(chuàng)建一個(gè)MyComparator
的對(duì)象用于排序操作,不難發(fā)現(xiàn)這樣做非常復(fù)雜需要重新定義一個(gè)全新的類伟桅,經(jīng)過(guò)前文的介紹這里完全可以用匿名內(nèi)部類
來(lái)代替敞掘,關(guān)于最后一行代碼list.forEach(System.out::println);
在后文會(huì)介紹,這里先賣個(gè)關(guān)子楣铁,它的作用就是遍歷整個(gè)集合并輸出玖雁。接下來(lái)看一下使用匿名內(nèi)部類
實(shí)現(xiàn)的方式:
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
list.sort(new Comparator<String>(){
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
list.forEach(System.out::println);
}
}
結(jié)果同上,顯而易見(jiàn)民褂,采用了匿名內(nèi)部類
更加的方便了,代碼簡(jiǎn)潔明了疯潭,那有沒(méi)有再簡(jiǎn)介一點(diǎn)的辦法呢赊堪?答案當(dāng)然是肯定的,那就是使用lambda
表達(dá)式竖哩,栗子如下:
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
list.sort((String o1, String o2)->{
return o1.compareTo(o2);
});
list.forEach(System.out::println);
}
}
通過(guò)上述代碼發(fā)現(xiàn)哭廉,sort
函數(shù)傳入的參數(shù)是一個(gè)lambda
表達(dá)式,整個(gè)代碼非常類似前文中我們實(shí)現(xiàn)的compare
函數(shù)相叁,但是我們不需要再寫new Comparator<String()
這樣繁瑣的代碼遵绰,也不需要寫函數(shù)名辽幌,因此lambda表達(dá)式
可以很好的替代匿名內(nèi)部類
,不難發(fā)現(xiàn)lambda表達(dá)式
由三部分組成:
- 首先是參數(shù)列表椿访,這個(gè)參數(shù)列表與要實(shí)現(xiàn)的方法的參數(shù)列表一致乌企,因?yàn)橄到y(tǒng)已經(jīng)知道了你需要實(shí)現(xiàn)哪一個(gè)方法因此可以省略形參類型,當(dāng)只有一個(gè)參數(shù)時(shí)也可以省略圓括號(hào)成玫,但是當(dāng)沒(méi)有形參時(shí)圓括號(hào)不可以省略
- 接下來(lái)是一個(gè)
->
符號(hào)加酵,該符號(hào)用于分隔形參列表與函數(shù)體,該符號(hào)不允許省略哭当。 - 最后就是代碼體了猪腕,如果代碼體只有一行代碼就可以省略掉花括號(hào),并且如果方法需要有返回值連
return
關(guān)鍵詞都可以省略钦勘,系統(tǒng)會(huì)自動(dòng)將這一行代碼的結(jié)果返回陋葡。
通過(guò)上面的講解,我們就可以寫一個(gè)更加簡(jiǎn)潔lambda表達(dá)式
了彻采,栗子如下:
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
list.sort((s1, s2)->s1.compareTo(s2));
list.forEach(System.out::println);
}
}
上面的代碼我們省略了形參的類型腐缤,由于只有一行我們同時(shí)省略了花括號(hào)和return
語(yǔ)句,整個(gè)代碼相比使用匿名內(nèi)部類
更加簡(jiǎn)潔了颊亮。到這里有同學(xué)可能會(huì)問(wèn)了柴梆,lambda表達(dá)式
是怎么知道我們實(shí)現(xiàn)的是接口的哪一個(gè)方法?
lambda表達(dá)式
的類型也被稱為目標(biāo)類型 target type
终惑,該類型必須是函數(shù)式接口 Functional Interface
绍在,函數(shù)式接口
代表有且只有一個(gè)抽象方法,但是可以包含多個(gè)默認(rèn)方法或類方法的接口雹有,因此使用lambda表達(dá)式
系統(tǒng)一定知道我們實(shí)現(xiàn)的接口的哪一個(gè)方法偿渡,因?yàn)閷?shí)現(xiàn)的接口有且只有一個(gè)抽象方法供我們實(shí)現(xiàn)。
函數(shù)式接口
可以使用注釋@FunctionalInterface
來(lái)要求編譯器在編譯時(shí)進(jìn)行檢查霸奕,是否只包含一個(gè)抽象方法溜宽。Java
提供了大量的函數(shù)式接口
這樣就能使用lambda表達(dá)式
簡(jiǎn)化編程。lambda表達(dá)式
的目標(biāo)類型必須是函數(shù)式接口
质帅,lambda表達(dá)式
也只能為函數(shù)式接口
創(chuàng)建對(duì)象因?yàn)?code>lambda表達(dá)式只能實(shí)現(xiàn)一個(gè)抽象方法适揉。
前文介紹了在使用lambda表達(dá)式
時(shí),如果代碼體只有一行代碼可以省略花括號(hào)煤惩,如果有返回值也可以省略return
關(guān)鍵詞嫉嘀,不僅如此,lambda表達(dá)式
在只有一條代碼時(shí)還可以引用其他方法或構(gòu)造器并自動(dòng)調(diào)用魄揉,可以省略參數(shù)傳遞剪侮,代碼更加簡(jiǎn)潔,引用方法的語(yǔ)法需要使用::
符號(hào)洛退。lambda表達(dá)式
提供了四種引用方法和構(gòu)造器的方式:
- 引用對(duì)象的方法
類::實(shí)例方法
- 引用類方法
類::類方法
- 引用特定對(duì)象的方法
特定對(duì)象::實(shí)例方法
- 引用類的構(gòu)造器
類::new
舉個(gè)栗子:
public class HelloWorld
{
public static void main(String[] args)
{
ArrayList<String> list = new ArrayList<>();
list.add("Objective-C");
list.add("Swift");
list.add("Python");
list.add("Golang");
list.add("Java");
//list.sort((s1, s2)->s1.compareTo(s2));
list.sort(String::compareTo);
list.forEach(System.out::println);
}
}
對(duì)比上述兩行代碼瓣俯,第一個(gè)sort
函數(shù)傳入了一個(gè)lambda表達(dá)式
用于實(shí)現(xiàn)Comparator
接口的compare
函數(shù)杰标,由于該實(shí)現(xiàn)只有一條代碼,因此可以省略花括號(hào)以及return
關(guān)鍵字彩匕。第二個(gè)sort
方法則直接引用了對(duì)象的實(shí)例方法腔剂,語(yǔ)法規(guī)則為類::實(shí)例方法
,系統(tǒng)會(huì)自動(dòng)將函數(shù)式接口
實(shí)現(xiàn)的方法的所有參數(shù)中的第一個(gè)參數(shù)作為調(diào)用者推掸,接下來(lái)的參數(shù)依次傳入引用的方法中即自動(dòng)進(jìn)行s1.compareTo(s2)
的方法調(diào)用桶蝎,明顯第二個(gè)sort
函數(shù)調(diào)用更加簡(jiǎn)潔明了。
最后一行代碼list.forEach(System.out::println);
則引用了類方法谅畅,集合類的實(shí)例方法forEach
接收一個(gè)Consumer
接口對(duì)象登渣,該接口是一個(gè)函數(shù)式接口
,只有一個(gè)抽象方法void accept(T t);
毡泻,因此可以使用lambda表達(dá)式
進(jìn)行調(diào)用胜茧,這里引用System.out
的類方法println
,引用語(yǔ)法類::類方法
仇味,系統(tǒng)會(huì)自動(dòng)將實(shí)現(xiàn)的函數(shù)式接口
方法中的所有參數(shù)都傳入該類方法并進(jìn)行自動(dòng)調(diào)用呻顽。
再舉一個(gè)栗子:
@FunctionalInterface
interface Index
{
int index(String subString);
}
@FunctionalInterface
interface Generator
{
String generate();
}
public class HelloWorld
{
static int getIndex(Index t, String subString)
{
return t.index(subString);
}
static String generateString(Generator g)
{
return g.generate();
}
public static void main(String[] args)
{
String str = "Hello World";
System.out.println(getIndex(str::indexOf, "e"));
System.out.println(generateString(String::new).length());
}
}
這個(gè)栗子似乎沒(méi)有任何實(shí)際意義,就是為了演示引用特定對(duì)象的實(shí)例方法和引用類的構(gòu)造器丹墨。接口Index
和Generator
都是函數(shù)式接口
廊遍,因此可以使用lambda表達(dá)式
。對(duì)于getIndex
方法需要傳入一個(gè)實(shí)現(xiàn)Index
接口的對(duì)象和一個(gè)子串贩挣,在調(diào)用時(shí)首先定義了一個(gè)字符串Hello World
喉前,然后引用了這個(gè)對(duì)象的實(shí)例方法indexOf
,這個(gè)時(shí)候系統(tǒng)會(huì)自動(dòng)將這個(gè)特定對(duì)象作為調(diào)用者然后將所有的參數(shù)因此傳入該實(shí)力方法王财。
引用構(gòu)造器的方法也很簡(jiǎn)單類::new
卵迂,不再贅述。
總結(jié)
本文主要講解匿名內(nèi)部類
绒净、lambda表達(dá)式
见咒、函數(shù)式接口
以及lambda表達(dá)式
的方法引用
和構(gòu)造器引用
,通過(guò)幾個(gè)例子逐步深入挂疆,逐步簡(jiǎn)化代碼的編寫改览,可以發(fā)現(xiàn)Java8
提供的lambda表達(dá)式
是如此的強(qiáng)大。接下來(lái)的一篇文章會(huì)對(duì)Java8
新增的Stream API
進(jìn)行講解缤言,Stream
的流式API支持并行宝当,對(duì)傳統(tǒng)編程方式進(jìn)行了改進(jìn),可以編寫出更簡(jiǎn)潔明了的高性能代碼墨闲。有興趣的讀者可以閱讀Java Stream API今妄。
備注
由于作者水平有限郑口,難免出現(xiàn)紕漏鸳碧,如有問(wèn)題還請(qǐng)不吝賜教盾鳞。