java1.8新特性(二)lambda表達式

---轉載於:---html

http://www.javashuo.com/article/p-frefytmt-bk.html
java

1、什麼是lambda表達式?git

     Lambda 是一個匿名函數,咱們能夠把 Lambda 表達式理解爲是一段能夠傳遞的代碼(將代碼像數據同樣進行傳遞)。能夠寫出更簡潔、更靈活的代碼。做爲一種更緊湊的代碼風格,使 Java的語言表達能力獲得了提高。redis

匿名內部類的寫法:express

複製代碼
public void demo1(){
    
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    
    Runnable runnable = new Runnable() {
        @Override
        public void run() {}
    };
}
複製代碼

這樣寫會發現一個問題,實現的方法是冗餘的代碼,實際當中並無什麼用處。咱們看看Lambda的寫法。微信

Lambda表達式的寫法多線程

public void demo2(){
    
    Comparator<Integer> comparator = (x,y) -> Integer.compare(x, y);
    
    Runnable runnable = () -> System.out.println("lambda表達式");
}

咱們會發現Lambda表達式的寫法更加的簡潔、靈活。它只關心參數和執行的功能(具體須要幹什麼,好比->後的Integer.compare(x, y))。app

2、lambda表達式語法ide

lambda表達式的通常語法:函數

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

包含三個部分:參數列表,箭頭(->),以及一個表達式或語句塊。

1.一個括號內用逗號分隔的形式參數,參數是函數式接口裏面方法的參數

2.一個箭頭符號:->

3.方法體,能夠是表達式和代碼塊,方法體是函數式接口裏面方法的實現,若是是代碼塊,則必須用{}來包裹起來,且須要一個return 返回值,但有個例外,若函數式接口裏面方法返回值是void,則無需{}。

整體看起來像這樣:

(parameters) -> expression 或者 (parameters) -> { statements; }

上面的lambda表達式語法能夠認爲是最全的版本,寫起來仍是稍稍有些繁瑣。彆着急,下面陸續介紹一下lambda表達式的各類簡化版:

1. 參數類型省略–絕大多數狀況,編譯器均可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:

(param1,param2, ..., paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

2. 單參數語法:當lambda表達式的參數個數只有一個,能夠省略小括號。lambda表達式簡寫爲:

param1 -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

3. 單語句寫法:當lambda表達式只包含一條語句時,能夠省略大括號、return和語句結尾的分號。lambda表達式簡化爲:

param1 -> statment

下面看幾個例子:

demo1:無參,無返回值,Lambda 體只需一條語句

Runnable runnable = () -> System.out.println("lamda表達式");

demo2:Lambda 只須要一個參數

Consumer<String> consumer=(x)->System.out.println(x);

demo3:Lambda 只須要一個參數時,參數的小括號能夠省略

Consumer<String> consumer=x->System.out.println(x);

demo4:Lambda 須要兩個參數

Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);

demo5:當 Lambda 體只有一條語句時,return 與大括號能夠省略

BinaryOperator<Integer> binaryOperator=(x,y)->(x+y);

demo6:數據類型能夠省略,由於可由編譯器推斷得出,稱爲「類型推斷」

BinaryOperator<Integer> bo=(x,y)->{
System.out.println("Lambda");
return x+y;};

類型推斷

    上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然能夠編譯,這是由於 javac 根據程序的上下文,在後臺推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的 「類型推斷」。

3、lambda表達式的類型

咱們都知道,Java是一種強類型語言。全部的方法參數都有類型,那麼lambda表達式是一種什麼類型呢?

複製代碼
View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //...
    }
};
        
button.setOnClickListener(listener);
複製代碼

如上所示,以往咱們是經過使用單一方法的接口來表明一個方法而且重用它。

在lambda表達式中,仍使用的和以前同樣的形式。咱們叫作函數式接口(functional interface)。如咱們以前button的點擊響應事件使用的View.OnClickListener就是一個函數式接口。

複製代碼
public class View implements Drawable.Callback, KeyEvent.Callback,
    AccessibilityEventSource {
    ...
    
    public interface OnClickListener {
        void onClick(View v);
    }
    ...
}
複製代碼

那究竟什麼樣的接口是函數式接口呢?

     函數式接口是隻有一個抽象方法的接口,用做表示lambda表達式的類型。 好比Java標準庫中的java.lang.Runnable和java.util.Comparator都是典型的函數式接口。java 8提供 @FunctionalInterface做爲註解,這個註解是非必須的,只要接口符合函數式接口的標準(即只包含一個方法的接口),虛擬機會自動判斷,但最好在接口上使用註解@FunctionalInterface進行聲明,以避免團隊的其餘人員錯誤地往接口中添加新的方法。舉例以下:

複製代碼
@FunctionalInterface
public interface Runnable { void run(); }

public interface Callable<V> { V call() throws Exception; }

public interface ActionListener { void actionPerformed(ActionEvent e); }

public interface Comparator<T> { 
    int compare(T o1, T o2); 
    
    boolean equals(Object obj); 
}
複製代碼

     注意最後這個Comparator接口。它裏面聲明瞭兩個方法,貌似不符合函數接口的定義,但它的確是函數接口。這是由於equals方法是Object的,全部的接口都會聲明Object的public方法——雖然大可能是隱式的。因此,Comparator顯式的聲明瞭equals不影響它依然是個函數接口。

     Java中的lambda沒法單獨出現,它須要一個函數式接口來盛放,lambda表達式方法體其實就是函數接口的實現。即Lambda表達式不能脫離目標類型存在,這個目標類型就是函數式接口,看下面的例子: 

String []datas = new String[] {"peng","zhao","li"};
Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
Arrays.sort(datas,comp);
Stream.of(datas).forEach(param -> {System.out.println(param);}); 

Lambda表達式被賦值給了comp函數接口變量。

你能夠用一個lambda達式爲一個函數接口賦值:

Runnable r1 = () -> {System.out.println("Hello Lambda!");};

 而後再賦值給一個Object:

Object obj = r1;

但卻不能這樣幹:

 Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!

必須顯式的轉型成一個函數接口才能夠:

Object o = (Runnable) () -> { System.out.println("hi"); }; // correct

一個lambda表達式只有在轉型成一個函數接口後才能被當作Object使用。因此下面這句也不能編譯:

 System.out.println( () -> {} ); //錯誤! 目標類型不明

必須先轉型:

System.out.println( (Runnable)() -> {} ); // 正確

假設你本身寫了一個函數接口,長的跟Runnable如出一轍:

@FunctionalInterface
public interface MyRunnable {
    public void run();
}

那麼

Runnable r1 =    () -> {System.out.println("Hello Lambda!");};
MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};

都是正確的寫法。這說明一個lambda表達式能夠有多個目標類型(函數接口),只要函數匹配成功便可。但需注意一個lambda表達式必須至少有一個目標類型。

JDK預約義了不少函數接口以免用戶重複定義。最典型的是Function:

@FunctionalInterface
public interface Function<T, R> {  
    R apply(T t);
}

這個接口表明一個函數,接受一個T類型的參數,並返回一個R類型的返回值。另外一個預約義函數接口叫作Consumer,跟Function的惟一不一樣是它沒有返回值。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

還有一個Predicate,用來判斷某項條件是否知足。常常用來進行篩濾操做:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

綜上所述,一個lambda表達式其實就是定義了一個匿名方法,只不過這個方法必須符合至少一個函數接口。

4、lambda表達式可以使用的變量

先舉例:

複製代碼
@Test
public void test1(){
    //將爲列表中的字符串添加前綴字符串
    String waibu = "lambda :";
    List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
    List<String>execStrs = proStrs.stream().map(chuandi -> {
        Long zidingyi = System.currentTimeMillis();
        return waibu + chuandi + " -----:" + zidingyi;
    }).collect(Collectors.toList());
    
    execStrs.forEach(System.out::println);
}
複製代碼

輸出:

lambda :Ni -----:1498722594781
lambda :Hao -----:1498722594781
lambda :Lambda -----:1498722594781

變量waibu :外部變量

變量chuandi :傳遞變量

變量zidingyi :內部自定義變量

     lambda表達式能夠訪問給它傳遞的變量,訪問本身內部定義的變量,同時也能訪問它外部的變量。不過lambda表達式訪問外部變量有一個很是重要的限制:變量不可變(只是引用不可變,而不是真正的不可變)。

當在表達式內部修改waibu = waibu + " ";時,IDE就會提示你:

Local variable waibu defined in an enclosing scope must be final or effectively final

編譯時會報錯。由於變量waibu被lambda表達式引用,因此編譯器會隱式的把其當成final來處理

之前Java的匿名內部類在訪問外部變量的時候,外部變量必須用final修飾。如今java8對這個限制作了優化,能夠不用顯示使用final修飾,可是編譯器隱式當成final來處理。

5、lambda表達式做用域

整體來講,Lambda表達式的變量做用域與內部類很是類似,只是條件相對來講,放寬了些,之前內部類要想引用外部類的變量,必須像下面這樣 

複製代碼
final String[] datas = new String[] { "peng", "Zhao", "li" };
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();
複製代碼

將變量聲明爲final類型的,如今在Java 8中能夠這樣寫代碼

複製代碼
String []datas = new String[] {"peng","Zhao","li"};
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();
複製代碼

也能夠這樣寫:

new Thread(() -> System.out.println(datas)).start();

     看了上面的兩段代碼,可以發現一個顯著的不一樣,就是Java 8中內部類或者Lambda表達式對外部類變量的引用條件放鬆了,不要求強制的加上final關鍵字了,可是Java 8中要求這個變量是effectively final。What is effectively final?

Effectively final就是有效只讀變量,意思是這個變量能夠不加final關鍵字,可是這個變量必須是隻讀變量,即一旦定義後,在後面就不能再隨意修改,以下代碼會編譯出錯

String []datas = new String[] {"peng","Zhao","li"};
datas = null;
new Thread(() -> System.out.println(datas)).start();

Java中內部類以及Lambda表達式中也不容許修改外部類中的變量,這是爲了不多線程狀況下的race condition。

6、lambda表達式中的this概念

在lambda中,this不是指向lambda表達式產生的那個對象,而是聲明它的外部對象。

例如:

複製代碼
package com.demo;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class WhatThis {
    
    public void whatThis(){
        //轉全小寫
         List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
        List<String> execStrs = proStrs.stream().map(str -> {
             System.out.println(this.getClass().getName());
             return str.toLowerCase();
        }).collect(Collectors.toList());
        
        execStrs.forEach(System.out::println);
    }
    
    public static void main(String[] args) {
        WhatThis wt = new WhatThis();
        wt.whatThis();
    }

}
複製代碼

輸出:

com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda

 

分類: java8
3
0
« 上一篇: Java多線程(九)—— interrupt()和線程終止方式
» 下一篇: redis學習(九)——數據持久化
</div><p>---恢復內容結束---</p><div class="postBody">

1、什麼是lambda表達式?

     Lambda 是一個匿名函數,咱們能夠把 Lambda 表達式理解爲是一段能夠傳遞的代碼(將代碼像數據同樣進行傳遞)。能夠寫出更簡潔、更靈活的代碼。做爲一種更緊湊的代碼風格,使 Java的語言表達能力獲得了提高。

匿名內部類的寫法:

複製代碼
public void demo1(){
    
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return Integer.compare(o1, o2);
        }
    };
    
    Runnable runnable = new Runnable() {
        @Override
        public void run() {}
    };
}
複製代碼

這樣寫會發現一個問題,實現的方法是冗餘的代碼,實際當中並無什麼用處。咱們看看Lambda的寫法。

Lambda表達式的寫法

public void demo2(){
    
    Comparator<Integer> comparator = (x,y) -> Integer.compare(x, y);
    
    Runnable runnable = () -> System.out.println("lambda表達式");
}

咱們會發現Lambda表達式的寫法更加的簡潔、靈活。它只關心參數和執行的功能(具體須要幹什麼,好比->後的Integer.compare(x, y))。

2、lambda表達式語法

lambda表達式的通常語法:

(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

包含三個部分:參數列表,箭頭(->),以及一個表達式或語句塊。

1.一個括號內用逗號分隔的形式參數,參數是函數式接口裏面方法的參數

2.一個箭頭符號:->

3.方法體,能夠是表達式和代碼塊,方法體是函數式接口裏面方法的實現,若是是代碼塊,則必須用{}來包裹起來,且須要一個return 返回值,但有個例外,若函數式接口裏面方法返回值是void,則無需{}。

整體看起來像這樣:

(parameters) -> expression 或者 (parameters) -> { statements; }

上面的lambda表達式語法能夠認爲是最全的版本,寫起來仍是稍稍有些繁瑣。彆着急,下面陸續介紹一下lambda表達式的各類簡化版:

1. 參數類型省略–絕大多數狀況,編譯器均可以從上下文環境中推斷出lambda表達式的參數類型。這樣lambda表達式就變成了:

(param1,param2, ..., paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

2. 單參數語法:當lambda表達式的參數個數只有一個,能夠省略小括號。lambda表達式簡寫爲:

param1 -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

3. 單語句寫法:當lambda表達式只包含一條語句時,能夠省略大括號、return和語句結尾的分號。lambda表達式簡化爲:

param1 -> statment

下面看幾個例子:

demo1:無參,無返回值,Lambda 體只需一條語句

Runnable runnable = () -> System.out.println("lamda表達式");

demo2:Lambda 只須要一個參數

Consumer<String> consumer=(x)->System.out.println(x);

demo3:Lambda 只須要一個參數時,參數的小括號能夠省略

Consumer<String> consumer=x->System.out.println(x);

demo4:Lambda 須要兩個參數

Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);

demo5:當 Lambda 體只有一條語句時,return 與大括號能夠省略

BinaryOperator<Integer> binaryOperator=(x,y)->(x+y);

demo6:數據類型能夠省略,由於可由編譯器推斷得出,稱爲「類型推斷」

BinaryOperator<Integer> bo=(x,y)->{
System.out.println("Lambda");
return x+y;};

類型推斷

    上述 Lambda 表達式中的參數類型都是由編譯器推斷得出的。Lambda 表達式中無需指定類型,程序依然能夠編譯,這是由於 javac 根據程序的上下文,在後臺推斷出了參數的類型。Lambda 表達式的類型依賴於上下文環境,是由編譯器推斷出來的。這就是所謂的 「類型推斷」。

3、lambda表達式的類型

咱們都知道,Java是一種強類型語言。全部的方法參數都有類型,那麼lambda表達式是一種什麼類型呢?

複製代碼
View.OnClickListener listener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //...
    }
};
        
button.setOnClickListener(listener);
複製代碼

如上所示,以往咱們是經過使用單一方法的接口來表明一個方法而且重用它。

在lambda表達式中,仍使用的和以前同樣的形式。咱們叫作函數式接口(functional interface)。如咱們以前button的點擊響應事件使用的View.OnClickListener就是一個函數式接口。

複製代碼
public class View implements Drawable.Callback, KeyEvent.Callback,
    AccessibilityEventSource {
    ...
    
    public interface OnClickListener {
        void onClick(View v);
    }
    ...
}
複製代碼

那究竟什麼樣的接口是函數式接口呢?

     函數式接口是隻有一個抽象方法的接口,用做表示lambda表達式的類型。 好比Java標準庫中的java.lang.Runnable和java.util.Comparator都是典型的函數式接口。java 8提供 @FunctionalInterface做爲註解,這個註解是非必須的,只要接口符合函數式接口的標準(即只包含一個方法的接口),虛擬機會自動判斷,但最好在接口上使用註解@FunctionalInterface進行聲明,以避免團隊的其餘人員錯誤地往接口中添加新的方法。舉例以下:

複製代碼
@FunctionalInterface
public interface Runnable { void run(); }

public interface Callable<V> { V call() throws Exception; }

public interface ActionListener { void actionPerformed(ActionEvent e); }

public interface Comparator<T> { 
    int compare(T o1, T o2); 
    
    boolean equals(Object obj); 
}
複製代碼

     注意最後這個Comparator接口。它裏面聲明瞭兩個方法,貌似不符合函數接口的定義,但它的確是函數接口。這是由於equals方法是Object的,全部的接口都會聲明Object的public方法——雖然大可能是隱式的。因此,Comparator顯式的聲明瞭equals不影響它依然是個函數接口。

     Java中的lambda沒法單獨出現,它須要一個函數式接口來盛放,lambda表達式方法體其實就是函數接口的實現。即Lambda表達式不能脫離目標類型存在,這個目標類型就是函數式接口,看下面的例子: 

String []datas = new String[] {"peng","zhao","li"};
Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
Arrays.sort(datas,comp);
Stream.of(datas).forEach(param -> {System.out.println(param);}); 

Lambda表達式被賦值給了comp函數接口變量。

你能夠用一個lambda達式爲一個函數接口賦值:

Runnable r1 = () -> {System.out.println("Hello Lambda!");};

 而後再賦值給一個Object:

Object obj = r1;

但卻不能這樣幹:

 Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!

必須顯式的轉型成一個函數接口才能夠:

Object o = (Runnable) () -> { System.out.println("hi"); }; // correct

一個lambda表達式只有在轉型成一個函數接口後才能被當作Object使用。因此下面這句也不能編譯:

 System.out.println( () -> {} ); //錯誤! 目標類型不明

必須先轉型:

System.out.println( (Runnable)() -> {} ); // 正確

假設你本身寫了一個函數接口,長的跟Runnable如出一轍:

@FunctionalInterface
public interface MyRunnable {
    public void run();
}

那麼

Runnable r1 =    () -> {System.out.println("Hello Lambda!");};
MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};

都是正確的寫法。這說明一個lambda表達式能夠有多個目標類型(函數接口),只要函數匹配成功便可。但需注意一個lambda表達式必須至少有一個目標類型。

JDK預約義了不少函數接口以免用戶重複定義。最典型的是Function:

@FunctionalInterface
public interface Function<T, R> {  
    R apply(T t);
}

這個接口表明一個函數,接受一個T類型的參數,並返回一個R類型的返回值。另外一個預約義函數接口叫作Consumer,跟Function的惟一不一樣是它沒有返回值。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

還有一個Predicate,用來判斷某項條件是否知足。常常用來進行篩濾操做:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

綜上所述,一個lambda表達式其實就是定義了一個匿名方法,只不過這個方法必須符合至少一個函數接口。

4、lambda表達式可以使用的變量

先舉例:

複製代碼
@Test
public void test1(){
    //將爲列表中的字符串添加前綴字符串
    String waibu = "lambda :";
    List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
    List<String>execStrs = proStrs.stream().map(chuandi -> {
        Long zidingyi = System.currentTimeMillis();
        return waibu + chuandi + " -----:" + zidingyi;
    }).collect(Collectors.toList());
    
    execStrs.forEach(System.out::println);
}
複製代碼

輸出:

lambda :Ni -----:1498722594781
lambda :Hao -----:1498722594781
lambda :Lambda -----:1498722594781

變量waibu :外部變量

變量chuandi :傳遞變量

變量zidingyi :內部自定義變量

     lambda表達式能夠訪問給它傳遞的變量,訪問本身內部定義的變量,同時也能訪問它外部的變量。不過lambda表達式訪問外部變量有一個很是重要的限制:變量不可變(只是引用不可變,而不是真正的不可變)。

當在表達式內部修改waibu = waibu + " ";時,IDE就會提示你:

Local variable waibu defined in an enclosing scope must be final or effectively final

編譯時會報錯。由於變量waibu被lambda表達式引用,因此編譯器會隱式的把其當成final來處理

之前Java的匿名內部類在訪問外部變量的時候,外部變量必須用final修飾。如今java8對這個限制作了優化,能夠不用顯示使用final修飾,可是編譯器隱式當成final來處理。

5、lambda表達式做用域

整體來講,Lambda表達式的變量做用域與內部類很是類似,只是條件相對來講,放寬了些,之前內部類要想引用外部類的變量,必須像下面這樣 

複製代碼
final String[] datas = new String[] { "peng", "Zhao", "li" };
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();
複製代碼

將變量聲明爲final類型的,如今在Java 8中能夠這樣寫代碼

複製代碼
String []datas = new String[] {"peng","Zhao","li"};
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(datas);
    }
}).start();
複製代碼

也能夠這樣寫:

new Thread(() -> System.out.println(datas)).start();

     看了上面的兩段代碼,可以發現一個顯著的不一樣,就是Java 8中內部類或者Lambda表達式對外部類變量的引用條件放鬆了,不要求強制的加上final關鍵字了,可是Java 8中要求這個變量是effectively final。What is effectively final?

Effectively final就是有效只讀變量,意思是這個變量能夠不加final關鍵字,可是這個變量必須是隻讀變量,即一旦定義後,在後面就不能再隨意修改,以下代碼會編譯出錯

String []datas = new String[] {"peng","Zhao","li"};
datas = null;
new Thread(() -> System.out.println(datas)).start();

Java中內部類以及Lambda表達式中也不容許修改外部類中的變量,這是爲了不多線程狀況下的race condition。

6、lambda表達式中的this概念

在lambda中,this不是指向lambda表達式產生的那個對象,而是聲明它的外部對象。

例如:

複製代碼
package com.demo;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class WhatThis {
    
    public void whatThis(){
        //轉全小寫
         List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
        List<String> execStrs = proStrs.stream().map(str -> {
             System.out.println(this.getClass().getName());
             return str.toLowerCase();
        }).collect(Collectors.toList());
        
        execStrs.forEach(System.out::println);
    }
    
    public static void main(String[] args) {
        WhatThis wt = new WhatThis();
        wt.whatThis();
    }

}
複製代碼

輸出:

com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda

 

分類: java8
3
0
« 上一篇: Java多線程(九)—— interrupt()和線程終止方式
» 下一篇: redis學習(九)——數據持久化
</div>
相關文章
相關標籤/搜索