Java8新特性【函数式接口、Lambda表达、Stream流】

一、Lambda表达式


Java8是Java语言自JDK1.5以后的一个重大的里程碑版本,因为它增加了很多新特性,这些新特性会改变编程的风格和解决问题的方式。

例如:日期时间API、Lambda表达式(λ)、Stream API(操作集合)、方法引用、CompletableFuture(异步编程)等。


1. 函数式编程思想


之前我们写的代码都是面向对象的编程思想,该思想强调的是通过对象来做事情。例如 我们想要线程执行任务就必须创建一个实现Runnable接口的实现类对象,但是我们真正要做的事情实际上就是执行run方法中的代码从而来执行线程的任务。

函数式编程思想省略了创建对象的复杂语法,然后通过 lambda表达式 直接传递代码来执行线程任务。而不需要创建Runnable接口的实现类对象。

函数式编程思想强调的是做什么,而不是以什么方式去做。

下面列举面向对象编程和函数式编程两种编程风格的对比:(函数式编程相比oop语法更加简洁)

package com.baidou;

// 定义游泳接口,并只提供一个抽象方法
public interface Swimming {
    // 游泳方法
    void swimm();
}
package com.baidou;

/**
 * 面向对象编程和函数式编程两种编程风格的对比
 */
public class LambdaTest1 {

    public static void main(String[] args) {
        // 1、调用方法传递匿名内部类对象
        // 面向对象编程风格
        goSwimm(new Swimming() { //多态:编译看左边(父类),运行看右边(子类)
            @Override
            public void swimm() {
                System.out.println("小明在游泳...");
            }
        });

        System.out.println("----------------------------");
        // 2、使用lambda表达式简化上面的匿名内部类(接口中只提供一个抽象方法)
        // 调用方法传递lambda表达式
        goSwimm(() -> {
            System.out.println("王五在游泳...");
        });

    }


    /**
     * 定义一个静态方法,使用Swimming接口作为方法参数,然后调用的时候我们只需传递接口实现类对象便可执行具体方法
     *
     * @param swimm 匿名内部类对象、接口实现类对象
     */
    public static void goSwimm(Swimming swimm) {
        swimm.swimm();
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第1张图片


2. Lambda表达式的使用


2.1 Lambda的标准格式

Lambda的标准语法格式由3个部分组成:一些参数、一个箭头、一段代码{}。

Lambda表达式的标准语法格式如下:

(参数列表)->{代码块}    

解释:

  • () 表示函数式接口的抽象方法的参数列表;(可推导)
  • -> 是lambda表达式的固定组成部分,箭头表示它指向什么;(lambda的标志)
  • {} 表示函数式接口的抽象方法的方法体,也是方法要做的事情。

示例:使用lambda表达式创建并开启线程

package com.baidou;

/**
 * Lambda表达式标准语法格式
 *
 * @author 白豆五
 * @version 2023/04/15
 * @since JDK8
 */
public class LambdaTest2 {
    public static void main(String[] args) {

        //  使用匿名内部类对象的方式创建并开启线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("使用匿名内部类对象的方式创建并开启线程A");
            }
        }).start();

        //使用lambda表达式创建并开启线程
        new Thread(()->{
            System.out.println("使用lambda表达式创建并开启线程B");
        }).start();
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第2张图片


2.2 Lambda的简化格式

在Lambda标准格式的基础上,使用简化写法的规则如下:

  • 小括号内参数的数据类型可以省略; 例如(int a,int b),省略写法为 (a,b)
  • 如果小括号内有且仅有一个参数,则小括号可以省略;
  • 如果大括号内有且仅有一条执行语句{}; 以及 return 都可以省略。(注意:要么全省略,要么全保留)

示例:lambda的简写—省略参数

package com.baidou;

/**
 * 汽车类
 *
 * @author 白豆五
 * @version 2023/04/16
 * @since JDK8
 */
public class Car {
    private String name;
    private String color;
    private Integer price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Car(String name, String color, Integer price) {
        this.name = name;
        this.color = color;
        this.price = price;
    }

    public Car() {
    }

}
package com.baidou;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * Lambda表达式简化格式使用
 *
 * @author 白豆五
 * @version 2023/04/15
 * @since JDK8
 */
public class LambdaTest3 {
    public static void main(String[] args) {
        // 创建list集合对象,并使用Collections工具类往list集合添加一些数据
        List<Car> list = new ArrayList<>();
        Collections.addAll(list,
                new Car("法拉利拉法", "红色", 100),
                new Car("保时捷", "蓝色", 86),
                new Car("宾利", "白色", 50)
        );

        // 集合初始元素顺序为:[Car{name='法拉利拉法', color='红色', price=100}, Car{name='保时捷', color='蓝色', price=86}, Car{name='宾利', color='白色', price=50}]
        // System.out.println("集合初始元素顺序为:" + list);

        // 1. 匿名内部方式,按照价格从小到大排序
       /*
           Collections.sort(list, new Comparator() {
                        @Override
                        public int compare(Car c1, Car c2) {
                            return c1.getPrice() - c2.getPrice(); //升序:c1-c2,降序:c2-c1
                        }
                    }
            );
            System.out.println("按照价格升序的集合为:"+list);
        */

        // 2.使用lambda标准格式,按照价格从小到大排序
      /*
          Collections.sort(list, (Car c1, Car c2) -> {
                return c1.getPrice() - c2.getPrice();
            });
            System.out.println("按照价格升序的集合为:" + list);
        */

        // 3.使用lambda简化格式---省略参数,按照价格降序排序
        // Collections.sort(list, (c1, c2) -> {
        //     return c2.getPrice() - c1.getPrice();
        // });

        // 最简化写法
        Collections.sort(list, (c1, c2) -> c2.getPrice() - c1.getPrice());
        
        // 按照价格降序的集合为:[Car{name='法拉利拉法', color='红色', price=100}, Car{name='保时捷', color='蓝色', price=86}, Car{name='宾利', color='白色', price=50}]
        System.out.println("按照价格降序的集合为:" + list);
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第3张图片


3. Lambda表达式和匿名内部类的区别


1、所需类型不同:

  • 匿名内部类:可以是接口,也可以是抽象类,甚至还可以是具体类;
  • Lambda表达式:只能是接口。

2、使用限制不同:

  • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类;
  • 如果接口中有多个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式。

3、实现原理不同:

  • 匿名内部类:编译之后,会生成一个单独的.class字节码文件;
  • Lambda表达式:编译之后,不会生成一个单独的.class字节码文件,对应的字节码会在运行的时候动态生成。

4. 使用Lambda表达式的前提条件


函数式接口:有且仅有一个抽象方法的接口,称为函数式接口。

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用Lambda表达式有2个前提条件:

1、必须要有接口(函数式接口),要求该接口中有且仅有一个抽象方法,可以有默认方法、静态方法;

  • 在接口上添加@FunctionalInterface注解,表示当前接口是函数式接口,可以对函数式接口进行检测;

  • 一旦使用@FunctionalInterface注解定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。

2、必须要有方法使用函数式接口作为方法参数;

  • 可以使用@verride注解,检测子类方法是否对父类方法进行覆盖重写;

示例:Lambda表达式的前提条件演示

函数式接口:

package com.baidou;

/**
 * 定义一个函数式接口
 *
 * @author 白豆五
 * @version 2023/04/16
 * @since JDK8
 */

@FunctionalInterface // 标记的接口就是函数式接口,可以对函数式接口进行检测
public interface MyFunctionalInterface {
    // 必须要被覆盖重写的抽象方法
    void method();

    // 不强制要求实现类必须覆盖重写该方法
    // 原因:所有实现类最终都会继承Object类,而Object类已经提供了该方法
    boolean equals(Object obj);

    // 默认方法
    // default void defaultMethod(){}


    // 静态方法
    // static void staticMethod(){}
}

测试:

package com.baidou;

/**
 * Lambda表达式的前提条件
 *
 * @author 白豆五
 * @version 2023/04/16
 * @since JDK8
 */
public class LambdaTest4 {
    public static void main(String[] args) {
        // 1. 调用方法传递匿名内部类对象
        fun(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("A...");
            }
        });

        // 2. 调用方法传递lambda表达式标准格式
        fun(() -> {  //按照抽象方法推导
            System.out.println("B...");
        });

        // 3. 调用方法传递lambda表达式简化格式
        fun(() -> System.out.println("C..."));

        // lambda表达式可以直接给接口变量赋值 (这种写法用的少)
        MyFunctionalInterface mFun = () -> {
            System.out.println("D...");
        };
        mFun.method();
    }


    public static void fun(MyFunctionalInterface mfi) {
        // 调用抽象方法
        mfi.method();
    }

}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第4张图片


5. Lambda表达式的应用场景


1、变量(使用极少)

package com.baidou.java.jdk.feature.java8;

/**
 * 自定义函数式接口
 *
 * @author baidou [email protected]
 * @version 2022/6/5 14:56
 * @since JDK8
 */
@FunctionalInterface
public interface CustomFunctionalInterface {
    // 实现两个整数相加
    int add(int a, int b);
}
/**
 * lambda表达式的应用场景
 */
@Test
public void testLambdaExpressionUsage() {
    // 将lambda表达式赋值给一个接口变量
    CustomFunctionalInterface c = (int a, int b) -> {
        return a + b;
    };
    int result = c.add(12, 34);
    System.out.println("result = " + result);
}

2、方法的参数

Collections.sort(list, (value1,value2) -> value2 - value1);

3、方法的返回值

/**
 * lambda表达式可以作为方法的返回值
 */
public CustomFunctionalInterface getCustomFunctionalInterface(){
    return (int a,int b)->{return a+b;};
}
CustomFunctionalInterface c = getCustomFunctionalInterface();
System.out.println("result = " + c.add(11, 22));

二、函数式接口


除了我们自定的函数式接口外,Java也为我们提供了相关的函数式接口,方便我们后续使用Lambda表达式;

函数式接口:有且仅有一个抽象方法的接口,称为函数式接口。

常用的函数式接口位于java.util.funcation包下,例如 Supplier接口、Consumer接口、Function接口、Predicate接口。


1. Supplier接口


java.util.function.Supplier接口,表示供给型接口,生产一个数据的 ,对应的Lambda表达式需要对外提供一个符合泛型类型的对象数据。

  • Supplier接口只提供了一个无参的抽象方法get(),用来获取一个指定的泛型数据。

Supplier接口源码如下:

package java.util.function;

@FunctionalInterface
public interface Supplier<T> { 
    T get(); //返回一个泛型对象
}


示例: 匿名内部类的方式,返回拼接后的字符串

package com.baidou.supplier;

import java.util.function.Supplier;

public class SupplierTest1 {
    public static void main(String[] args) {

        String s1 = "hello";
        String s2 = "java";

        // 1. 匿名内部类的方式,返回拼接后的字符串
        fun(new Supplier<String>() {
            @Override
            public String get() {
                return s1 + s2;
            }
        });
        
    }

    /**
     * 定义方法,使用函数式接口Supplier作为参数
     *
     * @param supplier Supplier接口实现类对象、匿名内部类对象、lambda表达式
     */
    public static void fun(Supplier<String> supplier) {
        // 调用抽象方法
        // 至于str存的内容是什么以及怎么获取,这里暂时说不清楚,谁调用谁去解决这个问题
        String str = supplier.get();
        System.out.println(str);//hellojava
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第5张图片


示例:使用lambda表达式,返回拼接后的大写字符串

package com.baidou.supplier;

import java.util.function.Supplier;

public class SupplierTest2 {
    public static void main(String[] args) {

        String s1 = "hello";
        String s2 = "java";

        // 2. 使用lambda表达式,返回拼接后的大写字符串
        fun(() -> (s1 + s2).toUpperCase());

    }


    /**
     * 定义方法,使用函数式接口Supplier作为参数
     *
     * @param supplier Supplier接口实现类对象、匿名内部类对象、lambda表达式
     */
    public static void fun(Supplier<String> supplier) {
        // 调用抽象方法
        // 至于str存的内容是什么以及怎么获取,这里暂时说不清楚,谁调用谁去解决这个问题
        String str = supplier.get();
        System.out.println(str);
    }
}
  • 字符串名.toUpperCase() :将字符串中的字母全部转换为大写,非字母不受影响
  • 字符串名.toLowerCase() :将字符串中的字母全部转换为小写,非字母不受影响

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第6张图片


2. Consumer接口


java.util.function.Consumer接口,与Supplier接口正好相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型参数决定。

  • Consumer接口提供了一个抽象方法accept(T t),用于消费(处理)一个指定泛型的数据。

Consumer接口源码如下:

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Consumer<T> {

    // 用于消费(处理)一个指定泛型的数据
    // 什么是消费呢?只要给accept方法添加方法体就叫做消费,不管{}里写的是什么,都叫做消费
    void accept(T t);

    //需要两个Consumer接口,可以把两个Consumer接口组合到一起,在对数据进行消费
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

示例:Consumer接口的accept()方法

package com.baidou.consumer;

import java.util.function.Consumer;

public class ConsumerTest1 {
    public static void main(String[] args) {
        String str = "hello Springboot";

        // 1.匿名内部类的方式,按照字符串大写格式输出
        fun(str, new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s.toUpperCase()); //HELLO SPRINGBOOOT
            }
        });

        // 2.使用lambda表达式,按照字符串小写格式输出
        fun(str, (String s) -> {
            System.out.println(s.toLowerCase());//hello springboot
        });

        // 3.使用lambda表达式,按照字符串长度消费
        fun(str, s -> System.out.println(s.split(" ")[0]));// hello
    }

    /**
     * 定义方法,使用函数式接口Consumer作为参数
     *
     * @param ss       待传入的字符串参数
     * @param consumer consumer接口实现类对象、匿名内部类对象、lambda表达式
     */
    public static void fun(String ss, Consumer<String> consumer) {
        // 调用抽象方法,处理字符串ss
        // 如何处理字符串ss,这里说不清楚,谁调用了谁处理
        consumer.accept(ss);
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第7张图片


3. Function接口


java.util.function.Function接口,用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有进有出,所以称为函数Function。

  • Function接口提供了一个抽象方法R apply(T t),表示根据类型T的参数获取类型R的结果。
  • 使用的场景例如:将String类型转换为Integer类型。

源码如下:

package java.util.function;

import java.util.Objects;

@FunctionalInterface
public interface Function<T, R> {


    // 根据类型T的参数获取类型R的结果 (数据转换)
    // 示例:将String类型转换为Integer类型
    R apply(T t);


    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }


    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }


    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

示例:使用Function接口的apply()方法,将String数字转成int类型数字

package com.baidou.function;

import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 演示Function接口的apply方法
 *
 * @author 白豆五
 * @version 2023/04/17
 * @since JDK8
 */
public class FunctionTest {
    public static void main(String[] args) {
        String str = "12300";

        // 1.匿名内部类的方式,将String数字转成int类型数字
        fun(str, new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s);
            }
        });

        // 2.使用lambda表达式,将String数字转成int类型数字后,再扩大10倍
        fun(str, (String s) -> {
            return Integer.parseInt(s) * 10;
        });

        // 3.使用lambda表达式,将String数字转成int类型数字后,再缩小10倍
        fun(str, s -> Integer.parseInt(s) / 10);

    }

    /**
     * 定义方法,使用函数式接口Function作为参数
     *
     * @param str  待传入的字符串数字
     * @param function
     */
    public static void fun(String str, Function<String, Integer> function) {
        // 调用抽象方法,将字符串转成Integer类型数据
        // 具体功能让子类去实现
        System.out.println(function.apply(str));
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第8张图片


4. Predicate接口


有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate接口。

Predicate接口提供了一个抽象方法boolean test(T t)。用于条件判断的场景,条件判断的标准是传入的Lambda表达式逻辑。

Predicate接口源码如下:

package java.util.function;

import java.util.Objects;

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * 

This is a functional interface * whose functional method is {@link #test(Object)}. * * @param the type of the input to the predicate * * @since 1.8 */ @FunctionalInterface public interface Predicate<T> { // 用于条件判断 boolean test(T t); default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }


示例:使用Predicate接口的test()方法进行条件判断

package com.baidou.predicate;

import java.util.function.Predicate;

/**
 * @author 白豆五
 * @version 2023/04/17
 * @since JDK8
 */
public class PredicateTest {

    public static void main(String[] args) {
        String str = "helloWorld";
        // 1.使用内名内部类方式,判断字符串长度是否大于5
        fun(str, new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length() > 5;
            }
        });

        // 2.使用lambda方式,判断字符串是否包含W
        fun(str, (s) -> {
            return s.contains("W");
        });


        // 3.使用lambda方式,判断字符串是否以ld结尾
        fun(str, s -> s.endsWith("ld"));

    }


    // 定义方法,使用Predicate接口作为方法参数
    public static void fun(String str, Predicate<String> predicate) {
        // 调用抽象方法
        System.out.println(predicate.test(str));
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第9张图片

ok,到这里我们已经把四个常用的函数式接口演示完毕了,接下来就是最重要的环节Stream流。


三、Stream流


Stream流可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。(流式编程思想)

1. 什么是Stream流


  • 在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的概念:Stream流。

  • 作用:简化了操作集合和数组的API,结合lambda表达式。


Stream流的思想和使用步骤:

1、先拿到集合或者数组的Stream流(Stream流相当于一根传送带)
2、把元素放上去。
3、然后就用这个Stream流的API操作元素。


2. 如何获取Stream流


java.util.stream.Stream是Java 8新加入的最常用的流接口。(它并不是一个函数式接口)

Java8新特性【函数式接口、Lambda表达、Stream流】_第10张图片

Stream是接口,要想按照Stream流式编程,必须获取接口的实现类对象,然后才能使用流的功能。


1、集合获取Stream流的方式:

集合获取Stream的方式是通过调用stream()方法实现的。

名称 说明
default Stream stream() 获取当前集合对象的stream流

2、数组获取Stream流的方式:

名称 说明
public static Stream stream(T[ ] array) 获取当前数组的Stream流(Stream接口提供)
public static Stream of(T… vilues) 获取当前数组/可变数据的stream流(Arrays类提供)

示例:

package com.baidou.get_stream;

import java.util.*;
import java.util.stream.Stream;

/**
 * 分别获取集合和数组的Stream流
 *
 * @author 白豆五
 * @version 2023/04/17
 * @since JDK8
 */
public class GetStreamTest {
    public static void main(String[] args) {
        /*
             1. 获取单列集合的Stream流对象
         */
        // 1.1 获取List集合对象对应的stream流
        List<String> list = new ArrayList<>();
        Stream<String> listStream = list.stream();
        System.out.println(listStream); //java.util.stream.ReferencePipeline$Head@14ae5a5

        // 1.2 获取set集合对象对应的stream流
        Set<String> set = new HashSet<>();
        Stream<String> setStream = set.stream();
        System.out.println(setStream); //java.util.stream.ReferencePipeline$Head@7f31245a


         /*
             2. 获取双列集合的Stream流对象
             map内部没有直接定义stream方法获取stream流,需要先转换为单列集合,然后再获取stream流
             - 可以先通过keySet或者entrySet获取一个set集合,然后再获取steam流
         */
        Map<String, String> map = new HashMap<>();
        // 2.1 获取存储键的set集合
        Set<String> keySet = map.keySet();
        Stream<String> keyStream = keySet.stream();//键流
        // 2.2 获取存储值的collection集合,然后再拿到对应的值流
        Stream<String> valuestream = map.values().stream();

        // 2.3 获取存储键值对对象的set集合
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        Stream<Map.Entry<String, String>> entryStream = entrySet.stream(); //键值对流


        /*
             3. 获取数组的Stream流对象
         */
        String[] arr = {"太白金猩", "张丝锐", "李斯", "王武"};
        // 3.1 通过java.utils.Arrays工具类的stream静态方法,把数组转换称stream流对象
        Stream<String> arrstream1 = Arrays.stream(arr);

        // 3.2 获取数组对应的stream流对象
        // Stream接口提供
        Stream<String> arrStream2 = Stream.of(arr);
        Stream<String> arrStream3 = Stream.of("太白金猩", "张丝锐", "李斯", "王武");
    }
}

3. Stream流的终结操作方法


Stream流的三类方法:获取stream流、中间方法、终结方法。

  • void forEach(Consumer action):遍历流中的元素,Consumer消费型接口;
  • long count():统计流中的元素个数,返回值long类型;

注意:终结操作方法,调用完成之后流就无法继续使用了,原因是它不会返回Stream流。


示例: Stream流常见的终结方法

package com.baidou.stream_endmethod;

import java.util.function.Consumer;
import java.util.stream.Stream;

/**
 * 测试Stream流结束方法:forEach()、count()
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo01 {
    public static void main(String[] args) {
        // 1.获取stream流
        Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃");
        // 2.遍历stream流
        // 2.1匿名内部类方式
       /* s.forEach(new Consumer() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/

        // 2.2lambda表达式方式,(如果不会写lambda,就先把匿名内部类写出来,然后通过覆盖重写的抽象方法推导出lambda)
        s.forEach(str-> System.out.println(str));

        // 报错:java.lang.IllegalStateException: stream has already been operated upon or closed(流已经被操作或关闭)
        // 调用终结方法后流就不可以继续使用了,原因是它不会返回Stream流。
        System.out.println(s.count());
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第11张图片


小结: 终结方法和非终结方法的含义?

  • 调用终结方法后流不可以继续使用,调用非终结方法会返回新的流,支持链式编程。

4. Stream流的中间操作方法


Stream流常见的中间操作方法有:

  • Stream filter(Predicate predicate):过滤元素(过滤条件);
    • Predicate 接口提供的方法:boolean test(T t),对给定的参数进行条件判断的;
  • Stream limit(long maxSize):获取前几个元素;
  • Stream skip(long n):跳过前几个元素;
  • static Stream concat(Stream a, Stream b):合并流;
  • Stream distinct():去除流中重复的元素。(依赖hashCode和equals方法);
  • Stream map(Function mapper):加工方法。
  • Stream sorted(Comparator comparator),指定排序规则,并返回新的stream流。

注意事项:

  • 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。

  • 在Stream流中无法直接修改集合、数组中的数据。


示例:filter方法使用,主要还是依赖test方法的条件判断

package com.baidou.stream_centermethd;

import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * 测试中间方法 filter(过滤操作)
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo2 {
    public static void main(String[] args) {
        // 获取stream流
        Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五");

        // 匿名内部类方式,过滤所有带娃的元素(中间方法)
        // 返回一个新的stream流--s2,存放带娃的元素
        Stream<String> s2 = s.filter(new Predicate<String>() {
            @Override
            public boolean test(String name) {
                return name.contains("娃");
            }
        });
        // 遍历输出(终结方法)
        s2.forEach(name -> System.out.println(name));

        System.out.println("------");

        // lambda表达式方式,过滤所有不带娃的元素(中间方法)
        s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五");
        Stream<String> s3 = s.filter(name -> !name.contains("娃"));
        s3.forEach(name -> System.out.println(name));

    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第12张图片


示例2:limit方法使用,获取前7个元素

package com.baidou.stream_centermethd;

import java.util.function.Predicate;
import java.util.stream.Stream;

/**
 * 测试中间方法 limit(获取前n个数据)
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo3 {
    public static void main(String[] args) {
        // 获取stream流
        Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五");

        // 获取前7个数据
        Stream<String> s1 = s.limit(7);

        // 遍历输出
        s1.forEach(str-> System.out.println(str));
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第13张图片


示例3:skip方法使用,跳过前3个元素

package com.baidou.stream_centermethd;

import java.util.stream.Stream;

/**
 * 测试中间方法 skip(跳过前n个数据)
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo4 {
    public static void main(String[] args) {
        // 获取stream流
        Stream<String> s = Stream.of("大娃", "二娃", "三娃", "四娃", "五娃", "六娃", "七娃", "白豆五");

        // 跳过前3个数据
        Stream<String> s1 = s.skip(3);

        // 遍历输出
        s1.forEach(str-> System.out.println(str));
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第14张图片


示例4:concat、distinct方法使用

package com.baidou.stream_centermethd;

import java.util.stream.Stream;

/**
 * 测试中间方法 concat(合并流)、distinct(去重)
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo5 {
    public static void main(String[] args) {
        // 获取stream流
        Stream<String> s1 = Stream.of("唐僧", "孙悟空", "沙和尚", "猪八戒", "白龙马");
        Stream<String> s2 = Stream.of("白骨精", "金角大王", "银角大王", "黑熊精", "白骨精");

        // 去除流中重复元素
        Stream<String> s3 = s2.distinct();

        // 合并流并遍历
        Stream.concat(s1, s3).forEach(str -> System.out.println(str));
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第15张图片


示例:加工方法map

package com.baidou.stream_centermethd;

import java.util.function.Function;
import java.util.stream.Stream;

/**
 * 测试中间方法 map(加工方法)
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo6 {
    public static void main(String[] args) {
        // 获取stream流
        Stream<String> s1 = Stream.of("java", "springboot", "springcloud", "mybatis", "php");

        /*
            map加工方法
            map(new Function<原材料类型, 加工后的结果类型>())
         */
        // 为流中每一个元素前面添加hello
        // 匿名内部类方式
        // s1.map(new Function() {
        //     @Override
        //     public Object apply(String s) {
        //
        //         return "hello " + s;
        //     }
        // }).forEach(System.out::println);

        //  lambda表达式方式
        s1.map(s -> "hello " + s).forEach(System.out::println);
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第16张图片


示例:使用sorted方法对stream数据排序

package com.baidou.stream_collect;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 使用sorted方法对stream流排序
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo8 {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        Collections.addAll(list,
                new Student("张三", 18),
                new Student("李四", 38),
                new Student("王武", 28));

        // 获取list集合对应的stream流对象
        Stream<Student> s1 = list.stream();

        //  使用sorted方法对流进行升序排列
        s1.sorted(((o1, o2) -> o1.getAge() - o2.getAge())).forEach(System.out::println);
    }
}

class Student {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第17张图片


5. Stream流练习


练习1:先筛选所有姓张的人,然后再次对名字有三个字的人进行筛选,最后对结果进行打印输出。

package com.baidou.test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * 先筛选所有姓张的人,然后再次对名字有三个字的人进行筛选,最后对结果进行打印输出。(过滤方法)
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamTest1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>() {{
            add("张三");
            add("张思锐");
            add("李斯");
            add("王武");
            add("赵赛克斯");
        }};

        // 1.获取list集合对应的Stream流
        Stream<String> s = list.stream();

        // 2.筛选所有姓张的人,然后再对名字有三个字的人进行筛选,最后对结果进行打印输出
        // Stream aStream = s.filter(name -> name.startsWith("张"));
        // Stream bStream = aStream.filter(name -> name.length() == 3);
        // bStream.forEach(name -> System.out.println(name));

        // 简化写法
        // s.filter(name -> name.startsWith("张") && name.length() == 3).forEach(name -> System.out.println(name));
        s.filter(name -> name.startsWith("张") && name.length() == 3).forEach(System.out::println); //输出用到了方法引用
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第18张图片


练习2:现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
package com.baidou.test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/*
练习2:现在有两个`ArrayList`集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
    1. 第一个队伍只要名字为3个字的成员姓名;
    2. 第一个队伍筛选之后只要前3个人;
    3. 第二个队伍只要姓张的成员姓名;
    4. 第二个队伍筛选之后不要前2个人;
    5. 将两个队伍合并为一个队伍;
 */
public class StreamTest2 {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        one.add("迪丽凉巴");
        one.add("宋近桥");
        one.add("苏星河");
        one.add("土豆");
        one.add("茄子");
        one.add("芒果");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力大山");
        two.add("张无忌");
        two.add("张三丰");
        two.add("张二狗");
        two.add("张天天");
        two.add("张三");

        // 1. 第一个队伍只要名字为3个字的成员姓名 (过滤filter)
        // 2. 第一个队伍筛选之后只要前3个人 (限定limit)
        // one.stream().filter(name->name.length()==3).limit(3).forEach(System.out::println);
        Stream<String> s1 = one.stream().filter(name -> name.length() == 3).limit(3);

        // 3. 第二个队伍只要姓张的成员姓名
        // 4. 第二个队伍筛选之后不要前2个人
        // two.stream().filter(name -> name.startsWith("张")).skip(2).forEach(System.out::println);
        Stream<String> s2 = two.stream().filter(name -> name.startsWith("张")).skip(2);

        // 5. 将两个队伍合并为一个队伍
        Stream.concat(s1,s2).forEach(System.out::println);
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第19张图片


6. Stream流的收集


即使我们使用流的方式操作数据,但是数据始终保存在流中,并没有持久化到集合或数组中,这时需要我们把操作的结果数据恢复到集合或者数组中去。

Stream流的收集方法:

  • R collect(Collector collector),收集Stream流,需要指定收集器。

Collectors工具类提供了具体的收集方式:

  • public static Collector toList(),把元素收集到List集合中;
  • public static Collector toSet(),把元素收集到Set集合中;
  • public static Collector<> toMap(Function keyMapper,Function valueMapper) ,把元素收集到Map集合中。

示例:收集stream流

package com.baidou.stream_collect;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 使用collect方法收集stream流
 *
 * @author 白豆五
 * @version 2023/04/18
 * @since JDK8
 */
public class StreamDemo7 {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        Collections.addAll(list,
                new Person("张三", 18),
                new Person("李四", 28),
                new Person("王武", 38));

        // 获取list集合对应的stream流对象
        Stream<Person> s1 = list.stream();

        // 利用map方法,对每个person对象的年龄增加两岁后,存储到新的Stream流对象中
        // Stream s2 = s1.map(person -> {
        //     person.setAge(person.getAge() + 2);
        //     return person;
        // });

        // 利用collect方法把stream流对象,转换为List集合
        // list = s2.collect(Collectors.toList());


        // 简化写法
        list = s1.map(person -> {
            person.setAge(person.getAge() + 2);
            return person;
        }).collect(Collectors.toList());
        // 遍历集合
        list.forEach(System.out::println);
    }
}

class Person {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Person() {
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

运行结果:

Java8新特性【函数式接口、Lambda表达、Stream流】_第20张图片


完结撒花

你可能感兴趣的:(Java基础,java,android,jvm)