当前位置:   article > 正文

JAVA进阶-lambda及Stream使用精讲

JAVA进阶-lambda及Stream使用精讲

7.lambda

7.1 lambda理解

Lambda表达式是JDK8的一个新特性,可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。

JDK也提供了大量的内置函数式接口供我们使用,使得Lambda表达式的运用更加方便、高效。

Lambda表达式,也称为闭包:java8的新特性,lambda运行将函数作为一个方法的参数,也就是将函数作为参数传递到方法中。使用lambda表达式可以让代码更加简洁。
Lambda表达式常用于简化接口实现,关于接口实现,可以有很多种方式。例如:创建接口的实现类;或者使用匿名内部类;

举例说明:

接口和继承实现

public interface TestInterface {
    public void test();
}

public class TestInterfaceImpl implements TestInterface {
    @Override
    public void test() {
        System.out.println("实现继承接口方法");
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

使用

 @Test
    public void t7(){
        //继承实现接口
        System.out.println("继承实现接口==============");
        
        TestInterface service=new TestInterfaceImpl();
        service.test();
        
        System.out.println("匿名类实现接口==============");
        
        service=new TestInterface(){

            @Override
            public void test() {
                System.out.println("匿名类实现");
            }
        };
        service.test();

        System.out.println("匿名类调用==============");
        
        new TestInterface(){
            @Override
            public void test() {
                System.out.println("匿名类调用直接调用");
            }
        }.test();
    }
  • 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
  • 26
  • 27
  • 28

使用lambda表达式就非常简单:

 TestInterface ts=()->{
            System.out.println("我用使用lambda语法来实现接口");
        };
        ts.test();
  • 1
  • 2
  • 3
  • 4

7.2lambda语法

7.2.1 基本定义

基本语法: (parameters) ->{ statements; }

(parameters)就是省略了类名的构造函数
->是lambda标志,后面是方法体{}

new Class类名《省略类名》(构造参数) ->{
函数语句
}
Lambda表达式由三部分组成:

  • paramaters:
    类似方法中的形参列表,这里的参数是函数式接口里的参数。这里的参数类型可以明确的声明也可不声明而由JVM隐含的推断。另外当只有一个推断类型时可以省略掉圆括号。
  • ->:
    -可理解为“被用于”的意思,是lambda标志
  • 方法体:
    -可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不反回。
// 1. 不需要参数,返回值为 2
() -> 2
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的和
(x, y) -> x + y
// 4. 接收2个int型整数,返回他们的乘积
(int x, int y) -> x * y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7.2.2 函数式接口

要了解Lambda表达式,首先需要了解什么是函数式接口,函数式接口定义:一个接口有且只有一个抽象方法

注意:

1.如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口

2.如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口,这样如果有两个抽象方法,程序编译就会报错的。所以,从某种意义上来说,只要你保证你的接口中只有一个抽象方法,你可以不加这个注解。加上就会自动进行检测的。
定义方式:

@FunctionalInterface
public interface NoParameterNoReturn {
    void test();
}
  • 1
  • 2
  • 3
  • 4

7.2.3 Lambda表达式的基本使用

函数接口声明,声明无返回函数接口

//1.无参无返回函数接口
@FunctionalInterface
public interface NoParameterNoReturn {
    void test();
}
//2.有参无返回参数
public interface OneParameterNoReturn {
    public void test(int x);
}

//3.多参无返回函数接口
@FunctionalInterface
public interface NoParameterNoReturn {
    void test();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

lambda和匿名实现类的对比

   @Test
    public  void t0() {

        NoParameterNoReturn n = () -> {
            System.out.println("无参无返回参数");
        };
        n.test();
       //lambda表达式等价于匿名类接口实现
        NoParameterNoReturn nn=new NoParameterNoReturn(){
            @Override
            public void test() {
                System.out.println("无参无返回参数");
            }
        };
        nn.test();
        System.out.println("==========================================");

        OneParameterNoReturn o=(x)->{
            System.out.println("x的值==="+x);
        };
        o.test(200);
        //lambda表达式等价于匿名类接口实现
        OneParameterNoReturn on=new OneParameterNoReturn(){

            @Override
            public void test(int x) {
                System.out.println("x的值==="+x);
            }
        };
        on.test(200);

    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

有返回值的接口函数

//1.无参有返回函数接口
public interface NoParamReturn {
    public int test();
}
//2.有参有返回函数接口
public interface OneParamReturn {
    public int test(int x);
}
//3.多参有返回函数接口
public interface MoreParamReturn {
    public int test(int x,int y);
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

对应的lambda函数

@Test
    public void t2(){
        NoParamReturn nr=()->{
            return 666;
        };
        System.out.println(" nr.test()="+ nr.test());;
        NoParamReturn nr2=()->2;
        System.out.println(" nr2.test()="+ nr2.test());;

        OneParamReturn or=(x)->{
            return 2*x;
        };
        System.out.println(" or.test(100)="+ or.test(100));

        MoreParamReturn mr=(x,y)->{
            return x+y;
        };
        System.out.println("mr.test(100, 300)="+mr.test(100, 300));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

7.2.4 lambda简写

1.参数类型可以省略,如果需要省略,每个参数的类型都要省略。
A a=(int x,int y)->{return 2;};
可以简写成
A a=(x,y)->{return 2;};

2.参数的小括号里面只有一个参数,那么小括号可以省略
A a=(int x)->{return 2;};
可以简写
A a=x->{return 2;}; //这种说实话,不好理解

3.如果方法体当中只有一句代码,那么大括号可以省略
A a=x->System.out.println(“ffff”);

4.如果方法体中只有一条语句,其是return语句,那么大括号可以省略,且去掉return关键字
()->2;
(x)-> 2x;
x-> 2;

再看示例:

	public static void main(String[] args) {
        MoreParameterNoReturn moreParameterNoReturn = (a, b)->{
            System.out.println("无返回值多个参数,省略参数类型:"+a+" "+b);
        };
        moreParameterNoReturn.test(20,30);
        OneParameterNoReturn oneParameterNoReturn = a ->{
            System.out.println("无参数一个返回值,小括号可以省略:"+ a);
        };
        oneParameterNoReturn.test(10);
        NoParameterNoReturn noParameterNoReturn = ()->System.out.println("无参数无返回值,方法体中只有 一行代码");
        noParameterNoReturn.test();
        //方法体中只有一条语句,且是return语句
        NoParameterReturn noParameterReturn = ()-> 40;
        int ret = noParameterReturn.test();
        System.out.println(ret);
    }


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

7.3 Lambda内置函数接口

今天我们还讲讲Consumer、Supplier、Predicate、Function这几个接口的用法,在 Java8 的用法当中,这几个接口虽然没有明目张胆的使用,但是,却是润物细无声的。为什么这么说呢?

这几个接口都在 java.util.function 包下的,分别是Consumer(消费型)、supplier(供给型)、predicate(谓词型)、function(功能性)

7.3.1 消费性Consumer/BiConsumer

从字面意思上我们就可以看得出啦,consumer接口就是一个消费型的接口,通过传入参数,然后输出值,就是这么简单,Java8 的一些方法看起来很抽象,其实,只要你理解了就觉得很好用,并且非常的简单。
Consumer源码

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

Consumer 顾名思义,就是消费者,它传入一个任意参数,通过accept()接口,交给实现者去处理, 其实Consumer就是一个内置lambda的函数接口,通过它,就可以使用lamdba语法了,用户不需要自己再写一个 函数接口了

实例:

 @Test
    public void t9(){
    /**
我们直接创建 Consumer 接口,并且实现了一个名为 accept 的方法,这个方法就是这个接口的关键了。
我们看一下 accept 方法;这个方法传入一个参数,不返回值。
当我们发现 forEach 需要一个 Consumer 类型的参数的时候,传入之后,就可以输出对应的值了
     **/
        Consumer<String> cl=new Consumer<String>(){

            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        cl.accept("who are you?");

        Consumer<String> cl1=(String str)->{
            System.out.println(str);
        };

        cl1.accept("who are you?");

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

再看一个例子,增加理解,list.forEach()需要传入一个Consumer函数接口

  @Test
    public void t10(){
        List<String> list=Arrays.asList("1","2","3");
        Consumer<String> cl1=(String str)->{
            System.out.println(str);
        };
        list.forEach(cl1);
        //简写forEach
        System.out.println("简写forEach=====================");


        list.forEach(str-> System.out.println(str));

        System.out.println("更简写forEach,使用引用=====================");
        //采用引用
        Consumer<String> cl2= System.out::println;
        list.forEach(cl2);
        //不需要赋值,直接传递给forEach
        System.out.println("不需要赋值,直接传递给forEach");
        list.forEach(System.out::println);

    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

BiConsumer 就是Consumer是两个参数,方法和Consumer一样

@Test
    public void t13(){
        System.out.println("BiConsumer 匿名函数==========");
        BiConsumer<Integer,String> bc=new BiConsumer<>(){
            @Override
            public void accept(Integer id, String name) {
                System.out.println("id="+id+",name="+name);
            }
        };
        bc.accept(10, "jzk");
        System.out.println("BiConsumer lambda表达式==========");
        bc=(id,name)->System.out.println("id="+id+",name="+name);
        bc.accept(10, "jzk");
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

7.3.2 供给型 supplier

Supplier 接口是一个供给型的接口,其实,说白了就是一个容器,可以用来存储数据,然后可以供其他方法使用的这么一个接口。

Supplier是Java8配合Lambda表达式和函数式接口编程组合使用的一个接口,对外表现为 ::

接口Supplier 最适合表示工厂。带有Supplier 的方法,通常应该限制输入工厂的类型参数使用有限制的通配符类型,以便客户端可以传入工厂,来创建制定类型的任意子类。

简而言之,Supplier就是来创建对象的。

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     * @return a result
     */
    T get();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

看一个实际代码

	@Test
    public void t11(){
        System.out.println("======Supplier==匿名函数");
        /***
         我们通过创建一个 Supplier 匿名对象,实现了一个 get 方法,这个方法无参数,返回一个值;
         所以,每次使用这个接口的时候都会返回一个值,并且保存在这个接口中,所以说是一个容器。
         */
        Supplier<Integer> supplier1=new Supplier<Integer>(){
            @Override
            public Integer get() {
                //返回一个随机值
                return new Random().nextInt();
            }
        };
        System.out.println(supplier1.get());

        System.out.println("======Supplier==lambda");
        /***
         使用 lambda 表达式返回一个 Supplier类型的接口,
         然后,我们调用 get 方法就可以获取这个值了
         */
        Supplier<Integer> supplier2=()->new Random().nextInt();
        System.out.println(supplier2.get());

        System.out.println("======Supplier==lambda方法引用");
        //方法引用也是返回一个Supplier类型的接口。
        Supplier<Double> supplier3 = Math::random;
        System.out.println(supplier3.get());

    }

  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

再看一个例子

 public int getVal(Supplier<Integer> supplier) {
        return supplier.get();
    }

    @Test
    public void testSupplier() {
        List<Integer> valList = Arrays.asList(1, 245, 6, 7, 8, 65, 432, 345);
        //getVal(Supplier<Integer> supplier)是一个函数式接口
        int maxVal = getVal(
                () -> {
                    int val = Integer.MIN_VALUE;
                    for (Integer v : valList) {
                        if (v > val) {
                            val = v;
                        }
                    }

                    return val;
                }
        );
        System.out.println("最大值:" + maxVal);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

7.3.4 Function/BiFunction函数

查看源码

@FunctionalInterface
public interface Function<T, R> {

    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));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Function<T,R> 是 Java 中的函数式接口,它代表一个函数,用来将一个类型为 T 的对象转换为类型为 R 的对象。它接受一个参数,执行转换操作,并返回一个转换后的结果。

Function<T,R> 接口只有一个抽象方法 R apply(T t),该方法用于将一个类型为 T 的对象转换为类型为 R 的对象。

使用 Function<T,R> 接口可以方便地实现一些转换操作,例如在需要将一个类型为 T 的对象转换为类型为 R 的对象时。另外,它也可以作为方法的参数,用于接收一个转换函数。

示例1

  @Test
    public void t15(){
        //Function(入参,返回)
        System.out.println("Function 匿名函数=========");
        Function<Integer,Person> fun=new Function<>(){

            @Override
            public Person apply( Integer id) {
                Person person=new Person();
                person.setId(id);
                person.setName("jzk");
                return person;
            }
        };

        System.out.println(fun.apply(1));

        System.out.println("Function lambel=========");
        Function<Integer,Person> fun2=(id)->{
            Person person=new Person();
            person.setId(id);
            person.setName("jzk");
            return person;
        };
        System.out.println(fun2.apply(1));

        System.out.println("Function lambel===参数传入======");
        dealFunc2(1,(id)->{
            Person person=new Person();
            person.setId(id);
            person.setName("jzk");
            return person;
        });

    }

    private void dealFunc2(Integer id,Function<Integer,Person> fun){
        Person person=fun.apply(id);
        System.out.println(person);

    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

示例2

@Test
    public void t16(){
        System.out.println("Function 匿名函数=========");
        Function<String,Integer> function = new Function<String,Integer>() {
            @Override
            public Integer apply(String s) {
                return s.length();
            }
        };
        System.out.println(function.apply("hello world"));

        System.out.println("Function lambel=========");
        Function<String,Integer> function2=(str)->{
            return str.length();
        };
        System.out.println(function2.apply("hello world"));

        System.out.println("Function lambel===方法参数======");
        fundeal("hello world",(s)-> {
            return s.length();
        }
        );


    }

    private void fundeal(String str,Function<String,Integer> func){
        System.out.println(func.apply(str));
    }



  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

BiFunction 允许传入两个形参,其他和Function保持一致

BiFunction 源码

@FunctionalInterface
public interface BiFunction<T, U, R> {

    R apply(T t, U u);
    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

测试代码

 @Test
    public void test31(){
        System.out.println("BiFunction匿名函数");
        BiFunction<String,String,String> bfunc=new BiFunction<>(){
            @Override
            public String apply(String first, String last) {
                return first+last;
            }
        };
        System.out.println(bfunc.apply("蒋", "增奎"));

        System.out.println("BiFunction lambda==========");
        bfunc=(first,last)->{
            return first+last;
        };
        System.out.println(bfunc.apply("蒋", "增奎"));

        //更简写
        bfunc=(first,last)-> first+last;
        System.out.println(bfunc.apply("蒋", "增奎"));
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

7.3.5 Predicate判断函数

Predicate(断言)的主要作用可以简单描述为:向其传入一个对象(可以理解为参数),将得到一个布尔值作为输出

源码:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    }
  • 1
  • 2
  • 3
  • 4

测试代码

 @Test
    public void t5(){
        //匿名函数
        Predicate<Integer> predicate=new Predicate<>(){
            @Override
            public boolean test(Integer i) {
                return i>0;
            }
        };
        System.out.println(predicate.test(2));
        //lambda
        predicate=(Integer i)->{return i>0;};
        //简写
        predicate=x->x>0;
        System.out.println(predicate.test(-2));

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

7.3.6 List/Map的forEach方法

List.forEach函数源码:

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

传入的是一个Consumer接口函数,看源码是循环获得
代码

@Test
    public void t8(){

        String[] array = {"aaaa", "bbbb", "cccc"};
        List<String> list = Arrays.asList(array);
        //引用静态方法,通过类名+::+方法名的方式
        //forEach传入的是Consumer接口
        list.forEach(
                (String s)->
                {
                    System.out.println(s);
                }
                );

        System.out.println("========简写");
        list.forEach(s-> System.out.println(s));
        System.out.println("=========简写==引用");
        list.forEach(System.out::println);

        System.out.println("复杂类型===============");
        List<Person> l=new ArrayList<>();
        l.add(new Person(1,"jzk"));
        l.add(new Person(2,"mike"));
        l.add(new Person(3,"smith"));
        l.forEach(person -> {
            if (person.getId()==1){
                System.out.println(person.getName());
            }
        });
        //引用函数
        System.out.println("复杂类型=========引用函数======");
        l.forEach(System.out::println);
    }

  @Test
    public void t88(){
        Map<Integer,Integer> map=new HashMap<>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        map.put(4, 4);
        map.forEach((x,y)->{
            System.out.println("x="+x+",y="+y);
        });
    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

7.3.7 内置函数总结

这两个接口方法很少,但是实用性很强,并且可以看成是一对逆运算.一个作为生产工厂生产对象(Supplier),另一个作为消费者去消费对象(Consumer). Function做一个转化,前输入后输出

函数原则
Supplier共给,无输入参数,生产类
Consumer消费,有输入,无返回,传入一个参数去消费它
Function输入一个参数,返回一个参数,输入输出做转化
Predicate输入一个参数,返回一个bool值

7.4 引用函数

jdk1.8它允许开发人员使用简洁的语法来引用现有的Java方法或构造函数。
函数引用可以看作是Lambda表达式的一种简化形式,它提供了一种更加简洁、易于阅读和维护的方式来编写代码。

函数引用的分类

  1. 静态方法引用:引用静态方法,例如:ClassName::staticMethodName

  2. 实例方法引用:引用实例方法,例如:object::instanceMethodName

  3. 构造函数引用:引用构造函数,例如:ClassName::new

7.4.1 引用类的静态方法 类名::静态方法名

语法:类名::方法名,
看输入输出参数,选择是Function/BiFunction、Supplier、Consumer/BiConsumer来接收

测试代码

public class YY {
    public static String getInfo1(Integer age){
        return "我的年龄:"+age;
    }

    public static void setAge(Integer age){
        System.out.println("传入参数:"+age);
    }

    public static Integer getAge(){
        return  Integer.valueOf("20");
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

引用静态类使用

@Test
    public void t14(){


        System.out.println("引用静态方法====================");
        //有输入输出,用Function
        System.out.println("(1)有输入输出,用Function");
        Function<Integer,String> fun1= YY::getInfo1;
        System.out.println(fun1.apply(20));

        System.out.println("(2)有输入无输出,Consumer");
        Consumer<Integer> consumer=YY::setAge;
        consumer.accept(20);

        System.out.println("(3)无输入有输出,Supplier");
        Supplier<Integer> supplier=YY::getAge;
        System.out.println("得到输出年龄:"+  supplier.get());

        /***
         sqrt是Math静态方法
         public static double sqrt(double a)
         */
        Function<Double, Double> sqrt = Math::sqrt;
        double result = sqrt.apply(4.0); // result = 2.0
        System.out.println(result);

    }
  • 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
  • 26
  • 27

7.4.2 实例方法引用 对象名::非静态方法名

语法: 对象名::非静态方法名
看输入输出参数,选择是Function/BiFunction、Supplier、Consumer/BiConsumer来接收

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private Integer id;
    private String name;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试代码

 @Test
    public void t20(){

        System.out.println("引用对象的非静态方法====================");
        //先创建一个对象
        YY yy=new YY();
        //有输入输出,用Function
        System.out.println("(1)有输入输出,用Function");
        Function<String,String> fun= yy::addXing;
        System.out.println(fun.apply("增奎"));

        System.out.println("(2)有输入无输出,Consumer");
        Consumer<String> consumer=yy::setName;
        consumer.accept("蒋增奎");

        System.out.println("(3)无输入有输出,Supplier");
        Supplier<String> supplier=yy::getName;
        System.out.println("得到输出姓名:"+  supplier.get());


    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

7.4.3 类引用普通方法 类名::普通方法(非静态)

语法:类名::普通方法(非静态),第一个入参必须是类的对象,所以接收函数只能是Consumer/Function
1.Consumer/Function<类名,…> cf=类名::普通方法名
(1) 如果是无返回值,用Consumer/BiConsumer
(2)如果有返回值,应Function/BiFunction
因为第一个入参必须是类的对象,有入参,就不可能为Supplier
2.调用
(1)无返回值
Consumer.accept(对象); 【无实际入参】
BiConsumer.accept(对象,真正入参);【有一个实际入参】
(2)有返回值
Function.apply(对象);【无实际入参】
Function.apply(对象,真正入参);【有一个实际入参】

初学者一时分不清【类名::普通方法(非静态)】和[类名::静态方法]区别,我们从代码上来说明

vo类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
    private Integer id;
    private String name;

    public String getIdName(String separator) {
        return id + separator + name;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

测试代码

@Test
    public void t21() {

        System.out.println("引用类的非静态方法====================");

        Person person=new Person(1,"jzk");
        System.out.println("(1)无入参无返回普通方法");
        //void noParamNoReturn() 无参无返回,但加上1个固定Person入参,成消费者接口,1个参数,用Consumer
        Consumer<Person> noParamNoReturn = Person::noParamNoReturn;
        noParamNoReturn.accept(person);

        System.out.println("(2)有一个入参数无返回普通方法");
        //setName(String) 1个入参,加上1个固定Person,两个入参,无返回,应该是消费这接口,用BiConsumer
        BiConsumer<Person, String> setName = Person::setName;
        setName.accept(person, "奎哥");
        System.out.println(person.getName());


        System.out.println("(3)无入参有返回普通方法");
        //有返回值,则肯定是Supplier或者Function,方法本身无入参,但加上1个固定Person,有一个入参应该是Function
        Function<Person, String> func = Person::getName;
        String str=func.apply(person);
        System.out.println(str);

        System.out.println("(4)有入参有返回普通方法");
        //String getIdName(String separator)  加上1个固定Person,有两个入参,有返回,应用用BiFunction
        BiFunction<Person, String, String> getIdName = Person::getIdName;
        String str2=getIdName.apply(person, "-");
        System.out.println(str2);


        /***
         总结:类:普通方法,第一入参必须是类的实例化对象,所以接收函数必然是Consumer/BiConsumer或者
         Function/BiFunction,不可能为Supplier
         */
    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

代码解析:

要使用类名::普通方法语法,必须把Person作为第一个入参传入,有入参就排除掉Supplier函数接口,再从是否有无返回值判断使用Consumner/BiConsumner还是Function/BiFunction

7.4.4 类引用构造函数 ::new

语法:
(1)无参构造函数
Supplier<类名> supplier = 类名::new;
实例化对象名=supplier.get() //执行构造函数实例化对象
(2)有参数构造函数,最多两个参数
Function<Integer,Person> function= Person::new;
只有1个参数的构造函数
BiFunction<Integer,String,Person> function= Person::new;
两个参数的构造函数

vo对象

@Data
public class Person {
    private Integer id;
    private String name;

    public Person() {
        System.out.println("无参构造函数 public Person()");
    }

    public Person(Integer id, String name) {
        this.id = id;
        this.name = name;
        System.out.println("有参构造函数Person(Integer id, String name) ");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

测试代码

   @Test
    public void t22() {
        //无参构造函数
        System.out.println("无参构造函数=============");
        Supplier<Person> supplier = Person::new;
        Person person=supplier.get();  //get时才执行

        System.out.println("有参构造函数=============");
        BiFunction<Integer,String,Person> function= Person::new;
        Person person1= function.apply(1, "jzk");

    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

7.6 总结

lambda的优点的代码简洁,问题可能开来代码易读性行差和难以调试,在功能性代码开发中慎用,我们学习的目的是看得懂别人写的框架性lambda代码

8. Stream流

8.1 Stream理解

那么Stream是什么?简短的定义就是“源中支持聚合操作的一系列元素”。让我们分解一下:

  • 元素序列:
    流为特定元素类型的序列值集提供接口。但是,流实际上并不存储元素。它们是按需计算的。
    源:流从提供数据的源(例如集合,数组或I / O资源)进行消耗。
  • 聚合操作:
    流支持像SQL一样功能的编程语言操作和常用的操作,如filter,map,reduce,find,match,sorted等。
    此外

流操作具有两个基本特征:

  • 流水线:
    许多流操作本身都会返回一个流。这允许将操作链接在一起以形成更大的管道。这使某些优化,如懒惰和短路,这是我们后来探索。
  • 内部迭代:
    与显式迭代的集合(外部迭代)相反,流操作为您在后台进行迭代。

什么是Stream流

Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如,“过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

而和迭代器又不同的是,Stream可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/Join框架(JSR166y)来拆分任务和加速处理过程。

Stream 的另外一大特点是,数据源本身可以是无限的。

stream的价值

  • 在传统的J2EE应用中,Java代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:
  • 客户每月平均消费金额
  • 最昂贵的在售商品
  • 本周完成的有效订单(排除了无效的)
  • 取十个数据样本作为首页推荐

在jdk1.8前,只能只有通过迭代器遍历循环所有遍历进行处理,而Stream提供了类似数据库相关的筛选统计功能

8.2 Stream语法详解

8.2.1 Stream流创建

创建方式说明
(1)从Collection和数组获得Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of()
(2)从BufferedReader获得java.io.BufferedReader.lines()
(3)静态工厂java.util.stream.IntStream.range()
java.nio.file.Files.walk()
(4)自己构建java.util.Spliterator
(5)其他Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()

代码

    @Test
    public void t1() throws FileNotFoundException {

        System.out.println("流的创建===============");
        System.out.println("1.java.util.Collection.stream()接口");
        //list集合 ArrtayList Vector  LinkedList
        List<Integer>  list= Arrays.asList(1,2,3,4);
        Stream<Integer> listStream=list.stream();

        //Set集合 HashSet LinkedSet  TreeSet
        Set<Integer> set=new HashSet<>();
        set.add(1);set.add(2);set.add(3);
        Stream<Integer> setStream=set.stream();

        System.out.println("2.java.util.Arrays.stream(T[] array)接口");
        Integer[] arrays={1,2,3,4};
        Stream<Integer>  arrayStream=Arrays.stream(arrays);

        System.out.println("3.Stream的静态方法 Stream.of等");
        Stream<Integer> ofStream=Stream.of(1,2,3,4,5);
        Stream.of(1);
        Stream.of(1,2,3);
        Stream.iterate(0,(e->e+1)).limit(5).forEach(e-> System.out.println(e));
        Stream.generate(()->Math.random()).limit(5).forEach(e-> System.out.println(e));

        System.out.println("4.从BufferedReader.lines()获得");
        File file = new File("d:\\a.txt");
        BufferedReader br=new BufferedReader(new FileReader(file));
        Stream<String> st=br.lines();

    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);
  • 1
  • 2
  • 3

8.2.2 流的转换

Stream也可以转换成对应的类型

转换类语法
转ListList list1=listStream.collect(Collectors.toList());
ArrayList list2=listStream.collect(Collectors.toCollection(ArrayList::new));
转setSet set1=setStream.collect(Collectors.toSet());
HashSet set2= setStream.collect(Collectors.toCollection(HashSet::new));
转数组Integer[] arrays1=arrayStream.toArray(Integer[]::new);
StackStack stack1 = stream.collect(Collectors.toCollection(Stack::new));
@Test
    public void t2() throws FileNotFoundException {

        System.out.println("1-1.Stream转换为List===============");
        //list集合 ArrtayList Vector  LinkedList
        List<Integer>  list= Arrays.asList(1,2,3,4);
        Stream<Integer> listStream=list.stream();
        System.out.println("1-1.Stream转换为List");
        //直接转List接口
        List<Integer> list1=listStream.collect(Collectors.toList());
        list1.forEach(System.out::println);
        //转子类
        ArrayList<Integer> list2=listStream.collect(Collectors.toCollection(ArrayList::new));


        System.out.println("1-2.Stream转换为Set");
        //Set集合 HashSet LinkedSet  TreeSet
        Set<Integer> set=new HashSet<>();
        set.add(1);set.add(2);set.add(3);
        Stream<Integer> setStream=set.stream();

        //直接转Set接口
        Set<Integer> set1=setStream.collect(Collectors.toSet());
        //转set子类
        HashSet<Integer> set2= setStream.collect(Collectors.toCollection(HashSet::new));


        System.out.println("2.java.util.Arrays.stream(T[] array)接口");
        Integer[] arrays={1,2,3,4};
        Stream<Integer>  arrayStream=Arrays.stream(arrays);


        Stream<String> strStream=Stream.of("1","2","3");
        String str = strStream.collect(Collectors.joining());
        System.out.println(str);

    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

8.2.3 流的操作API1-中间操作符

(一)中间操作符
通常对于Stream的中间操作,可以视为是源的查询,并且是懒惰式的设计,对于源数据进行的计算只有在需要时才会被执行,与数据库中视图的原理相似;

Stream流的强大之处便是在于提供了丰富的中间操作,相比集合或数组这类容器,极大的简化源数据的计算复杂度

一个流可以跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用

这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的 filter、map 等

说了这么多,中间操作流最直白的就是操作后还可以追加操作.map().filter().keep().sort().distinct()

方法说明举例
map修改数据流里的值Stream.of(1,2,3).map(n->n+1);//2,3,4
filter过滤数据流里的数据Stream.of(3,2,4).filter(i -> i % 2 == 0) //2,4
distinct去掉重复数据Stream.of(2,2,4).distinctt();//2,4
sorted排序Stream.of(3,2,4).sorted() //2,3,4
limit返回数据流里前几位Stream.of(1,2,3,4,6).limit(2);//1,2
skip忽略数据流里前几位Stream.of(1,2,3,4,6).skip(2);//3,4,6
keep遍历,但不改变流数据
1-1.map映射

接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”),说一句大白话就是改变循环变量的值。

源码:

  <R> Stream<R> map(Function<? super T, ? extends R> mapper);

  • 1
  • 2

测试代码1

 @Test
    public void t3(){
        List<Integer> list=Arrays.asList(1,2,3);
        Stream<Integer> stream1= list.stream();
        //元素映射成另外一个元素,返回流
        Stream<Integer> stream2=stream1.map((x)->{return x*x;});
        stream2.forEach(x-> System.out.println(x));
        //简写
        System.out.println("====简写==========");
        List<Integer> list2= list.stream().map(n->n*n).collect(Collectors.toList());
        list2.forEach(System.out::println); //1,4,9
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

测试代码2:改变循环因子数据类型

 List<Person> list2=new ArrayList<>();
        list2.add(new Person(1,"jzk"));
        list2.add(new Person(2,"mimi"));
        list2.add(new Person(3,"mike"));

        Stream<String> stream1= list2.stream().map(Person::getName);
        stream1.forEach(System.out::println);//jzk,mimi,mike
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

代码分析:
这里stream流里的循环因子已经变成了字符串,Person::getName输出的是字符串
Person::getName 等价于 person->person.getName;

1-2.filter过滤

filter对原始Stream进行某项过滤,通过过滤的元素被留下来生成一个新Stream。

源代码:其参数是一个判断Predicate内置函数接口

  Stream<T> filter(Predicate<? super T> predicate);
  • 1

测试代码1

@Test
    public void t4(){
        List<Integer> list=Arrays.asList(1,2,3,4,5,6,7,8);
        Stream<Integer> stream1= list.stream();
        //过滤掉循环因子的一些条件
        //大于2的偶数
        Stream<Integer> stream2=stream1.filter(n->n>2).filter(n->n%2==0);
        stream2.forEach(x-> System.out.println(x)); //4,6,8
        //简写
        System.out.println("====简写==========");

        List<Integer> list2=list.stream().filter(n->n>2).filter(n->n%2==0).collect(Collectors.toList());
        list2.forEach(System.out::println);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

测试代码2

  @Test
    public void t6(){
        List<Person> list=new ArrayList<>();
        list.add(new Person(1,"jzk"));
        list.add(new Person(2,"smith"));
        list.add(new Person(3,"mike"));
        list.add(new Person(4,"mimi"));
        //过滤id>2的
        List<Person> list2=list.stream().filter(person -> person.getId()>2).collect(Collectors.toList());
        list2.forEach(System.out::println);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
1-3.distinct去重

返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流

源代码:

 Stream<T> distinct();
  • 1

测试代码

@Test
    public void t7(){
        Stream<Integer> stream=Stream.of(1,1,2,3,4,5);
        stream.distinct().forEach(System.out::println);  //1,2,3,4,5

        List<Person> list=new ArrayList<>();
        list.add(new Person(1,"jzk"));
        list.add(new Person(1,"jzk"));
        list.add(new Person(2,"mike"));
        list.stream().distinct().forEach(System.out::println);  //只剩下jzk和mike


		List<Person> list2=new ArrayList<>();
        list2.add(new Person(1,"jzk"));
        list2.add(new Person(2,"jzk"));
        list2.add(new Person(3,"mike"));
        
        list2.stream().map(Person::getName).distinct().forEach(System.out::println);  	//只剩下jzk和mike

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
1-4. sorted排序

源码:

  Stream<T> sorted();
  Stream<T> sorted(Comparator<? super T> comparator);
  • 1
  • 2

1.如果流里的数据仅仅是简单的数字类型,正序直接用 stream.sorted()即可,倒序用Stream.sorted(Comparator.reverseOrder())
2.如果流里的数据是一个复杂对象,正序则要用到sorted(Comparator.comparing(对象类名::getId)),倒序则Comparator增加.reversed()

测试代码

  @Test
    public void t8(){
        //1.stream.sorted() 直接比较循环因子的大小
        System.out.println("1.stream.sorted() 直接比较循环因子的大小");
        Stream<Integer> stream=Stream.of(7,8,2,4,5,3,6);
        //正序
        System.out.println("stream.sorted()正序");
        stream.sorted().forEach(System.out::println);//2,3,4,5,6,7,8
        //倒序
        System.out.println("stream.sorted(Comparator.reverseOrder())倒序");
        Stream.of(7,8,2,4,5,3,6).sorted(Comparator.reverseOrder()).forEach(System.out::println);//8,7,6,5,4,3,2


        List<Person> list=new ArrayList<>();
        list.add(new Person(3,"jzk"));
        list.add(new Person(1,"mimi"));
        list.add(new Person(2,"dong"));
        list.add(new Person(4,"lisi"));

        //2、Stream.sorted(Comparator):排序,根据id正序
        System.out.println("2、Stream.sorted(Comparator):排序,根据id正序");
        Stream<Person> stream1=list.stream().sorted(Comparator.comparing(Person::getId));
        stream1.forEach(System.out::println);

        //3、Stream.sorted(Comparator):排序,根据id倒序
        System.out.println("3、Stream.sorted(Comparator):排序,根据id倒序");
        Stream<Person> stream2=list.stream().sorted(Comparator.comparing(Person::getId).reversed());
        stream2.forEach(System.out::println);


    }
  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
1-5.limit长度限制

取流里前几条数据

源码:

Stream<T> limit(long maxSize);
  • 1

eg:

 @Test
    public void t9(){
        //前2条数据
        Stream.of(1,2,3,4,6).limit(2).forEach(System.out::println);  //1.2
    }
  • 1
  • 2
  • 3
  • 4
  • 5
1-6.skip忽略

返回一个扔掉了前n个元素的流

源码

  Stream<T> skip(long n);
  • 1

eg:

@Test
    public void t10(){
        //忽略前几条数据
        Stream.of(1,2,3,4,6).skip(2).forEach(System.out::println);  //3.4.5.6
    }
  • 1
  • 2
  • 3
  • 4
  • 5
1-7.keep调试遍历

遍历流里数据,注意这个方法更多是用于调试查看流里的数据,并无法改变流里的数据

keep()返回的是Stream,不是终端输出,forEach是终端输出遍历,返回的是void

源码:入参是流里的循环因子

 Stream<T> peek(Consumer<? super T> action);
  • 1

无返回值

   void forEach(Consumer<? super T> action);
  • 1

map是Function有返回值,代替以前的数据

  <R> Stream<R> map(Function<? super T, ? extends R> mapper);
  • 1

测试代码:

  @Test
    public void t11(){
        Stream<Integer> stream=Stream.of(1,2,3,4,5);
        Stream<Integer> stream1=stream.peek(n->n=n+2);

        stream1.forEach(System.out::println); //依然输出1,2,3,4,5,没有改变

        //这里改变了,是因为参数传的是地址
        System.out.println("====传址改变========");
        List<Person> list=new ArrayList<>();
        list.add(new Person(1,"jzk"));
        list.add(new Person(2,"mimi"));
        list.add(new Person(3,"dong"));
        list.add(new Person(4,"lisi"));
        list.stream().peek(person -> person.setId(person.getId()+1)).forEach(System.out::println);//person.id=2,3,4,5
        

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

分析代码:

list.stream().peek(
(person) ->
{
	 person.setId(person.getId()+1);   //传入的person是数据流,但形参是对象,所以是传址
 }
 );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

8.2.4 流的操作API2-终端操作符

Stream流执行完终端操作之后,无法再执行其他动作,否则会报状态异常,提示该流已经被执行操作或者被关闭,想要再次执行操作必须重新创建Stream流

一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。

终端操作的执行,才会真正开始流的遍历。如 count、collect 等

方法含义例子
collect收集器,将流转换为其他形式List strings = Arrays.asList(“cv”, “abd”, “aba”, “efg”, “abcd”,“jkl”, “jkl”);
Set set = strings.stream().collect(Collectors.toSet());
List list = strings.stream().collect(Collectors.toList());
Map<String, String> map = strings.stream().collect(Collectors.toMap(v ->v.concat(“_name”), v1 -> v1, (v1, v2) -> v1));
forEach遍历流strings.stream().forEach(s -> out.println(s));
findFirst返回第一个元素Optional first = strings.stream().findFirst();
String str=first.get();
findAny随机返回任意元素Optional any = strings.stream().findAny();
count返回流中元素总数long count = strings.stream().count();
sum求和int sum = userList.stream().mapToInt(User::getId).sum();
max/min求最大/最小元素User user=userList.stream().max(Comparator.comparingInt(User::getId)).get();
User user=userList.stream().min(Comparator.comparingInt(User::getId)).get()
anyMatch
allMatch
noneMatch
元素匹配情况1.任意元素匹配
boolean b = strings.stream().anyMatch(s -> s == “abc”);
2.所有元素都匹配
boolean b = strings.stream().allMatch(s -> s == “abc”);
3.所有元素都不匹配
boolean b = strings.stream().noneMatch(s -> s == “abc”);
2-1 collect转换

把Stream转化成List、Set、数组

//1、collect:收集器,将流转换为其他形式
        Set<Person> set = personList.stream().collect(Collectors.toSet());
        set.forEach(System.out::println);
        System.out.println("--------------------------");
        List<Person> list = personList.stream().collect(Collectors.toList());
        list.forEach(System.out::println);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
2-2 forEach遍历

输出数据流里的数据

源码:

default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试

 		
 		 List<Person> list=new ArrayList<>();
        list.add(new Person(1,"jzk"));
        list.add(new Person(2,"smith"));
        list.add(new Person(3,"mike"));
        list.add(new Person(4,"mimi"));
        
        list.stream().forEach(person -> {
            System.out.println(person);
        });
        //简写
        list.stream().forEach(System.out::println);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
2-3 findFirst第一条数据

Stream.findFirst().get() //得到第一条数据

源码

 Optional<T> findFirst();
  • 1

测试

@Test
    public void t12(){
      Integer ret=Stream.of(1,2,3,4).findFirst().get(); //1
      System.out.println(ret);

 	List<Person> list=getList();
    Person person=list.stream().findFirst().get();
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
2-4 findAny 随机

Stream.findAny().get() 任意一条数据

//4、findAny:将返回当前流中的任意元素
User findUser = userList.stream().findAny().get();
User findUser1 = userList.stream().filter(user -> "上海".equals(user.getCity())).findAny().get();

  • 1
  • 2
  • 3
  • 4
2-5 count 数据记录数

Stream.count()获得流里的记录条数

测试代码:

		//数据流里的记录条数
        long num=Stream.of(1,2,3,4).filter(n->n>2).count();
        System.out.println("Stream.of(1,2,3,4).filter(n->n>2).count()="+num);
        //对象里数据来过滤
        num=getList().stream().filter(person1 -> person1.getId()>3).count();
        System.out.println("getList().stream().filter(person1 -> person1.getId()>3).count()="+num);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
2-6 sum 求和

IntStream.sum() 整数/Double/Long的数据流之和
注意:Stream无Sum()函数,需要用mapInt/Double/Long来转化
测试代码:

 @Test
    public void t13(){
        
        //Stream<Integer>要先转化成IntStream
       long num= Stream.of(1,2,3,4).mapToInt(n->n).sum(); //10
        System.out.println(num);
        num= IntStream.of(1,2,3,100).sum(); //106
        System.out.println(num);
        //计算流循环对象.id之和
       num= getList().stream().mapToInt(Person::getId).sum();//15
        System.out.println(num);
    }

  private List<Person> getList(){
        List<Person> list=new ArrayList<>();
        list.add(new Person(1,"jzk"));
        list.add(new Person(2,"mimi"));
        list.add(new Person(3,"dong"));
        list.add(new Person(4,"lisi"));
        list.add(new Person(5,"zs"));
        return list;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
2-7 max /min最大最小

从数据流里选中最大/最小值的记录数出来,入参是一个Comparator函数接口
注意:返回值是一个 Optional ,要用get()或者想要的数据

源码:

 Optional<T> max(Comparator<? super T> comparator);
 Optional<T> min(Comparator<? super T> comparator);
  • 1
  • 2

max测试代码

 @Test
    public void t14(){
        //Stream是数字流
        Integer num=Stream.of(1,2,3,4).max(Comparator.naturalOrder()).get();
        System.out.println(num); //4
        //Stream是对象流,某个对象里面字段最大值
        Person person=getList().stream().max(Comparator.comparingInt(Person::getId)).get();
        System.out.println(person);//Person(id=5, name=zs)

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

min测试代码

  @Test
    public void t15(){
        //Stream是数字流
        Integer num=Stream.of(1,8,3,4).min(Comparator.naturalOrder()).get();
        System.out.println(num); //1
        //Stream是对象流,某个对象里面字段最大值
        Person person=getList().stream().min(Comparator.comparingInt(Person::getId)).get();
        System.out.println(person);//Person(id=1, name=jzk)

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
2-8 anyMatch/allMatch/noneMatch匹配检查

anyMatch/allMatch/noneMatch来表示数据流的匹配度,入参都是一个predicate函数接口,返回值都是一个布尔值
1.anyMatch: 只要任意一条数据符合predicate表达式,则为true
2.allMatch: 所有数据符合predicate表达式,则为true
3.noneMatch:所有数据都不符合predicate表达式,则为true

源代码

	boolean anyMatch(Predicate<? super T> predicate);
    boolean allMatch(Predicate<? super T> predicate);
    boolean noneMatch(Predicate<? super T> predicate);
  • 1
  • 2
  • 3

测试代码

@Test
    public void t16(){
        //anyMatch只要有就返回true
        Stream<String> stream=Stream.of("蒋增奎","蒋天豪","蒋大爷");
        //只要数据流里的任何一条数据包含有“奎”,则为true
        boolean b=stream.anyMatch(s-> s.contains("奎"));//contains 相当于sql like  如果用equals()则是sql的"="
        System.out.println(b);//true

        List<Person> list=new ArrayList<>();
        list.add(new Person(1,"jzk1"));
        list.add(new Person(2,"mimi1"));
        list.add(new Person(3,"dong1"));
        list.add(new Person(4,"lisi1"));
        list.add(new Person(5,"zs1"));
        b=list.stream().anyMatch(person -> person.getName().contains("j"));
        System.out.println(b);//true

        //只要数据流里的所有数据包含有“奎”,则为true
        b=Stream.of("蒋增奎","蒋天豪","蒋大爷").allMatch(s-> s.contains("蒋"));
        System.out.println(b);//true

        b=list.stream().allMatch(person -> person.getName().contains("1"));
        System.out.println(b);//true
        //noneMatch所有循环因子都没有包含,则为true
        b=Stream.of("蒋增奎","蒋天豪","蒋大爷").noneMatch(s-> s.contains("日天"));
        System.out.println(b);//true
    }
  • 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
  • 26
  • 27

8.2.5 Collect收集器

Collector:结果收集策略的核心接口,具备将指定元素累加存放到结果容器中的能力;并在Collectors工具中提供了Collector接口的实现类,其核心点是在collect(Collector参数里)

Collector参数说明
Collectors.toList()转List
Collectors.toMap(k,v)转map
Collectors.toSet()转set
Collectors.counting()流里总元素数
Collectors.summingInt(User::getId)求和
Collectors.minBy(Comparator.comparingInt(User::getId)最大
Collectors.minBy(Comparator.comparingInt(User::getId))最小
Collectors.joining(“-”)字符串连接
(Collectors.groupingBy(User::getCity)map分组

源码:

 <R, A> R collect(Collector<? super T, A, R> collector);
  • 1
3-1 toList-转List

将用户ID存放到List集合中

List<Integer> idList = userList.stream().map(User::getId).collect(Collectors.toList()) ;
  • 1
3-2 toMap-转map

将用户ID和Name以Key-Value形式存放到Map集合中

Map<Integer,String> userMap = userList.stream().collect(Collectors.toMap(User::getId,User::getName));
  • 1
3-3 toSet-转set

将用户所在城市存放到Set集合中

Set<String> citySet = userList.stream().map(User::getCity).collect(Collectors.toSet());
  • 1
3-4 counting -总记录数

符合条件的用户总数

long count = userList.stream().filter(user -> user.getId()>1).collect(Collectors.counting());
  • 1
3-5 summingInt -求和

对结果元素即用户ID求和

Integer sumInt = userList.stream().filter(user -> user.getId()>2).collect(Collectors.summingInt(User::getId)) ;
  • 1
3-6 minBy- 最小

筛选元素中ID最小的用户

User maxUser = userList.stream().collect(Collectors.minBy(Comparator.comparingInt(User::getId))).get() ;
  • 1

3-7 joining -连接

将用户所在城市,以指定分隔符链接成字符串;

String joinCity = userList.stream().map(User::getCity).collect(Collectors.joining("||"));
  • 1

3-8 groupingBy-分组map

按条件分组,以城市对用户进行分组;

Map<String,List<User>> groupCity = userList.stream().collect(Collectors.groupingBy(User::getCity));
  • 1

8.3Int/Long/DoubleStream

对于基本数值型,目前有三种对应的包装类型Stream:IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long>和Stream<Double>,但是boxing/unboxing会很耗时,所以特别为这三种基本数值型提供了对应的Stream。

IntStream、LongStream、DoubleStream他们的语法一致的,我们只讲一种IntStream

8.3.1 创建IntStream

多种常见方式

IntStream.of

 public void t17(){
        // 支持一个参数,也支持可变参数
        IntStream intStream1 = IntStream.of(1);
        IntStream intStream2 = IntStream.of(1,2,3,4,5,6,7);
  }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

IntStream.builder()方式 (了解即可)

 // 1.可以使用add()方法添加元素
        IntStream builderStream= IntStream.builder().add(111).add(222).add(333).add(444).build();
       // 2.也可以使用accept()方式添加,add()方法底层也是通过accept()方式添加的(这种方式不支持链式调用,此处了解一下即可)
        IntStream.Builder builder = IntStream.builder();
        builder.accept(3);
        builder.accept(4);
        builder.accept(5);
        IntStream builderStream1 = builder.build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. IntStream.range()、IntStream.rangeClosed()方式

将指定范围内的元素都添加到int流中
range()前者不包含最后一个元素,即:前闭后开区间;
rangeClosed()后者包含,即:前闭后闭区间;

// 支持一个参数,也支持可变参数
        IntStream range1 = IntStream.range(1, 100);    // 返回 1,2,3,4,......,99
        IntStream range2 = IntStream.rangeClosed(1, 100);    // 返回 1,2,3,4,......,99,100

  • 1
  • 2
  • 3
  • 4
  1. IntStream.generate()方式 (了解即可)
// 1.这种方式,是无限连续的流,会源源不断的生成100内的随机数(持续不断输出)
IntStream generate1 = IntStream.generate(() -> new Random().nextInt(100));
// 2.使用limit()会限制流中元素的数量
IntStream generate1 = IntStream.generate(() -> new Random().nextInt(100)).limit(6);

  • 1
  • 2
  • 3
  • 4
  • 5
  1. IntStream.iterate()方式

指定生成int流中int元素的生成函数,前者的生成函数没有入参,后者会将前一次调用结果作为下一次调用生成函数的入参
 第一个参数seed,就是第一次调用生成函数的入参

// 规则:生成2的幂次方int流
// 根据指定规则,生成一串int流(使用iterate()方式,也是无限连续的流,需使用limit()来限制元素的数量)
IntStream intStream11 = IntStream.iterate(1,x->{
	int a = 2 * x;
	return a;
}).limit(6);
// 简写:
IntStream intStream11 = IntStream.iterate(1,x->2 * x).limit(6);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  1. IntStream.concat()方式 (将两个int流合并成一个流)
// 将A流和B流合并,生成新流
IntStream range1 = IntStream.range(1, 5);   // A流
IntStream range2 = IntStream.range(10, 15);   // B流
IntStream concat = IntStream.concat(range1, range2);   // 合并后的流
  • 1
  • 2
  • 3
  • 4

8.3.2 IntStream独有的方法

mapToObj / mapToLong / mapToDouble / asLongStream / asDoubleStream类型转化
sum / min / max / count / average / summaryStatistics计算
boxed转化成Stream<>
toArray转化成数组

8.3.3 mapToObj / mapToLong / mapToDouble / asLongStream / asDoubleStream

1.mapToObj:将 int 流转换成其他类型的流 (其他类型:可以使自定义对象类型,也可以是List类型等)
2.mapToLong:将 int 流转换成Long类型的流,不可指定返回值规则  (挺简单的,此处不做demo演示)
3.mapToDouble:将 int 流转换成Double类型的流,不可指定返回值规则  (挺简单的,此处不做demo演示)
4.asLongStream:将 int 流转换成Long类型的流,该方法遵循标准的int到指定类型的转换规则,不能指定规则,比如说将int流对象先 乘以2再返回  (挺简单的,此处不做demo演示)
5.asDoubleStream:将 int 流转换成Double类型的流,该方法遵循标准的int到指定类型的转换规则,不能指定规则,比如说将int流对象先 乘以2再返回  (挺简单的,此处不做demo演示)

// mapToObj()方法演示
@Test
public void test06() {
    IntStream intStream = IntStream.range(1, 10);
    // mapToObj():将int流转换成Person对象
    intStream.mapToObj(val -> new Person(val, "Mary" + val))
            .forEach(System.out::println);
}

// Person类
public class Person {

    private int id;

    private String name;

    public Person(int id, String name) {
        this.id = id;
        this.name = name;
    }

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

  • 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
  • 26
  • 27
  • 28
  • 29
  • 30

8.3.4 sum / min / max / count / average / summaryStatistics

sum:求和
min:求最小值
max:求最大值
count:计算IntStream流中元素个数
average:求平均值
summaryStatistics:该方法一次调用获取上述5个属性值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@Test
public void test11() {
    IntStream intStream = IntStream.of(1, 9, 4, 35, 5, 2);
    // sum:求和
    int sum = intStream.sum();
    System.out.println("sum -> " + sum);
    
    // min:最小值
    IntStream intStream1 = IntStream.of(1, 9, 4, 35, 5, 2);
    OptionalInt min = intStream1.min();
    System.out.println("min -> " + min.getAsInt());
    
    // max:最大值
    IntStream intStream2 = IntStream.of(1, 9, 4, 35, 5, 2);
    OptionalInt max = intStream2.max();
    System.out.println("max -> " + max.getAsInt());
    
    // count:统计流中元素个数
    IntStream intStream3 = IntStream.of(1, 9, 4, 35, 5, 2);
    long count = intStream3.count();
    System.out.println("count -> " + count);
    
    // average:统计流中元素个数
    IntStream intStream4 = IntStream.of(1, 9, 4, 35, 5, 2);
    OptionalDouble average = intStream4.average();
    System.out.println("average -> " + average.getAsDouble());
    
    // summaryStatistics:汇总方法,一次性返回sum/min/max/count/average值
    // average:统计流中元素个数
    IntStream intStream5 = IntStream.of(1, 9, 4, 35, 5, 2);
    IntSummaryStatistics intSummaryStatistics = intStream5.summaryStatistics();
    System.out.println("summaryStatistics -> " + intSummaryStatistics.toString());
    System.out.println("summaryStatistics.sum -> " + intSummaryStatistics.getSum());
    System.out.println("summaryStatistics.min -> " + intSummaryStatistics.getMin());
    System.out.println("summaryStatistics.max -> " + intSummaryStatistics.getMax());
    System.out.println("summaryStatistics.count -> " + intSummaryStatistics.getCount());
    System.out.println("summaryStatistics.average -> " + intSummaryStatistics.getAverage());
}

  • 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
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

8.3.5 boxed

oxed:翻译过来是盒子被包装的意思。即:返回由IntStream流中的元素组成的Stream流,每个box 都装着Integer类
在Stream流中,是没有boxed()方法的;
只有在IntStream、DoubleStream、LongStream 这三种类型的流中,才有boxed()方法

源码

@Override
public final Stream<Integer> boxed() {
    return mapToObj(Integer::valueOf);
}

  • 1
  • 2
  • 3
  • 4
  • 5

测试代码

  @Test
    public  void t18(){

            // boxed:返回一个流中元素的包装类的流(即:将IntStream转成Stream<Integer>类型)

            // 1.通过mapToObj()方法的方式返回Stream<xxx>类型
            IntStream intStream = IntStream.of(1, 9, 4, 35, 5, 2);
            Stream<Integer>  stream=intStream.mapToObj(Integer::valueOf);

            // 2.通过boxed()方法的方式返回Stream<Integer>类型
            IntStream intStream1 = IntStream.of(1, 9, 4, 35, 5, 2);
            Stream<Integer> boxed = intStream1.boxed();  // 看这里,通过boxed返回的是Stream<Integer>类型
          
        
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

8.3.6 toArray

toArray:将IntStream流中的元素转换成一个数组

@Test
public void test19() {
    IntStream intStream = IntStream.of(1, 9, 4, 35, 5, 2);
    // toArray:将IntStream流中的元素转换成一个数组
    int[] intArray = intStream.toArray();
    System.out.println(Arrays.toString(intArray));
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/662350
推荐阅读
相关标签
  

闽ICP备14008679号