JDK8 新特性

為什么要學Java8

  • Java8讓你的編程變得更容易
  • 充分穩定的利用計算機硬件資源

Lambda

lambda 是什么 ?

"Lambda 表達式 "(lambda expression) 是一個匿名函數,Lambda 表達式基于數學中的 λ 演算得名,直接對應于其中的 lambda 抽象(lambda abstraction) ,是一個匿名函數,即沒有函數名的函數,所以試著使用匿名函數的方式來理解:

(params) -> expression // 函數名呢 ? 沒有 !!!
(params) -> statement // 函數名呢 ? 沒有 !!!
(params) -> { statements } // 函數名呢 ? 沒有 !!!
語法是什么 ?
  • 一個括號:括號內用逗號分隔的形式參數(參數是函數式接口里面方法的參數)

  • 一個箭頭符號: 箭頭符號 “->” 指向方法體,方法體是函數式接口里面需要實現的方法。

//方法體只有一行表達式的時候可以省略{}
textView.setOnClickListener((view)-> textView.setText("1"));
//方法體中有一行以上代碼就不能省略{}
textView.setOnClickListener((view) -> {
            textView.setText("一行代碼");
            textView.setText("二行代碼");
        });

Lambda 表達式是在 JDK 8 中開始支持的一種函數式推導語言,能夠大量減少匿名內部類那種冗余的代碼。在 Android 中,可以大量使用在設置監聽,設置異步回調等場景。

Lambda 表達式 vs 匿名類

既然 lambda 表達式即將正式取代 Java 代碼中的匿名內部類,那么有必要對二者的不同點做一個比較分析.

  • 關鍵字 this 。
    匿名類的 this 關鍵字指向匿名類
    lambda 表達式的 this關鍵字指向包圍 lambda 表達式的
    類。
  • 編譯方式。
    Java 編譯器將 lambda 表達式編譯成類的私有方法。
快速開始
  • 在module中build.gradle中
    android節點上面添加
/*lambda表達式*/
apply plugin: 'me.tatarka.retrolambda'

在android節點中添加

compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

在dependencies節點上面android節點外面添加:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        /*lambda表達式*/
        classpath 'me.tatarka:gradle-retrolambda:3.2.0'
    }
}
實例
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = (TextView) findViewById(R.id.tv_text);
        textView.setOnClickListener((view)-> textView.setText("lambda表達式實現的點擊事件"));
        
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                textView.setText(" 內部類實現的點擊事件");
            }
        });
    }
}
小結

使用 lambda 表達式設計的代碼會更加簡潔,而且還可讀.

函數式接口

函數式接口是只包含一個抽象方法的接口。函數式接口有時候被稱為SAM類型,意思是單抽象方法(Single Abstract Method)。新版的Java API帶有@FunctionInterface注解的接口表示該接口被設計為函數式接口,如果用@FunctionInterface定義了一個接口卻有多個抽象方法,那么編譯器會報錯。

使用函數式接口

新版的Java API已經為我們提供了很多函數式接口,在java.util.function包下,下面我們舉例介紹幾個常用的接口。

  • Predicate (java.util.function.Predicate<T>)
    Predicate定義了一個名為test的抽象方法,接收T,返回boolean值.
/** 接口定義 */
@FunctionInterface
public interface Predicate<T>{
    boolean test(T t);
}
/** Example */
public static <T> List<T> process(List<T> mList, Predicate<T> condition) {
    List<T> results = new ArrayList<>();
    for (T t : mList) {
        if (condition.test(t)) {
            results.add(t);
        }
    }
    return results;
}
  • Consumer (java.util.function.Consumer<T>)
    Consumer定義了一個名為accept的抽象方法接收T,返回值(void)
/** 接口定義 */
@FunctionInterface
public interface Consumer<T>{
    void accept(T t);
}
/** Example */
public static <T> void process(List<T> mList, Consumer<T> c) {
    for (T t : mList) {
        c.accept(t);
    }
}
  • Function (java.util.function.Function<T, R>)
    Function定義了一個名為apply的抽象方法,接收T, R
/** 接口定義 */
@FunctionInterface
public interface Function <T, R>{
    R apply(T t);
}
/** Example */
public static <T, R> List<R> process(List<T> mList, Function<T, R> f) {
    List<R> results = new ArrayList<>();
    for (T t : mList) {
        results.add(f.apply(t));
    }
    return results;
}
  • Supplier (java.util.function.Supplier<T>)
    Supplier定義了一個名為get的抽象方法,不接受任何參數,返回T
/** 接口定義 */
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
/** Example */
Supplier<String> supplier = () -> {return "Hello Java8";};
System.out.println(supplier.get()); //Hello Java8

還有很多有興趣的可以自己查閱:

  • BinaryOperator
    (T,?T) -> T
  1. BiPredicate
    (T,?U) -> boolean
  2. BiConsumer
    (T,?U) -> void
  3. BiFunction
    (T,?U) -> R

注意:Java中的數據類型可分為引用類型(Byte、Integer、List等)和原始類型(byte、int、float、char等)。Java中提供裝箱和拆箱的機制,例如裝箱就是把原始類型包裝后放到堆上,所以裝箱后需要更多的內存,所以Java8為大多數函數式接口提供了相應的版本來避免這個問題,例如:
IntPredicate、LongPredicate、DoublePredicate
IntConsumer、LongConsumer、DoubleConsumer

方法引用 ::

方法引用是什么 ?

方法引用是用來直接訪問類或者實例的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由兼容的函數式接口構成的目標類型上下文。計算時,方法引用會創建函數式接口的一個實例。

當Lambda表達式中只是執行一個方法調用時,不用Lambda表達式,直接通過方法引用的形式可讀性更高一些。

  • 作用
    方法引用的唯一用途是支持Lambda的簡寫。
    方法引用提高了代碼的可讀性,也使邏輯更加清晰。
  • 組成
    使用::操作符將方法名和對象或類的名字分隔開。
    “::” 是域操作符(也可以稱作定界符、分隔符)。


  • 分類
  1. 靜態方法引用
    組成語法格式:ClassName::staticMethodName
String::valueOf等價于lambda表達式 (s) ->String.valueOf(s) 
Math::pow等價于lambda表達式(x, y) -> Math.pow(x, y);
  1. 實例方法引用
    這種語法與用于靜態方法的語法類似,只不過這里使用的是對象引用而不是類名.
    實例方法引用又分以下三種類型
    a.實例上的實例方法引用
    組成語法格式:instanceReference::methodName
    b.超類上的實例方法引用
    組成語法格式:super::methodName
    eg:
super::name //通過super指向父類方法
this :: equals等價于lambda表達式  x -> this.equals(x) //通過this指向本類方法

c.類型上的實例方法引用
組成語法格式:ClassName::methodName(不推薦使用)
3)構造方法引用
構造方法引用又分構造方法引用和數組構造方法引用。
a.構造方法引用 (也可以稱作構造器引用)
組成語法格式:Class::new
構造函數本質上是靜態方法,只是方法名字比較特殊,使用的是new 關鍵字。
eg:

String::new, 等價于lambda表達式 () -> new String() 

b.數組構造方法引用:
組成語法格式:TypeName[]::new
eg:
int[]::new是一個含有一個參數的構造器引用,這個參數就是數組的長度。

int[]::new等價于lambda表達式  x -> new int[x]。
假想存在一個接收int參數的數組構造方法
IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10);  //創建數組 int[10]

forEach

forEach 內部迭代以前 Java 集合是不能夠表達內部迭代的,而只提供了一種外部迭代的方式,也就是 for 或者 while 循環。
jdk8之前

final List<String> mList = Arrays.asList("1111", "2222", "3333", "4444");  
//普通for循環
for (int i = 0; i < mList.size(); i++) {  
    System.out.println(mList.get(i));
}   
//增強for循環:
for(String str : mList) {  
    System.out.println(str);
} 

Java8中Iterable接口擁有一個forEach方法用來實現內部遍歷器:

//java8之后
mList.forEach(new Consumer<String>() {  
    public void accept(final String str) {
        System.out.println(str);
    }
});

結合Lambda:

mList.forEach(str -> System.out.println(str));
mList.forEach(System.out::println);  

Stream 初體驗

Stream是元素的集合,這點讓 Stream 看起來用些類似 Iterator;
可以支持順序和并行的對原 Stream 進行匯聚的操作;

  • Stream 是什么?高級迭代器
    大家可以把 Stream 當成一個高級版本的 Iterator 。
    Iterator, 用戶只能一個一個的遍歷元素并對其執行某些操作;Stream用戶只要給出需要對其包含的元素執行什么操作
    比如 “ 過濾掉長度大于 10 的字符串 ” 、 “ 獲取每個字符串的首字母 ” 等,具體這些操作如何應用到每個元素上,就給 Stream 就好了
List<Integer> nums = Arrays.asList(1,null,3,4,null,6);
nums.stream()// 創建 stream 實例
      .filter(num -> num != null)// 使用條件進行過濾
      .forEach(n->System.out.println(n));// 遍歷集合元素

上面這段代碼是獲取一個 List 中,元素不為 null 的個數。


  1. 紅色框是生命開始的地方,負責創建一個 Stream 實例;
  2. 綠色框是賦予Stream靈魂的地方,把一個 Stream 轉換成另外一個 Stream,即包含所有 nums 變量的 Stream ,經過綠框的 filter 方法以后,重新生成了一個過濾掉原 nums列表所有為null的 Stream ;
  3. 藍色框中的語句是豐收的地方,把 Stream 的里面包含的內容按照某種算法來匯聚成一個值


  • 使用步驟
    在此我們總結一下使用 Stream 的基本步驟:創建 Stream;轉換 Stream,每次轉換原有Stream對象不改變,返回一個新的Stream對象(可以有多次轉換); 對 Stream 進行聚合(Reduce)操作,獲取想要的結果;
  • 操作符
//創建Integer類型的List
List<Integer> nums = Arrays.asList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println("sum is:"+
nums.stream()// 獲取其對應的 Stream 對象
.filter(num -> num != null)// 使用條件過濾
.distinct()// 去重
.mapToInt(num -> num * 2)// 每個元素乘以 2
.peek(System.out::println)// 每個元素被消費的時候打印自身
.skip(2)// 跳過前兩個元素
.limit(4)// 返回前四個元素
.sum());// 加和運算

所有轉換操作都是 lazy 的,多個轉換操作只會在匯聚操作的時候融合起來,一次循環完成。我們可以這樣簡單的理解, Stream 里有個操作函數的集合,每次轉換操作就是把轉換函數放入這個集合中,在匯聚操作的時候循環 Stream 對應的集合,然后對每個元素執行所有的函數。

經典案例

例 1 、用 lambda 表達式實現 Runnable

private void testRunnable() {
        // Java 8 之前:
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("在Java8之前,需要些很多代碼");
            }
        }).start();
        //Java 8 方式:
        new Thread(() -> System.out.println("在java8中簡單的一句")).start();
    }

例 2 、使用 Java 8 lambda 表達式進行事件處理,給一個按鈕添加監聽器:

private void testAddListener() {
        // Java 8 之前:
        mBnt = (Button) findViewById(R.id.main_btn_click);
        mBnt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ToastUtil.showShort(MainActivity.this, " 我被點擊了");
            }
        });
        //Java 8 方式:
        mBnt.setOnClickListener(v -> ToastUtil.showShort(MainActivity.this, "我被點擊了"));
    }

例 3 、使用 forEach+lambda 表達式對集合進行迭代

protected static void testForEach() {
        // Java 8 之前:
        List<String> list = Arrays.asList("java web", "ios", "android", "h5");
        for (Object item : list) {
            System.out.println(item);
        }
        //Java8方式:
        list.forEach(System.out::println);
    }

例 4 、 Java 8 中使用 lambda 表達式的 Map示例

protected static void test() {
        // jdk8之前
        List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
        for (Integer cost : costBeforeTax) {
            double price = cost + 0.12 * cost;
            System.out.println(price);
        }
        // jdk8
        List<Integer> costBeforeTax2 = Arrays.asList(100, 200, 300, 400, 500);
        costBeforeTax2.stream()// 獲取 Stream 對象
                      .map((cost) -> cost + 0.12 * cost)// 對每一個元素加上 12% 的稅
                      .forEach(System.out::println);// 遍歷
    }

例 5 Java 8 中使用 lambda 表達式的 Map 和 Reduce 示例

protected static void test() {
        //  為每個訂單加上 12% 的稅
        // JDK8 之前
        List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
        double total = 0;
        for (Integer cost : costBeforeTax) {
            double price = cost + .12 * cost;
            total = total + price;
        }
        System.out.println("Total : " + total);
        // JDK8
        List<Integer> costBeforeTax2 = Arrays.asList(100, 200, 300, 400, 500);
        double bill = costBeforeTax2.stream()// 獲取 stream 對象
                .map((cost) -> cost + 0.12 * cost)// 每一個元素 進行增加
                .reduce((sum, cost) -> sum + cost)//sun(a,b)
                .get();// 返回結果
        System.out.println("Total : " + bill);
    }

例 6 、通過過濾創建一個 String 列表過濾是 Java 開發者在大規模集合上的一個常用操作,而現在使用 lambda 表達式和流 API 過濾大規模數據集合是驚人的簡單。流提供了一個 filter() 方法,接受一個 Predicate 對象,即可以傳入一個 lambda 表達式作為過濾邏輯。

private static void test() {
        //創建一個字符串列表,每個字符串長度大于 2
        List<String> strList = Arrays.asList("Java", "Scala", "C", "Haskell", "Lisp");
        List<String> filtered = strList.stream()// 獲取 Stream 對象
                                       .filter(x -> x.length() > 2)// 過濾
                                       .collect(Collectors.toList());// 返回集合
        System.out.printf(" 原集合 : %s, 過濾后 : %s %n", strList, filtered);
        // 原集合 : [Java, Scala, C, Haskell, Lisp], 過濾后 : [Java, Scala, Haskell, Lisp]
    }

例 7 、對列表的每個元素應用函數
我們通常需要對列表的每個元素使用某個函數,例如逐一乘以某個數、除以某個數或者做其它操作。這些操作都很適合用 map() 方法,可以將轉換邏輯以lambda 表達式的形式放在 map() 方法里,就可以對集合的各個元素進行轉換了,如下所示。

private static void testStream7() {
        //  將字符串換成大寫并用逗號鏈接起來
        List<String> list = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.", "Canada");
        String listToString = list.stream()// 獲取 stream 對象
                .map(x -> x.toUpperCase())// 格式修改 轉成大寫
                .collect(Collectors.joining(", "));// 每個字符串拼接
        System.out.println(listToString);
    }

例 8 、復制不同的值,創建一個子列表
本例展示了如何利用流的 distinct() 方法來對集合進行去重。
private static void testStream8() {
// 用所有不同的數字創建一個正方形列表
List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers.stream()// 獲取 Stream 對象
.map( i -> i*i)// 求平方
.distinct()// 去重
.collect(Collectors.toList());// 聚集:轉成集合
System.out.printf(" 原有集合 : %s, 去重后 : %s %n", numbers, distinct);
}
例 9 、計算集合元素的最大值、最小值、總和以及平均值獲取常用統計值

private static void testStream9() {
        //  獲取數字的個數、最小值、最大值、總和以及平均值
        List<Integer> list = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
        //IntSummaryStatistics: 集合概要例如 : count, min, max, sum, and average.
        IntSummaryStatistics stats = list//
                .stream()// 獲取 Stream 對象
                .mapToInt((x) -> x)// 格式轉換
                .summaryStatistics();//
        System.out.println(" 最大值 : " + stats.getMax());
        System.out.println(" 最小值 : " + stats.getMin());
        System.out.println(" 求和 : " + stats.getSum());
        System.out.println(" 平均值 : " + stats.getAverage());
    }

總結
若一個方法接收 Runnable 、 Comparable 或者 Callable 接口,都有單個抽象方法,可以傳入 lambda 表達式。
類似的java.util.function包內的接口,例如 Predicate 、 Function 、 Consumer 或 Supplier ,那么可以向其傳 lambda 表達式。
lambda 表達式內可以使用方法引用,僅當該方法不修改 lambda 表達式提供的參數。本例中的 lambda 表達式可以換為方法引用,因為這僅是一個參數相同的
簡單方法調用。

list.forEach(n -> System.out.println(n));
list.forEach(System.out::println); //  使用方法引用

若對參數有任何修改,則不能使用方法引用,而需鍵入完整地 lambda 表達

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

推薦閱讀更多精彩內容

  • 基礎語法 Lambda需要函數式接口支持 接口用@Functiona1Interface修飾,就是函數式接口 La...
    小草丶body閱讀 505評論 0 0
  • 官方新特性說明地址 下面對幾個常用的特性做下重點說明。 一、Lambda表達式 1.1 函數式編程 百科介紹:h...
    丘八老爺閱讀 974評論 0 6
  • Java8是Java5以來最具革命性的版本。為Java語言、類庫、開發工具與JVM帶來了大量新特性。以下是新特性的...
    quantumcs閱讀 196評論 0 3
  • lambda表達式(又被成為“閉包”或“匿名方法”)方法引用和構造方法引用擴展的目標類型和類型推導接口中的默認方法...
    183207efd207閱讀 1,492評論 0 5
  • 原鏈接:http://www.cnblogs.com/langtianya/p/3757993.html JDK各...
    把愛放下會走更遠閱讀 1,125評論 0 10