1. 程式人生 > >Java8 In Action-3.高效 Java 8 程式設計(一)

Java8 In Action-3.高效 Java 8 程式設計(一)

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.除錯 除錯有問題的程式碼時,程式設計師的兵器庫裡有兩大老式武器,分別是:  檢視棧跟蹤  輸出日誌