Java8 In Action-3.高效 Java 8 程式設計(一)
阿新 • • 發佈:2018-12-13
1.為改善可讀性和靈活性重構程式碼 改善程式碼的可讀性 三種簡單的重構,利用Lambda表示式、方法引用以及Stream改善程式程式碼的可讀性: 重構程式碼,用Lambda表示式取代匿名類 用方法引用重構Lambda表示式 用Stream API重構命令式的資料處理
從匿名類到 Lambda 表示式的轉換 雖然將匿名類轉為Lambda表示式程式碼會更簡潔,可讀性更好,但是某些情況下,將匿名類轉換為Lambda表示式可能是一個比較複雜的過程。首先,匿名類和Lambda表示式中的this和super的含義是不同的。在匿名類中, this代表的是類自身,但是在Lambda中,它代表的是包含類。其次,匿名類可以遮蔽包含類的變數,而Lambda表示式不 能(它們會導致編譯錯誤),譬如下面這段程式碼:
int a = 10;
Runnable r1 = () -> {
int a = 2; //編譯錯誤
System.out.println(a);
};
Runnable r2 = new Runnable(){
public void run(){
int a = 2; //一切正常
System.out.println(a);
}
};
最後,在涉及過載的上下文裡,將匿名類轉換為Lambda表示式可能導致最終的程式碼更加晦澀。實際上,匿名類的型別是在初始化時確定的,而Lambda的型別取決於它的上下文。
interface Task{
public void execute();
}
public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ a.execute(); }
/**
* 匿名類的方式
*/
doSomething(new Task() {
public void execute() {
System.out.println("Danger danger!!");
}
});
/**
*將這種匿名類轉換為Lambda表示式時,就導致了一種晦澀的方法呼叫,因為Runnable和Task都是合法的目標型別
*/
//doSomething(Runnable) 和doSomething(Task)都匹配該型別
doSomething(() -> System.out.println("Danger danger!!"));
//可以對Task嘗試使用顯式的型別轉換來解決這種模稜兩可的情況:
doSomething((Task)() -> System.out.println("Danger danger!!"));
public class Fruit {
protected String color;
protected Integer weight;
public Fruit() { }
public Fruit(String color) {
this.color = color;
}
public Fruit(Integer weight) {
this.weight = weight;
}
public Fruit(String color, Integer weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Fruit{" +
"color='" + color + '\'' +
", weight=" + weight +
'}';
}
}
public class Apple extends Fruit {
public Apple() { }
public Apple(String color) {
super(color);
}
public Apple(Integer weight) {
super(weight);
}
public Apple(String color, Integer weight) {
super(color, weight);
}
public FruitWeightLevel getWeightLevel(){
if (this.weight <= 10) return FruitWeightLevel.LEVEL1;
else if (this.weight <= 20) return FruitWeightLevel.LEVEL2;
else return FruitWeightLevel.LEVEL3;
}
}
public class Orange extends Fruit{
public Orange() { }
public Orange(String color) {
super(color);
}
public Orange(Integer weight) {
super(weight);
}
public Orange(String color, Integer weight) {
super(color, weight);
}
}
public enum FruitWeightLevel {
LEVEL1(1,"[0,10]"),
LEVEL2(2,"[11,20]"),
LEVEL3(3,"[21,∞]");
private int level;
private String range;
FruitWeightLevel(int level, String range) {
this.level = level;
this.range = range;
}
}
/**
* 從Lambda 表示式到方法引用的轉換
* Lambda表示式非常適用於需要傳遞程式碼片段的場景。不過,為了改善程式碼的可讀性,也請
* 儘量使用方法引用。因為方法名往往能更直觀地表達程式碼的意圖。
*/
List<Apple> list = Arrays.asList(new Apple("g", 8), new Apple("r", 12), new Apple("g", 21), new Apple("g", 16));
//求出不同的重量級別的所有蘋果 [0,10]~LEVEL1 [11,20]~LEVEL2 >20~LEVEL3
//1.將分組邏輯直接以Lambda表示式傳遞
Map<FruitWeightLevel, List<Apple>> map = list.stream().collect(Collectors.groupingBy(a -> {
Integer weight = a.getWeight();
if (weight <= 10) return FruitWeightLevel.LEVEL1;
else if (weight <= 20) return FruitWeightLevel.LEVEL2;
else return FruitWeightLevel.LEVEL3;
}));
map.forEach((k,v) -> System.out.println(k + "=" + v));
System.out.println("+++++++++++++++++++");
//2.將Lambda表示式轉為方法引用,但需要在Apple類中新增分組方法
Map<FruitWeightLevel, List<Apple>> map1 = list.stream().collect(Collectors.groupingBy(Apple::getWeightLevel));
map.forEach((k,v) -> System.out.println(k + "=" + v));
System.out.println("+++++++++++++++++++");
/**
* 從命令式的資料處理切換到Stream
* 將所有使用迭代器這種資料處理模式處理集合的程式碼都轉換成Stream API的方式。
* Stream API能更清晰地表達資料處理管道的意圖。除此之外,通過短路和延遲載
* 入以及現代計算機的多核架構,我們可以對Stream進行優化。
* 但是將命令式的程式碼結構轉換為Stream API的形式是個困難的任務,因為你需要考慮
* 控制流語句,比如break、continue、return,並選擇使用恰當的流操作。
*/
//篩選出重量大於10的所有蘋果的重量分別是多少
//以前的方式
List<Integer> weightList = new ArrayList<>();
for (Apple a:list){
if (a.getWeight() > 10){
weightList.add(a.getWeight());
}
}
System.out.println(weightList);
//現在的方式
List<Integer> weightList2 = list.stream().filter(a -> a.getWeight() > 10).map(Apple::getWeight).collect(Collectors.toList());
System.out.println(weightList2);
System.out.println("======================");
/**
* 增加程式碼的靈活性
* 1.採用函式介面
* 沒有函式介面,你就無法使用Lambda表示式。因此,你需要在程式碼中引入函式介面
* 在什麼情況下使用? 有條件的延遲執行和環繞執行
* 2.有條件的延遲執行
* 我們經常看到這樣的程式碼,控制語句被混雜在業務邏輯程式碼之中。典型的情況包括進行安全性檢查以及日誌輸出。
* 3. 環繞執行
*/
2.使用Lambda 重構面向物件的設計模式
public class Validator {
private final ValidationStrategy strategy;
public Validator(ValidationStrategy v) {
this.strategy = v;
}
public boolean validate(String s) {
return strategy.execute(s);
}
}
public class IsNumeric implements ValidationStrategy {
@Override
public boolean execute(String s){
return s.matches("\\d+");
}
}
public class IsAllLowerCase implements ValidationStrategy {
@Override
public boolean execute(String s) {
return s.matches("[a-z]+");
}
}
package com.h.java8;
import java.util.Objects;
/**
* <br>責任鏈模式示例</br>
*/
public abstract class ProcessingObject<T> {
protected ProcessingObject<T> successor;
public void setSuccessor(ProcessingObject<T> successor) {
this.successor = successor;
}
/**
* 定義工作處理的框架
* 不同的處理物件可以通過繼承ProcessingObject類,提供handleWork方法來進行建立。
* @param input
* @return
*/
public T handle(T input){
T r = handleWork(input);
if (Objects.nonNull(successor)){
return successor.handleWork(r);
}
return r;
}
protected abstract T handleWork(T input);
}
public class HeaderTextProcessing extends ProcessingObject<String> {
@Override
public String handleWork(String text){
return "From Raoul, Mario and Alan: " + text;
}
}
public class SpellCheckerProcessing extends ProcessingObject<String> {
@Override
public String handleWork(String text){
return text.replaceAll("labda", "lambda");
}
}
public class FruitFactory {
public static Fruit createFruit(String name){
switch (name){
//case "apple":return new Apple("g",1);
case "apple":return new Apple();
//case "orange":return new Orange("y",1);
case "orange":return new Orange();
default:throw new RuntimeException("No such fruit " + name);
}
}
}
public class FruitFactory2 {
private static final Map<String,Supplier<Fruit>> map = new HashMap<>(2);
/**
* 建立一個Map,將水果名對映到對應的建構函式
*/
static {
map.put("apple",Apple::new);
map.put("orange",Orange::new);
}
public static Fruit createFruit(String name){
Supplier<Fruit> supplier = map.get(name);
if (Objects.isNull(supplier)) {
throw new RuntimeException("No such fruit " + name);
}
return supplier.get();
}
}
package com.h;
import com.h.java8.*;
import java.util.function.Function;
import java.util.function.UnaryOperator;
public class MyTest {
public static void main(String[] args) throws Exception {
/**
* 示Lambda表示式是如何另闢蹊徑解決設計模式原來試圖解決的問題的?
* 1.策略模式
* 策略模式代表瞭解決一類演算法的通用解決方案,你可以在執行時選擇使用哪種方案。
* 策略模式包含三部分內容:
* 一個代表某個演算法的介面(它是策略模式的介面)。
* 一個或多個該介面的具體實現,它們代表了演算法的多種實現(比如,實體類Concrete-trategyA或者ConcreteStrategyB)。
* 一個或多個使用策略物件的客戶。
*/
/**
* 應用場景示例:驗證輸入的內容是否根據標準進行了恰當的格式化(比如只包含小寫字母或數字)
*/
/**
* 傳統的方式
*/
Validator validator = new Validator(new IsNumeric());
boolean b1 = validator.validate("aaaa");
System.out.println(b1);
Validator validator1 = new Validator(new IsAllLowerCase());
boolean b2 = validator1.validate("bbb");
System.out.println(b2);
/**
* 使用Lambda表示式
* 不需要宣告新的類來實現不同的策略,通過直接傳遞Lambda表示式就能達到同樣的目的,並且還更簡潔
* ValidationStrategy是一個函式介面,與Predict<String>具有同樣的函式描述
*/
Validator validator2 = new Validator(s -> s.matches("\\d+"));
boolean b3 = validator2.validate("aaaa");
System.out.println(b3);
Validator validator3 = new Validator((String s) -> s.matches("[a-z]+"));
boolean b4 = validator3.validate("bbbb");
System.out.println(b4);
System.out.println("++++++++++++++++");
/**
* 模板方法
* 模板方法模式在你“希望使用這個演算法,但是需要對其中的某些行進行改進,才能達到希望的效果”時是非常有用的
* 建立演算法框架,讓具體的實現插入某些部分,你想要插入的不同演算法元件可以通過Lambda表示式或者方法引用的方式實現。
*/
/**
*觀察者模式
*某些事件發生時(比如狀態轉變),如果一個物件(通常我們稱之為主題)需要自動地通知其他多個物件(稱為觀察者),就會採用該方案。
*/
/**
* 那麼,是否我們隨時隨地都可以使用Lambda表示式呢?答案是否定的!
* 演示的例子中,,Lambda適配得很好,那是因為需要執行的動作都很簡單,因此才能很方便地消除僵化程式碼。
* 但是,觀察者的邏輯有可能十分複雜,它們可能還持有狀態,抑或定義了多個方法,諸如此類。在這些情形下,你還是應該繼續使用類的方式。
*/
/**
* 責任鏈模式
* 責任鏈模式是一種建立處理物件序列(比如操作序列)的通用方案。一個處理物件可能需要在完成一些工作之後,
* 將結果傳遞給另一個物件,這個物件接著做一些工作,再轉交給下一個處理物件,依此類推。
* 通常,這種模式是通過定義一個代表處理物件的抽象類來實現的,在抽象類中會定義一個字
* 段來記錄後續物件。一旦物件完成它的工作,處理物件就會將它的工作轉交給它的後繼。
*/
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);//將兩個處理物件連結起來
String result = p1.handle("Aren't labda really sexy?!!");
System.out.println(result);
/**
* 使用Lambda表示式
* 將處理物件作為函式的一個例項,或者更確切地說作為UnaryOperator<String>的一個例項。
* 為了連結這些函式,你需要使用andThen方法對其進行構造
*/
UnaryOperator<String> headerProcessing =
(String text) -> "From Raoul, Mario and Alan: " + text;
UnaryOperator<String> spellCheckerProcessing =
(String text) -> text.replaceAll("labda", "lambda");
Function<String, String> pipeline =
headerProcessing.andThen(spellCheckerProcessing);
String result1 = pipeline.apply("Aren't labda really sexy?!!");
System.out.println(result1);
System.out.println("+++++++++++++++++++");
/**
* 工廠模式
* 使用工廠模式,你無需向客戶暴露例項化的邏輯就能完成物件的建立。
*/
Fruit orange = FruitFactory.createFruit("orange");
System.out.println(orange);
/**
* 使用Lambda表示式
* 如果工廠方法createFruit需要接收多個傳遞給產品構造方法的引數,這種方式的擴充套件性不是很好。
* 你不得不提供不同的函式介面,無法採用之前統一使用一個簡單介面的方式。
* 可以自定義函式式介面,但這會使原本的設計變得更復雜
*/
Fruit orange1 = FruitFactory2.createFruit("orange");
System.out.println(orange1);
}
}
3.測試Lambda 表示式
4.除錯 除錯有問題的程式碼時,程式設計師的兵器庫裡有兩大老式武器,分別是: 檢視棧跟蹤 輸出日誌