當前軟件行業的氣候正在不斷地變化。數據量的爆炸式增長,導致程序員在開發時不得不去面對存在的各種效率問題,並希望利用多核計算機或計算集羣來有效地處理。這意味着需要使用並行處理,而曾經的java對於並行的支持並不好,這使得其不得不進行進化,以適應當前的行業氣候。
Java 8中開發出並行和編寫更簡潔通用代碼的功能,下面我們一起學習。
一、java8有哪些變化
1.1 流處理
Java 8在 java.util.stream 中添加了一個Stream API。通過這個流處理有兩個較爲直觀的好處:
1)把這樣的流變成那樣的流。
什麼意思?簡單舉個小例子,在Stream提供了很多方法,此處以mapToInt舉例:
IntStream mapToInt(ToIntFunction<? super T> mapper);
用法:
public static void main(String[] args) {
// 有如下的字符串列表
List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
// 打印出每個字符串的長度
list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
// 經過Lambda替換方法後,儘量的簡化代碼
list.stream().mapToInt(String::length).forEach(System.out::println);
}
結果:
4
3
3
6
6
在上面的代碼我們看到只通過一行代碼就可以完成操作,最大化的簡化了代碼。比傳統的for循環更容易查看。
2)集合處理。
幾乎每個Java應用都會製造和處理集合。但集合用起來並不總是那麼理想。比方說,你需要從一個列表中篩選金額較高的交易,然後按貨幣分組。你需要寫一大堆套路化的代碼來實現這個數據處理命令。
通過Stream API 可以簡單的完成上面的步驟,這裏舉個例子:
Map<Currency, List<Transaction>> transactionsByCurrencies =
transactions.stream()
.filter((Transaction t) -> t.getPrice() > 1000)//篩選金額較高的
.collect(groupingBy(Transaction::getCurrency));//按照金額分組
相比於使用集合Collection,使用Stream API我們能獲得兩點直觀的優點:
1)不需要自己去通過Collection API去迭代處理複雜的過程(外部迭代),使用流式處理的內部迭代,不需要操心循環的事情。
2)如果數據量非常龐大,那麼使用集合的方式,處理將會非常慢;引入多線程將會是原本就複雜的集合迭代變得更加困難,使用Steam API可以較好的解決這一問題,看3)中的介紹。
3)在不同cpu上執行Stream操作,不需要Thread。
Stream提供了並行操作,通過下面的例子簡單看下:
public static void main(String[] args) {
// 有如下的字符串列表
List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
// 打印出每個字符串的長度
//list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
// 經過Lambda替換方法後,儘量的簡化代碼
long startTime = System.currentTimeMillis();
list.stream().mapToInt(String::length).forEach(l -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(l);
});
System.out.println("耗時:" + (System.currentTimeMillis() - startTime));
}
結果:
4
3
3
6
6
耗時:5104
這時我們添加上並行操作parallel,代碼如下:
public static void main(String[] args) {
// 有如下的字符串列表
List<String> list = Arrays.asList("adas", "ada", "gfg", "asdasd", "adsasd");
// 打印出每個字符串的長度
//list.stream().mapToInt(s->s.length()).forEach(l-> System.out.println(l));
// 經過Lambda替換方法後,儘量的簡化代碼
long startTime = System.currentTimeMillis();
list.stream().mapToInt(String::length).parallel().forEach(l -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(l);
});
System.out.println("耗時:" + (System.currentTimeMillis() - startTime));
}
結果:
3
6
4
3
6
耗時:1096
總體耗時基本是五分之一,或者可以使用parallelStream():
list.parallelStream().mapToInt(String::length).forEach....
關於具體的內容後面會詳細講解。
1.2 用行爲參數化把代碼(方法)傳遞給方法
Java 8增加了把方法(你的代碼)作爲參數傳遞給另一個方法的能力。我們把這一概念稱爲行爲參數化。
下面會簡單講解時什麼意思。
1.2.1 函數
編程語言中的函數一詞通常是指方法,尤其是靜態方法。
Java 8中新增了函數:值的一種新形式。
編程語言的本質就是操作值。按照傳統的編程語言歷史,我們以java舉例:例如int,long,double這些可以直接被作爲參數傳遞的值,都是一等值(一等公民);而還有很多涉及到結構的內容,例如方法和類等,不能作爲參數傳遞,這些被稱爲二等公民。
java8中增加了新的功能,將二等公民添加到運行時傳遞,則二等公民就成爲了一等公民。
1.2.1.1 方法和 Lambda 作爲一等公民
Java 8的設計者決定允許方法作爲值,讓編程更輕鬆。此外,讓方法作爲值也構成了其他若干Java 8功能(如 Stream )的基礎。
我們介紹的Java 8的第一個新功能是方法引用。
舉個例子,你想要篩選一個目錄中的所有隱藏文件。 File類裏面有一個叫作 isHidden 的方法。我們可以把它看作一個函數,接受一個 File ,返回一個布爾值。但要用它做篩選,你需要把它包在一個 FileFilter 對象裏,然後傳遞給 File.listFiles方法,如下所示:
public void test(){
File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isHidden();
}
});
}
在java8當中我們可以使用如下的方式:
public void test(){
File[] hiddenFiles = new File(".").listFiles(File::isHidden);
}
你已經有了函數 isHidden ,因此只需用Java 8的方法引用 :: 語法(即“把這個方法作爲值”)將其傳給 listFiles 方法,我們也開始用函數代表方法了。
當使用File::isHidden時,其實創建了一個方法引用,與對象引用類似,我們就可以將其作爲一等公民傳遞。
除了允許(命名)函數成爲一等值外,Java 8還體現了更廣義的將函數作爲值的思想,包括Lambda(匿名函數)。直白說,將匿名函數作爲一等值。
將匿名函數傳遞有什麼好處呢?寫代碼的時候相信都會遇到一種情況,當你只需要一個簡單的運算的時候,還需要去重新定義一個類或者方法嗎?如果沒有方便的類或者方法可用的話,lambda就能幫助你使語法更簡潔。
1.2.1.2 使用案例
假設有一個手機倉庫,裏面放了很多品牌的手機,包括蘋果、華爲等。現在我們需要篩選出華爲手機有多少。那麼需要些如下的代碼:
public List<Phone> brandFilter(){
List<Phone> phones = new ArrayList<>();
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if ("華爲".equals(phone.getBrand())){
results.add(phone);
}
}
return results;
}
如果又需要篩選黑色的手機,那麼還需要寫下面的方法:
public List<Phone> colorFilter(){
List<Phone> phones = new ArrayList<>();
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if ("黑色".equals(phone.getColor())){
results.add(phone);
}
}
return results;
}
這樣一來重複代碼就會很多了,如果我們使用java8,可以像下面這樣寫:
package com.cloud.bssp.java8.stream;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* @description: 挑選手機
* @author:weirx
* @date:2021/10/15 16:02
* @version:3.0
*/
public class ChoicePhone {
public static void main(String[] args) {
List<Phone> list = new ArrayList<>();
list.add(new Phone("蘋果", "黑色"));
list.add(new Phone("華爲", "黑色"));
List<Phone> blackPhones = phoneFilter(list, ChoicePhone::isBlack);
System.out.println(blackPhones.toString());
List<Phone> huaweiPhones = phoneFilter(list, ChoicePhone::isHuawei);
System.out.println(huaweiPhones.toString());
}
@Data
static class Phone {
private String brand;
public Phone(String brand, String color) {
this.brand = brand;
this.color = color;
}
private String color;
}
/**
* 篩選華爲方法
*/
public static boolean isHuawei(Phone phone) {
return "華爲".equals(phone.getBrand());
}
/**
* 篩選黑色方法
*/
public static boolean isBlack(Phone phone) {
return "黑色".equals(phone.getColor());
}
/**
* 手機過濾方法,參數是手機list和Predicate<T>函數
*/
public static List<Phone> phoneFilter(List<Phone> phones, Predicate<Phone> p) {
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if (p.test(phone)) {
results.add(phone);
}
}
return results;
}
}
前面的代碼傳遞了方法 Apple::isGreenApple (它接受參數 Apple 並返回一個boolean )給 filterApples ,後者則希望接受一個 Predicate<Apple> 參數。
Java 8也會允許你寫 Function<Apple,Boolean>。
關於上面的代碼請同學們細細體會啊,一遍看不懂就多看幾遍。
前面就簡單介紹了lambda的作用,像我們前面的例子中,只是一個判斷的方法實在沒有必要單獨提供一個方法去維護,所以我們可以使用lambda的方式進行優化,就不需要單獨定義判斷方法isBlack和isHuawei了,使代碼更簡潔,如下所示:
List<Phone> blackPhones = phoneFilter(list, (Phone p) -> p.getColor().equals("黑色"));
List<Phone> huaweiPhones = phoneFilter(list, (Phone p) -> p.getBrand().equals("華爲"));
List<Phone> blackPhones = phoneFilter(list, (Phone p) ->
p.getColor().equals("黑色") || p.getBrand().equals("華爲")
);
1.3 默認方法
在java8之前的版本當中,接口被一個類實現,必須要求這個類實現該接口的全部方法,如果一個接口增加一個方法,那麼所有的實現類都需要增加這個方法的實現。那麼如何解決這個問題呢?
既然不想讓實現類自己實現這個方法,那麼只能由接口自己來實現了。這就給開發者提供了一個擴充接口的方式,而不會破壞現有的代碼。在java8中使用關鍵字default來表示這個關鍵點。
比如在java8當中,我們可以直接調用List接口中的sort方法:
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
這意味着 List 的任何實體類都不需要顯式實現 sort。
本篇主要針對java的變化有個簡單的認識,後續文章會逐漸深入到細節。
如果對您有幫助,給點個贊吧,感謝!!