JDK8入門
JDK8默認最大的特性應該就是Lambda表達式了吧。先上線幾個Lambda表達式進行體驗一下。
代碼我托管于GitHub社區:https://github.com/WeidanLi/Java-jdk8-demo
準備
為了測試,我們新建一個蘋果,圍繞著蘋果開來展開需求的實現。蘋果具有兩個屬性,一個顏色和一個重量。我們可以通過集合+我們自己的POJO類來實現。
實例是JDK8實戰中的例子,如有侵犯,請告知刪除。
package cn.liweidan.jdk8.pojo;
/**
* <p>Desciption:</p>
* CreateTime : 2017/6/2 下午2:55
* Author : Weidan
* Version : V1.0
*/
public class Apple {
private String color;
private int wight;
public Apple(String color, int wight) {
this.color = color;
this.wight = wight;
}
public Apple() {
}
public Apple(int wight) {
this.wight = wight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getWight() {
return wight;
}
public void setWight(int wight) {
this.wight = wight;
}
@Override
public String toString() {
return "Apple{" +
"color='" + color + '\'' +
", wight=" + wight +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Apple apple = (Apple) o;
if (wight != apple.wight) return false;
return color != null ? color.equals(apple.color) : apple.color == null;
}
@Override
public int hashCode() {
int result = color != null ? color.hashCode() : 0;
result = 31 * result + wight;
return result;
}
}
一、Predicate接口
Predicate接口中定義了一個需要實現的方法test(T t)方法,還有JDK8特有的幾個已經實現了的默認方法,打開Predicate里有一個注解,@FunctionalInterface,代表這個接口只能擁有一個未實現的方法,是一個函數接口。
現在,我們需要在我們的一個蘋果集合中,拿出重量大于100的蘋果。
如果是以前,那么我們就需要通過遍歷一個集合,拿出來符合條件的蘋果,然后進行返回。
public static List<Apple> filterApple(List<Apple> appleList){
List<Apple> apples = new ArrayList<>();
for (Apple apple : appleList) {
if(apple.getWight() > 100){
apples.add(apple);
}
}
return apples;
}
這種方式,可以實現現在的需求,但是當用戶的需求改了以后,比如找出紅色的蘋果,那我們就需要重寫里面的if方法的條件。不夠靈活。
JDK8引入Predicate接口以后,我們就可以通過把這個條件進行封裝,把經常改變需求的if里面的判斷語句進行封裝,變成Predicate接口,在調用filterApple的時候將條件傳入,即可實現需求的變更。
JDK8的方法可以這么寫:
/**
Predicate<T>接口:默認調用test方法,返回boolean值,傳遞的參數只有一個。
在需要調用Lambeda表達式返回boolean的時候使用。
*/
public static List<Apple> filterApple(List<Apple> appleList, Predicate<Apple> predicate){
List<Apple> apples = new ArrayList<>();
for (Apple apple : appleList) {
if(predicate.test(apple)){
apples.add(apple);
}
}
return apples;
}
Predicate即可通過Lambda表達式傳入需要的條件,即可實現可變的過濾需求。
public static void main(String[] args) {
List<Apple> appleList = Arrays.asList(new Apple("RED", 80),
new Apple("GREEN", 100),
new Apple("BLACK", 150));
// 重量大于100
List<Apple> filterApple = filterApple(appleList, apple -> apple.getWight() > 100);
// 紅色的蘋果
filterApple = filterApple(appleList, apple -> apple.getColor().equals("RED"));
}
可以看到,我們已經可以把我們需要的蘋果篩選出來了。
這個方法的調用,就是通過傳入一個Predicate<Apple>實現,然后把apple.getWight() > 100返回給test(Apple apple)方法,test方法再進行返回給if語句,從而拿到我們需要的蘋果,是不是比以前的方法簡單。
所以當我們有個需求是需要可變的返回值boolean得需求的時候,即可使用Predicate接口。后面的JDK8的集合流中的filter()也是通過傳入Predicate拿到boolean值進行篩選的。
代碼位置:cn.liweidan.jdk8.PredicateInterface中
二、Consumer<T>接口
Consumer接口也是個函數式接口,只有該接口只含有一個未實現的accept(T t)方法。返回值是void,用于封裝在日常生活中沒有返回值情況的代碼,比如遍歷一個集合并且打印每個元素,我們即可通過Consumer接口實現一個forEach方法。接收一個集合并且使用Lambda書寫需要對集合的操作,在Consumer參數中即可拿到每一個集合中的元素進行操作。
/**
* Consumer<T>提供了一個accept方法,返回void類型。
*/
public static <T>void forEach(List<T> list, Consumer<T> s){
for (T t : list) {
s.accept(t);
}
}
調用該forEach方法:
@Test
public void test01(){
forEach(Arrays.asList("Lambde", "test", "stream"),
s -> {
System.out.println(s);
});
}
Consumer意為消費者,在上面例子中,即拿到集合中所有的元素進行消費。
代碼位置:cn.liweidan.jdk8.ConsumerInterface
三、Function<T, R>接口
Function就好玩了,可以自定義傳入的類型以及傳出的類型。T表示傳入的類型,R表示傳出的類型。
Function接口只有一個未實現的R apply(T t)
方法,用于傳入一個T類型的值,而傳出一個R類型的值。比如我們現在需要把一組字符串的長度全部輸出出來。在以往我們就需要通過遍歷所有的元素,然后用一個集合去封裝,在遍歷中一個一個取出長度放入我們新建的集合當中去,然后將這個集合進行返回。
然而使用Function方法的時候,我們就可以很自由的進行取值。
現在我們來寫一個map方法,用于取出集合中元素某一個屬性的所有值并且進行返回。
/**
* Function<T, R>, T表示傳入的類型,R表示返回的類型。即傳入T類型的參數,返回R類型的參數
* @param list
* @param function
* @param <T>
* @param <R>
* @return
*/
public static <T,R> List<R> map(List<T> list, Function<T, R> function){
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(function.apply(t));
}
return result;
}
@Test
public void test01(){
List<String> stringList = Arrays.asList("lambda", "test", "javascript");
List<Integer> map = map(stringList, s -> s.length());
System.out.println(map);
}
代碼位置:cn.liweidan.jdk8.FunctionDemo
運行的時候JDK8可以通過傳入的集合的泛型是String屬性,從而把String賦予給T,然后再根據我們需要取值的結果的屬性值,在這里s.length()
取出來的值是Integer類型的。所以R的類型就是Integer類型。把T和R帶入Function中的兩個泛型值,我們就可以發現其實就是和我們以前寫的遍歷取值是一樣的,只不過是現在的方式可以更靈活。這里得益于Lambda表達式,通過() -> expression
或者() -> {statement;}
來表達我們需要的需求。
四、Lambda表達式
Lambda表達式的歷史就不說了。這里說的是Lambda的格式,Lambda表達式的前半部分(Apple a, Apple b)
表示傳入給Lambda的參數值,也就是相當于我們方法中的參數。這個不難理解吧。然而后半部分就比較有講究了。
后半部分{}
或者直接書寫"result"
,用于表示返回值。在這里需要注意的是{}
和"result"
的區別,{}
表示書寫Java語句,也就是我們日常寫的代碼,比如上面的forEach方法中,我們就是通過s -> {System.out.println(s);}
來寫Java代碼的,但是如果我們直接返回值,就不需要寫花括號,只需要直接把返回值放在后部分即可。如Predicate接口中的apple -> apple.getWight() > 100
后半部分直接用返回值返回boolean值。像函數式接口都支持Lambda的書寫方式。
比如:
() -> return "result";
和() -> {"result"}
均是錯誤的寫法,應該寫成() -> "result"
或者() -> {return "result";}
對于Lambda表達式我是這么去理解的,前半部分就是傳入的值,后半部分就是我們需要執行的語句,函數式接口就相當于把我們需要執行的語句放入Lambda的后半部分一樣。
這時候我們就需要來一個例子看看了,并且講解函數式接口。
五、自定義函數式接口。
我們現在有一個需求,我需要有個方法可以自定義我們讀取文件的內容的方法,但是前后的try-catch均讓代碼幫我封裝了。我只要關心我讀取的代碼就可以了。
這里就涉及到了環繞的函數式編程了,但是這并不難,我們先來看看傳統的方法:
public static String processFileOld(BufferedReaderProcess b){
StringBuilder str = new StringBuilder();
try(BufferedReader br =
new BufferedReader(
new FileReader("/Users/liweidan/Java/workspace/Java-jdk8-demo/src/main/java/cn/liweidan/jdk8/Lambda/LambdaDemo01.java"))){
str.append(br.readLine());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return str.toString();
}
這樣可以實現讀取指定文件的一行,但是當我們需要讀取文件的第二行的時候怎么辦,我們又需要修改這段代碼。
所以我們應該把讀取的內容給封裝起來。首先我們先建一個函數式接口BufferedReaderProcess接收一個BufferedReader,然后對BufferedReader的動作進行操作。
package cn.liweidan.jdk8.Lambda.Demo02;
import java.io.BufferedReader;
import java.io.IOException;
/**
* <p>Desciption:讀取流的函數式編程,把行為參數化</p>
* CreateTime : 2017/6/2 下午3:33
* Author : Weidan
* Version : V1.0
*/
@FunctionalInterface // 注意這個注解
public interface BufferedReaderProcess {
String process(BufferedReader b) throws IOException;
}
@FunctionalInterface注解,用于只是該接口是函數式接口,如果沒有該注釋也可以編譯通過,但是編譯器就不能判斷他只能擁有一個未實現的方法。
這時候我們可以編寫一個方法,用于接收BufferedReaderProcess,前后包裝流的常用動作,包括流的獲取以及流的關閉,這里我們使用JDK7的新特性,自動關閉流。
@Test
public void test01(){
String br = LambdaDemo02.processFile(b -> b.readLine());
System.out.println(br);
br = LambdaDemo02.processFile(b -> b.readLine() + b.readLine() + b.readLine());
System.out.println(br);
}
/**
* 將讀取文件的動作封裝成參數
* @param b
* @return
*/
public static String processFile(BufferedReaderProcess b){
try(BufferedReader br =
new BufferedReader(
new FileReader("/Users/liweidan/Java/workspace/Java-jdk8-demo/src/main/java/cn/liweidan/jdk8/Lambda/LambdaDemo01.java"))){
return b.process(br);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
代碼位置:cn.liweidan.jdk8.Lambda.LambdaDemo02
六、Lambda的方法引用
當我們需要使用Lambda表達式去調用一個對象的方法的時候,可以通過::
來調用,比如調用String的compareToIgnoreCase方法,可以寫成String:: compareToIgnoreCase
,又可以減少代碼量了。方法調用可以針對構造方法、普通方法以及靜態方法進行調用。下面給出幾個需求的實現代碼。
public class MethodQuoteDemo01 {
@Test
public void test(){
List<String> list = Arrays.asList("a", "b", "A", "B");
list.sort(String::compareToIgnoreCase);// 調用compareToIgnoreCase進行比較
System.out.println(list);
}
}
package cn.liweidan.jdk8.MethodQuote;
import cn.liweidan.jdk8.pojo.Apple;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* <p>Desciption:構造方法引用Demo</p>
* CreateTime : 2017/6/2 下午7:46
* Author : Weidan
* Version : V1.0
*/
public class MethodQuoteDemo02 {
@Test
public void test01(){
/**
* 空構造器
*/
Supplier<Apple> c1 = Apple::new;
Apple apple = c1.get();
System.out.println(apple);
}
@Test
public void test02(){
/**
* Apple存在只需要傳遞一個Integer參數的構造器
*/
Function<Integer, Apple> c2 = Apple::new;
Apple apply = c2.apply(100);
System.out.println(apply);
}
/**
* 使用map批量創建指定重量的蘋果
*/
@Test
public void test03(){
List<Integer> integers = Arrays.asList(7, 3, 9, 10);
List<Apple> appleList = map(integers, Apple::new);
System.out.println(appleList);
}
public static <T, R>List<R> map(List<T> wights, Function<T, R> function){
List<R> res = new ArrayList<>();
for (T t : wights) {
res.add(function.apply(t));
}
return res;
}
/**
* 調用getWight對蘋果進行按照重量進行排序
*/
@Test
public void test04(){
List<Apple> appleList = Arrays.asList(new Apple("RED", 80),
new Apple("GREEN", 100),
new Apple("BLACK", 150));
appleList.sort(Comparator.comparing(Apple::getWight));
System.out.println(appleList);
}
}