《寫給大忙人看的Java SE 8》——Java8新特性總結

接口中的默認方法和靜態方法

先考慮一個問題,如何向Java中的集合庫中增加方法?例如在Java 8中向Collection接口中添加了一個forEach方法。

如果在Java 8之前,對于接口來說,其中的方法必須都為抽象方法,也就是說接口中不允許有接口的實現,那么就需要對每個實現Collection接口的類都需要實現一個forEach方法。

但這就會造成在給接口添加新方法的同時影響了已有的實現,所以Java設計人員引入了接口默認方法,其目的是為了解決接口的修改與已有的實現不兼容的問題,接口默認方法可以作為庫、框架向前兼容的一種手段。

默認方法就像一個普通Java方法,只是方法用default關鍵字修飾。

下面來舉一個簡單的例子

public interface Person {
    //默認方法
    default String getName(String name) {
        return name;
    }
}
///////////////////////////////////////////////////////////////////////
public class Student implements Person {

}
//////////////////////////////////////////////////////////////////////
public class Test {
    public static void main(String[] args) {
        Person p = new Student();
        String name = p.getName("小李");
        System.out.println(name);
    }
}

我們定義了一個Person接口,其中getName是一個默認方法。接著編寫一個實現類,可以從結果中看到,雖然Student是空的,但是仍然可以實現getName方法。
顯然默認接口的出現打破了之前的一些基本規則,使用時要注意幾個問題。
考慮如果接口中定義了一個默認方法,而另外一個父類或者接口中又定義了一個同名的方法,該選擇哪個?
1.選擇父類中的接口。如果一個父類提供了具體的實現方法,那么接口中具有相同名稱和參數的默認方法會被忽略。
2.接口沖突。如果一個父接口提供了一個默認方法,而另一個接口也提供了具有相同名稱和參數類型的方法(不管該方法是否是默認方法),那么必須通過覆蓋方法來解決。
記住一個原則,就是“類優先”,即當類和接口都有一個同名方法時,只有父類中的方法會起作用。
“類優先”原則可以保證與Java 7的兼容性。如果你再接口中添加了一個默認方法,它對Java 8以前編寫的代碼不會產生任何影響。
下面來說說靜態方法
靜態方法就像一個普通Java靜態方法,但方法的權限修飾只能是public或者不寫。
默認方法和靜態方法使Java的功能更加豐富。
在Java 8中Collection接口中就添加了四個默認方法,stream()、parallelStream()、forEach()和removeIf()。Comparator接口也增加了許多默認方法和靜態方法。

函數式接口和Lambda表達式

函數式接口(Functional Interface)是只包含一個方法的抽象接口。
比如Java標準庫中的java.lang.Runnable,java.util.concurrent.Callable就是典型的函數式接口。
在Java 8中通過@FunctionalInterface注解,將一個接口標注為函數式接口,該接口只能包含一個方法。
@FunctionalInterface注解不是必須的,只要接口只包含一個方法,虛擬機會自動判斷該接口為函數式接口。
一般建議在接口上使用@FunctionalInterface注解進行聲明,以免他人錯誤地往接口中添加新方法,如果在你的接口中定義了第二個抽象方法的話,編譯器會報錯。

image

函數式接口是為Java 8中的lambda而設計的,lambda表達式的方法體其實就是函數接口的實現。
為什么要使用lambda表達式?
“lambda表達式”是一段可以傳遞的代碼,因為他可以被執行一次或多次。我們先回顧一下之前在Java中一直使用的相似的代碼塊。
當我們在一個線程中執行一些邏輯時,通常會將代碼放在一個實現Runnable接口的類的run方法中,如下所示:

new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++)
                    System.out.println("Without Lambda Expression");
            }}).start();

然后通過創建實例來啟動一個新的線程。run方法內包含了一個新線程中需要執行的代碼。
再來看另一個例子,如果想利用字符串長度排序而不是默認的字典順序排序,就需要自定義一個實現Comparator接口的類,然后將對象傳遞給sort方法。

class LengthComparator implements Comparator<String> {
    @Override
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
}
Arrays.sort(strings, new LengthComparator());

按鈕回調是另一個例子。將回調操作放在了一個實現了監聽器接口的類的一個方法中。

JButton button = new JButton("click");

button.addActionListener(new ActionListener() {    
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Without Lambda Expression");
    }
});

這三個例子中,出現了相同方式,一段代碼被傳遞給其他調用者——一個新線程、是一個排序方法或者是一個按鈕。這段代碼會在稍后被調用。
在Java中傳遞代碼并不是很容易,不可能將代碼塊到處傳遞。你不得不構建一個類的對象,由他的某個方法來包含所需的代碼。
而lambda表達式實際上就是代碼塊的傳遞的實現。其語法結構如下:
(parameters) -> expression 或者 (parameters) -> {statements;}
括號里的參數可以省略其類型,編譯器會根據上下文來推導參數的類型,你也可以顯式地指定參數類型,如果沒有參數,括號內可以為空。
方法體,如果有多行功能語句用大括號括起來,如果只有一行功能語句則可以省略大括號。

new Thread(() -> {
            for (int i = 0; i < 100; i++)
                System.out.println("Lambda Expression");
        }).start();
Comparator<String> c = (s1, s2) -> Integer.compare(s1.length(), s2.length());
button.addActionListener(e -> System.out.println("Lambda Expression"));

可以看到lambda表達式使代碼變得簡單,代替了匿名內部類。

下面來說一下方法引用,方法引用是lambda表達式的一種簡寫形式。 如果lambda表達式只是調用一個特定的已經存在的方法,則可以使用方法引用。

使用“::”操作符將方法名和對象或類的名字分隔開來。以下是四種使用情況:

  • 對象::實例方法
  • 類::靜態方法
  • 類::實例方法
  • 類::new
Arrays.sort(strings, String::compareToIgnoreCase);
// 等價于
Arrays.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));

上面的代碼就是第三種情況,對lambda表達式又一次進行了簡化。

Stream API

當處理集合時,通常會迭代所有元素并對其中的每一個進行處理。例如,我們希望統計一個字符串類型數組中,所有長度大于3的元素。

String[] strArr = { "Java8", "new", "feature", "Stream", "API" };
        int count = 0;
        for (String s : strArr) {
            if (s.length() > 3)
                count++;
        }

通常我們都會使用這段代碼來統計,并沒有什么錯誤,只是它很難被并行計算。這也是Java8引入大量操作符的原因,在Java8中,實現相同功能的操作符如下所示:

long count = Stream.of(strArr).filter(w -> w.length() > 3).count();

stream方法會為字符串列表生成一個Streamfilter方法會返回只包含字符串長度大于3的一個Stream,然后通過count方法計數。

一個Stream表面上與一個集合很類似,允許你改變和獲取數據,但實際上卻有很大區別:

  1. Stream自己不會存儲元素。元素可能被存儲在底層的集合中,或者根據需要產生出來。

  2. Stream操作符不會改變源對象。相反,他們返回一個持有新結果的Stream。

  3. Stream操作符可能是

    延遲執行

    的。意思是它們會等到需要結果的時候才執行。

Stream相對于循環操作有更好的可讀性。并且可以并行計算:

long count = Arrays.asList(strArr).parallelStream().filter(w -> w.length() > 3).count();

只需要把stream方法改成parallelStream,就可以讓Stream去并行執行過濾和統計操作。
Stream遵循“做什么,而不是怎么去做”的原則。只需要描述需要做什么,而不用考慮程序是怎樣實現的。
Stream很像Iterator,單向,只能遍歷一遍。但是Stream可以只通過一行代碼就實現多線程的并行計算。
當使用Stream時,會有三個階段:

  1. 創建一個Stream。
  2. 在一個或多個步驟中,將初始Stream轉化到另一個Stream的中間操作
  3. 使用一個終止操作來產生一個結果。該操作會強制他之前的延遲操作立即執行。在這之后,該Stream就不會在被使用了。
    從著三個階段來看,對應著三種類型的方法,首先是Stream的創建方法。
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();

中間操作包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
終止操作包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。

歡迎加入學習交流群569772982,大家一起學習交流。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容