為什么要學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
- BiPredicate
(T,?U) -> boolean - BiConsumer
(T,?U) -> void - BiFunction
(T,?U) -> R
注意:Java中的數據類型可分為引用類型(Byte、Integer、List等)和原始類型(byte、int、float、char等)。Java中提供裝箱和拆箱的機制,例如裝箱就是把原始類型包裝后放到堆上,所以裝箱后需要更多的內存,所以Java8為大多數函數式接口提供了相應的版本來避免這個問題,例如:
IntPredicate、LongPredicate、DoublePredicate
IntConsumer、LongConsumer、DoubleConsumer
方法引用 ::
方法引用是什么 ?
方法引用是用來直接訪問類或者實例的已經存在的方法或者構造方法。方法引用提供了一種引用而不執行方法的方式,它需要由兼容的函數式接口構成的目標類型上下文。計算時,方法引用會創建函數式接口的一個實例。
當Lambda表達式中只是執行一個方法調用時,不用Lambda表達式,直接通過方法引用的形式可讀性更高一些。
- 作用
方法引用的唯一用途是支持Lambda的簡寫。
方法引用提高了代碼的可讀性,也使邏輯更加清晰。 -
組成
使用::操作符將方法名和對象或類的名字分隔開。
“::” 是域操作符(也可以稱作定界符、分隔符)。
- 分類
- 靜態方法引用
組成語法格式:ClassName::staticMethodName
String::valueOf等價于lambda表達式 (s) ->String.valueOf(s)
Math::pow等價于lambda表達式(x, y) -> Math.pow(x, y);
- 實例方法引用
這種語法與用于靜態方法的語法類似,只不過這里使用的是對象引用而不是類名.
實例方法引用又分以下三種類型
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 的個數。
- 紅色框是生命開始的地方,負責創建一個 Stream 實例;
- 綠色框是賦予Stream靈魂的地方,把一個 Stream 轉換成另外一個 Stream,即包含所有 nums 變量的 Stream ,經過綠框的 filter 方法以后,重新生成了一個過濾掉原 nums列表所有為null的 Stream ;
-
藍色框中的語句是豐收的地方,把 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 + "*"));