• 《Java8实战》读书笔记07:Lambda 重构、测试和调试(设计模式实现)


    第8章 重构、测试和调试

    本章内容
    如何使用Lambda表达式重构代码
    Lambda表达式对面向对象的设计模式的影响
    Lambda表达式的测试
     如何调试使用Lambda表达式和Stream API的代码

    8.1 为改善可读性和灵活性重构代码

    8.1.1 改善代码的可读性

    为了确保你的代码能被其他人理解,有几个步骤可以尝试,比如确保你的代码附有良好的文档,并严格遵守编程规范。

    • 跟之前的版本相比较,Java 8的新特性也可以帮助提升代码的可读性:
    1. 使用Java 8,你可以减少冗长的代码,让代码更易于理解
    2. 通过方法引用Stream API,你的代码会变得更直观。这里我们会介绍三种简单的重构,利用Lambda表达式方法引用以及Stream改善程序代码的。
    • 可读性:
    1. 重构代码,用Lambda表达式取代匿名类
    2. 方法引用重构Lambda表达式
    3. Stream API重构命令式的数据处理

    8.1.2 从匿名类到 Lambda 表达式的转换

    注意:我们重构的目的是让代码更简洁直观,这也是我们要不要把匿名类换成Lambda的依据。(如果重构后,导致逻辑更绕,阅读成本增加就没必要了。)
    Lambda可用来简化匿名类的废话代码。(比如:核心代码就一句话,为了这一句,以前非得让我实例化一个匿名类再重写方法,太多无用功。)

    • 匿名类
    		Runnable r1 = new Runnable(){
    		    public void run(){
    		        System.out.println("Hello 匿名类");
    		    }
    		};
    		new Thread(r1).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • Lambda
    		Runnable r2 = () -> System.out.println("Hello Lambda");
    		new Thread(r2).start();
    
    • 1
    • 2
    1. Lambda表达式匿名类。对此保持清醒的认识,关于作用域this的问题就不言自明了。
    2. Lambda类型根据上下文决定。如果出现重载,可以显示转换类型。例子:
      (IntelliJ支持这种重构,能自动帮你检查)
    interface Task{ 
     public void execute(); 
    } 
    public static void doSomething(Runnable r){ r.run(); } 
    public static void doSomething(Task a){ a.execute(); }
    
    // 这样 doSomething 就知道自己是谁了
    doSomething((Task)() -> System.out.println("Danger danger!!"));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    8.1.3 从 Lambda 表达式到方法引用的转换

    这是一段Lambda实现的分组逻辑。将groupingBy内的业务代码提取出来改为方法引用可以让代码简洁意图清晰

            Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
                    .collect(
                            groupingBy(dish -> {
                                if (dish.getCalories() <= 400) return CaloricLevel.DIET;
                                else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                                else return CaloricLevel.FAT;
                            })
                    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Dish类中添加一个方法getCaloricLevel来实现groupingBy中的逻辑。

    public class Dish{
        // …
        public CaloricLevel getCaloricLevel(){
            if (this.getCalories() <= 400) return CaloricLevel.DIET;
            else if (this.getCalories() <= 700) return CaloricLevel.NORMAL;
            else return CaloricLevel.FAT;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    有了Dish::getCaloricLevel原来的代码就可以简化为如下效果:(取菜单流》按热量级别分组》收集到Map。代码简洁意图清晰,简直像读小说)

            Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
                    .collect(groupingBy(Dish::getCaloricLevel));
    
    • 1
    • 2

    JDK提供了大量现成工具:java.util.stream.Collectors 收集器、java.util.Comparator 比较器。《Java8实战》读书笔记04《Java8实战》读书笔记05 有相关介绍。

    8.1.4 从命令式的数据处理切换到 Stream

    命令式(传统)迭代方式:

            List<String> dishNames = new ArrayList<>();
            for(Dish dish: menu){
                if(dish.getCalories() > 300){
                    dishNames.add(dish.getName());
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Stream API隐式迭代:

            menu.parallelStream()
                    .filter(d -> d.getCalories() > 300)
                    .map(Dish::getName)
                    .collect(toList());
    
    • 1
    • 2
    • 3
    • 4

    哪个好阅读一目了然。但目前还有个问题就是不支持:break、continue、return 书上给了个连接说是辅助工具,但是无法访问 http://refactoring.info/tools/LambdaFicator/
    个人认为换个思路:break、continue 可以通过 filter、limit 等配合实现。return 我只想到能抛异常模拟。

    8.1.5 增加代码的灵活性

    本节介绍了具体的方法:

    1. 采用函数接口

    将参数类型声明为函数接口,这样就能能给它传Lambda了。然而何时需要这样做呢?引出了两个概念:条件的延迟执行环绕执行。(本质就是TemplateMethod模板方法的思想)

    2. 条件的延迟执行(别被名字唬了)

    客户代码原本是每次输出日志,先判断,再打印。

    if (logger.isLoggable(Log.FINER)){ 
     logger.finer("Problem: " + generateDiagnostic()); 
    }
    
    • 1
    • 2
    • 3

    提取为一个log函数后,每次调用,传入条件 level行为 msgSupplier。条件满足,就执行。

    public void log(Level level, Supplier<String> msgSupplier){ 
    	if(logger.isLoggable(level)){ 
    		log(level, msgSupplier.get()); 
    	} 
    }
    logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. 环绕执行(别被名字唬了)

    processFile可以接收一个Lambda,并在创建BufferedReader后用这个Lambda来处理它。

        public static String processFile(BufferedReaderProcessor p) throws IOException {
            try(BufferedReader br = new BufferedReader(new FileReader("src\\main\\resources\\test.txt"))){
                return p.process(br);
            }
        }
    
        public interface BufferedReaderProcessor{
            String process(BufferedReader b) throws IOException;
        }
    
        public static void main(String[] args) throws IOException {
            String oneLine = processFile((BufferedReader b) -> b.readLine());
            System.out.println("oneLine: " + oneLine);
            String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine());
            System.out.println("twoLines: " + twoLines);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    8.2 使用 Lambda 重构面向对象的设计模式

    8.2.1 策略模式

    准备工作

    准备:

    1. 策略接口 ValidationStrategy
    2. 应用类 Validator
    // 定义策略接口(只有一个方法,所以它也是一个函数式接口)
    public interface ValidationStrategy {
        boolean execute(String s);
    }
    
    // 构造时指定策略,validate 执行策略返回结果。
    public class Validator{
        private final ValidationStrategy strategy;
        public Validator(ValidationStrategy v){
            this.strategy = v;
        }
        public boolean validate(String s){
            return strategy.execute(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    传统写法

    // 实现策略 一
    public class IsAllLowerCase implements ValidationStrategy {
        public boolean execute(String s){
            return s.matches("[a-z]+");
        }
    }
    // 实现策略 二
    public class IsNumeric implements ValidationStrategy {
        public boolean execute(String s){
            return s.matches("\\d+");
        }
    }
    // 客户端代码
    public class LambdaDemo {
        public static void main(String[] args) {
            Validator numericValidator = new Validator(new IsNumeric());
            boolean b1 = numericValidator.validate("aaaa");
            Validator lowerCaseValidator = new Validator(new IsAllLowerCase ());
            boolean b2 = lowerCaseValidator.validate("bbbb");
        }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    Lambda 写法

    省了策略实现类,直接 new Validator 策略传Lambda就OK了。

    public static void main(String[] args) {
    	Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
    	boolean b1 = numericValidator.validate("aaaa");
    	Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
    	boolean b2 = lowerCaseValidator.validate("bbbb");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    8.2.2 模板方法

    如果你需要采用某个算法的框架,同时又希望有一定的灵活度,能对它的某些部分进行改进,
    那么采用模板方法设计模式是比较通用的方案。

    准备工作

    1. 消费者 Customer
    @Data
    @AllArgsConstructor
    public class Customer {
        private int id;
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    传统写法

    public abstract class OnlineBanking {
        public void processCustomer(int id){
            Customer c = Database.getCustomerWithId(id); // 从数据库取
            makeCustomerHappy(c);
        }
        abstract void makeCustomerHappy(Customer c);
    }
    
    public class BankA extends  OnlineBanking {
        @Override
        void makeCustomerHappy(Customer c) {
            System.out.println(String.format("ID: %d - %s  你好!",  c.getId(),  c.getName()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    客户端代码

    public class LambdaDemo {
        public static void main(String[] args) {
            BankA bankA = new BankA();
            bankA.processCustomer(666);
        }
    }
    // ID: 666 - 笨笨  你好!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Lambda 写法

    改造processCustomer加一个参数,类型为函数式接口Consumer<T>

    public class OnlineBankingLambda {
        public void processCustomer(int id, Consumer<Customer> makeCustomerHappy){
            Customer c = Database.getCustomerWithId(id); // 从数据库取
            makeCustomerHappy.accept(c);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    使用时,第二个参数传Lambda

    public class LambdaDemo {
        public static void main(String[] args) {
            OnlineBankingLambda onlineBankingLambda = new OnlineBankingLambda();
            onlineBankingLambda.processCustomer(666,  c -> {
                System.out.println(String.format("ID: %d - %s  你好!",  c.getId(),  c.getName()));
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    8.2.3 观察者模式

    观察者模式是一种比较常见的方案,某些事件发生时(比如状态转变),如果一个对象(通
    常我们称之为主题)需要自动地通知其他多个对象(称为观察者),就会采用该方案。创建图形用户界面(GUI)程序时,你经常会使用该设计模式。这种情况下,你会在图形用户界面组件(比如按钮)上注册一系列的观察者。如果点击按钮,观察者就会收到通知,并随即执行某个特定的行为。 但是观察者模式并不局限于图形用户界面。比如,观察者设计模式也适用于股票交易的情形,多个券商可能都希望对某一支股票价格(主题)的变动做出响应。

    书上写有的点多,我再简化一下。

    准备工作

    1. 观察者接口 Observer
    2. 主题接口 Subject
    3. 主题实现 Feed
    // 观察者接口
    interface Observer {
        void notify(String tweet);
    }
    // 主题接口
    interface Subject{
        void registerObserver(Observer o); // 注册观察者
        void notifyObservers(String tweet); // 广播通知所有观察者
    }
    // 主题实现
    class Feed implements Subject{
        private final List<Observer> observers = new ArrayList<>();
        public void registerObserver(Observer o) {
            this.observers.add(o);
        }
        public void notifyObservers(String tweet) {
            observers.forEach(o -> o.notify(tweet));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    传统写法

    实现 N 个具体的观察者。(书上还对消息内容做了判断,满足条件的消息,才处理。我这里简化了)

    // 观察者 A
    class ObserverA implements Observer{
        public void notify(String tweet) {
            System.out.println("观察者A 收到通知:" + tweet);
        }
    }
    // 观察者 B
    class ObserverB implements Observer{
        public void notify(String tweet) {
            System.out.println("观察者B 收到通知:" + tweet);
        }
    }
    // 观察者 C
    class ObserverC implements Observer{
        public void notify(String tweet) {
            System.out.println("观察者C 收到通知:" + tweet);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    客户端代码

        public static void main(String[] args) {
            Feed f = new Feed();
            f.registerObserver(new ObserverA());
            f.registerObserver(new ObserverB());
            f.registerObserver(new ObserverC());
            f.notifyObservers("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Lambda 写法

    无需实现几个类来用。直接传Lambda

        public static void main(String[] args) {
            Feed f = new Feed();
            f.registerObserver(tweet -> System.out.println("观察者A 收到通知:" + tweet));
            f.registerObserver(tweet -> System.out.println("观察者A 收到通知:" + tweet));
            f.registerObserver(tweet -> System.out.println("观察者A 收到通知:" + tweet));
            f.notifyObservers("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    8.2.4 责任链模式

    责任链模式是一种创建处理对象序列(比如操作序列)的通用方案。一个处理对象可能需要
    在完成一些工作之后,将结果传递给另一个对象,这个对象接着做一些工作,再转交给下一个处理对象,以此类推。
    通常,这种模式是通过定义一个代表处理对象的抽象类来实现的,在抽象类中会定义一个字
    段来记录后续对象。一旦对象完成它的工作,处理对象就会将它的工作转交给它的后继。

    典型场景 :Java 中 Servlet Filter 过滤器

    传统写法

    定义一个Filter 过滤器接口,再实现两个Filter 过滤器

    public abstract class MyFilter<T> {
        protected MyFilter<T> nextFilter;
        public void setNextFilter(MyFilter<T> nextFilter){
            this.nextFilter = nextFilter;
        }
        public T handle(T input){
            T r = handleWork(input);
            if(nextFilter != null){
                return nextFilter.handle(r);
            }
            return r;
        }
        abstract protected T handleWork(T input);
    }
    
    public class FilterA extends MyFilter<String> {
        public String handleWork(String text){
            return "【" + text + "】";
        }
    }
    public class FilterB extends MyFilter<String> {
        public String handleWork(String text){
            return text.replaceAll("笨笨", "笑虾");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    客户端

    public class LambdaDemo {
        public static void main(String[] args) {
            MyFilter<String> filterA = new FilterA(); // 实例化 FilterA
            MyFilter<String> filterB = new FilterB(); // 实例化 FilterB
            filterA.setNextFilter(filterB); // 把过滤器串联起来
            String result = filterA.handle("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!");
            System.out.println(result); // 【大家好,我是笑虾,笑虾的笨,笑虾的笨,谢谢!】
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Lambda 写法

    这里用到了函数式接口 UnaryOperator它有两个方法:
    R apply(T t) :用来执行;
    Function<T,V> andThen(Function<? super R,? extends V> after):用来串连。

    这里就完全借助函数式接口来实现了:

    public class LambdaDemo {
        public static void main(String[] args) {
            UnaryOperator<String> filterA = (String text) -> "【" + text + "】";
            UnaryOperator<String> filterB = (String text) -> text.replaceAll("笨笨", "笑虾");
            Function<String, String> pipeline = filterA.andThen(filterB);
            String result = pipeline.apply("大家好,我是笨笨,笨笨的笨,笨笨的笨,谢谢!");
            System.out.println(result); // 【大家好,我是笑虾,笑虾的笨,笑虾的笨,谢谢!】
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    8.2.5 工厂模式

    使用工厂模式,你无需向客户暴露实例化的逻辑就能完成对象的创建。比如,我们假定你为一家银行工作,他们需要一种方式创建不同的金融产品:贷款、期权、股票,等等。
    通常,你会创建一个工厂类,它包含一个负责实现不同对象的方法,如下所示:

    准备工作

    准备:

    1. 产品接口 Product
    2. 三个实现类ProductA, ProductB, ProductC
    3. ProductFactory 工厂类
    interface Product{}
    class ProductA implements Product{}
    class ProductB implements Product{}
    class ProductC implements Product{}
    
    • 1
    • 2
    • 3
    • 4

    传统写法

    class ProductFactory {
        public static Product createProduct(String name){
            switch(name){
                case "A": return new ProductA();
                case "B": return new ProductB();
                case "C": return new ProductC();
                default: throw new RuntimeException("没有产品:" + name);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    客户端

    public class LambdaDemo {
        public static void main(String[] args) {
            Product a = ProductFactory.createProduct("A");
            System.out.println(a);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Lambda 写法

    我暂时没领悟这个有啥优势。

    public class ProductFactory {
        final static Map<String, Supplier<Product>> map = new HashMap<>();
        static {
            map.put("A", ProductA::new);
            map.put("B", ProductB::new);
            map.put("C", ProductC::new);
        }
        public static Product createProduct(String name){
            Supplier<Product> p = map.get(name);
            if(p != null) return p.get();
            throw new IllegalArgumentException("没有产品:" + name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    客户端(完全一样)

    public class LambdaDemo {
        public static void main(String[] args) {
            Product a = ProductFactory.createProduct("A");
            System.out.println(a);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    8.3 测试 Lambda 表达式

    8.3.1 测试可见 Lambda 函数的行为

    我们可以借助某个字段访问Lambda函数,对它进行测试。但Lambda声明为public可不太好。

    public class Point{ 
    	public final static Comparator<Point> compareByXAndThenY = 
    	comparing(Point::getX).thenComparing(Point::getY); 
    	// …
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Test 
    public void testComparingTwoPoints() throws Exception { 
    	Point p1 = new Point(10, 15); 
    	Point p2 = new Point(10, 20); 
    	int result = Point.compareByXAndThenY.compare(p1 , p2); 
    	assertEquals(-1, result); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    8.3.2 测试使用 Lambda 的方法的行为

    我们不必针对p -> new Point(p.getX() + x, p.getY())进行测试,只要测试包含Lambda的方法moveAllPointsRightBy就可以了。

    public static List<Point> moveAllPointsRightBy(List<Point> points, int x){ 
    	return points.stream() 
    		.map(p -> new Point(p.getX() + x, p.getY())) 
    		.collect(toList()); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Test 
    public void testMoveAllPointsRightBy() throws Exception{ 
    	List<Point> points = Arrays.asList(new Point(5, 5), new Point(10, 5)); 
    	List<Point> expectedPoints = Arrays.asList(new Point(15, 5), new Point(20, 5)); 
    	List<Point> newPoints = Point.moveAllPointsRightBy(points, 10); 
    	assertEquals(expectedPoints, newPoints); 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意,上面的单元测试中,Point类恰当地实现equals方法非常重要,否则该测试的结果就取决于Object类的默认实现。

    8.3.3 将复杂的 Lambda 表达式分到不同的方法

    可能你会碰到非常复杂的Lambda表达式,包含大量的业务逻辑,比如需要处理复杂情况的定价算法。你无法在测试程序中引用Lambda表达式,这种情况该如何处理呢?一种策略是将Lambda表达式换为方法引用(这时你往往需要声明一个新的常规方法),我们在8.1.3节详细讨论过这种情况。这之后,你可以用常规的方式对新的方法进行测试。

    8.3.4 高阶函数的测试

    接受函数作为参数的方法或者返回一个函数的方法(所谓的“高阶函数”,higher-order function,我们在第14章会深入展开介绍)更难测试。如果一个方法接受Lambda表达式作为参数,你可以采用的一个方案是使用不同的Lambda表达式对它进行测试。比如,你可以使用不同的谓词对第2章中创建的filter方法进行测试。

    public List<Integer> filter(List<Integer> list, Function<Integer, Boolean> fun){
        return list.stream().filter(fun::apply).collect(Collectors.toList());
    }
        
    @Test
    public void testFilter() throws Exception{
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
        List<Integer> even = filter(numbers, i -> i % 2 == 0);
        List<Integer> smallerThanThree = filter(numbers, i -> i < 3);
        assertEquals(Arrays.asList(2, 4), even);
        assertEquals(Arrays.asList(1, 2), smallerThanThree);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果被测试方法的返回值是另一个方法,该如何处理呢?你可以仿照我们之前处理Comparator的方法,把它当成一个函数接口,对它的功能进行测试。
    然而,事情可能不会一帆风顺,你的测试可能会返回错误,报告说你使用Lambda表达式的方式不对。因此,我们现在进入调试的环节。

    8.4 调试

    调试有问题的代码时,程序员的兵器库里有两大老式武器,分别是:
     查看栈跟踪
     输出日志

    8.4.1 查看栈跟踪

    Lambda表达式是匿名的它没有名字,它的栈跟踪大概长这样:Debugging.lambda$main$0 这名字是编译器自动生成的,正常项目中Lambda一多了 $xxx 这种名字完全看不懂。
    解决方案:使用 方法引用 就能看到正常的方法名了。

    8.4.2 使用日志调试

    peek 的设计初衷就是在流的每个元素恢复运行之前,插入执行一个动作。但是它不像forEach那样恢复整个流的运行,而是在一个元素上完成操作之后,它只会将操作顺承到流水线中的下一个操作。

    在这里插入图片描述

    List<Integer> numbers = Arrays.asList(2, 3, 4, 5);
    numbers.stream()
            .map(x -> x + 17)
            .filter(x -> x % 2 == 0)
            .limit(3)
            .forEach(System.out::println);
            
    System.out.println("=============================================================");
    
    List<Integer> result = numbers.stream()
            .peek(x -> System.out.println("from stream: " + x))
            .map(x -> x + 17)
            .peek(x -> System.out.print("after map: " + x))
            .filter(x -> x % 2 == 0)
            .peek(x -> System.out.print("after filter: " + x))
            .limit(3)
            .peek(x -> System.out.print("after limit: " + x))
            .collect(toList());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出效果

    20
    22
    =====================================================================
    from stream: 2
    after map: 19from stream: 3
    after map: 20after filter: 20after limit: 20from stream: 4
    after map: 21from stream: 5
    after map: 22after filter: 22after limit: 22
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    8.5 小结

    下面回顾一下这一章的主要内容。

    1. Lambda表达式能提升代码的可读性灵活性
    2. 如果你的代码中使用了匿名类,尽量用Lambda表达式替换它们,但是要注意二者间语义的微妙差别,比如关键字this,以及变量隐藏。
    3. Lambda表达式比起来,方法引用的可读性更好 。
    4. 尽量使用Stream API替换迭代式的集合处理
    5. Lambda表达式有助于避免使用面向对象设计模式时容易出现的僵化的模板代码,典型的比如策略模式、模板方法、观察者模式、责任链模式,以及工厂模式。
    6. 即使采用了Lambda表达式,也同样可以进行单元测试,但是通常你应该关注使用了Lambda表达式的方法的行为
    7. 尽量将复杂Lambda表达式抽象到普通方法中。
    8. Lambda表达式会让栈跟踪的分析变得更为复杂。(可用方法引用改善)
    9. 流提供的peek方法在分析Stream流水线时,能将中间变量的值输出到日志中,是非常有用的工具。(直接用 Idea 的流调试工具更直观)

    在这里插入图片描述

    参考资料

    笑虾:《Java8实战》读书笔记04:Stream流操作 - 基本用法
    笑虾:《Java8实战》读书笔记05:Stream流操作 - collect 收集数据

    java.util.stream.Stream
    java.util.stream.Collector 接口定义了实现归约操作(即收集器)的规范
    java.util.stream.Collectors 工具类提供了大量常用的收集器
    java.util.Comparator 比较器

    函数式接口:UnaryOperator<T>

  • 相关阅读:
    基于springboot+vue+Mysql的广场舞团管理系统
    pg嵌套子查询
    「Verilog学习笔记」ROM的简单实现
    RISC-V架构——物理内存属性和物理内存保护
    申请宣告专利权无效的主体有哪些 ?
    Java异步判断线程池所有任务是否执行完成的方法
    贪心算法求解活动安排问题
    杰理之发射器相关的 API【篇】
    在autodl搭建stable-diffusion-webui+sadTalker
    C++Prime Plus(7)
  • 原文地址:https://blog.csdn.net/jx520/article/details/125457958