函数式接口+Stream流

huanjing

一、函数式接口

1.1、函数式接口概述

  • 概念

    有且仅有一个抽象方法的接口

  • 如何检测一个接口是不是函数式接口

    @FunctionalInterface

    放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败

  • 注意事项

    我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算我不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解

1.2、函数式接口作为方法的参数

package com.jdk8.functionalinterface;

public class RunnableDemo {
    public static void main(String[] args) {
        // 在主方法中调用startThread方法

        // 匿名内部类的方式
        startThread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "线程启动了");
            }
        });

        // Lambda方式
        startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了"));

    }

    private static void startThread(Runnable r) {
        new Thread(r).start();
    }
}

image-20240811195510038

1.3、函数式接口作为方法的返回值

需求描述

定义一个类(ComparatorDemo),在类中提供两个方法

一个方法是:Comparator getComparator() 方法返回值Comparator是一个函数式接口

一个方法是主方法,在主方法中调用getComparator方法

package com.jdk8.functionalinterface;

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

public class ComparatorDemo {
    public static void main(String[] args) {
        // 定义集合,存储字符串元素
        ArrayList<String> array = new ArrayList<String>();

        array.add("cccc");
        array.add("aa");
        array.add("b");
        array.add("ddd");

        System.out.println("排序前:" + array);

        Collections.sort(array, getComparator());

        System.out.println("排序后:" + array);

    }

    private static Comparator<String> getComparator() {
        // 匿名内部类的方式实现
//        return new Comparator<String>() {
//            @Override
//            public int compare(String s1, String s2) {
//                return s1.length()-s2.length();
//            }
//        };
        
		// Lambda方式实现
        return (s1, s2) -> s1.length() - s2.length();
    }
}

image-20240811195735563

1.4、常用函数式接口之Supplier

  • Supplier接口

    Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用。

  • 常用方法

    只有一个无参的方法

    方法名说明
    T get()按照某种实现逻辑(由Lambda表达式实现)返回一个数据
package com.jdk8.functionalinterface;

import java.util.function.Supplier;

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

        String s = getString(() -> "林青霞");
        System.out.println(s);

        Integer i = getInteger(() -> 30);
        System.out.println(i);
    }

    // 定义一个方法,返回一个整数数据
    private static Integer getInteger(Supplier<Integer> sup) {
        return sup.get();
    }

    // 定义一个方法,返回一个字符串数据
    private static String getString(Supplier<String> sup) {
        return sup.get();
    }

}

image-20240811195926451

案例需求

定义一个类(SupplierTest),在类中提供两个方法

一个方法是:int getMax(Supplier sup) 用于返回一个int数组中的最大值

一个方法是主方法,在主方法中调用getMax方法

package com.jdk8.functionalinterface;

import java.util.function.Supplier;

public class SupplierTest {
    public static void main(String[] args) {
        // 定义一个int数组
        int[] arr = {19, 50, 28, 37, 46};

        int maxValue = getMax(() -> {
            int max = arr[0];

            for (int i = 1; i < arr.length; i++) {
                if (arr[i] > max) {
                    max = arr[i];
                }
            }

            return max;
        });

        System.out.println(maxValue);
    }

    // 返回一个int数组中的最大值
    private static int getMax(Supplier<Integer> sup) {
        return sup.get();
    }
}

image-20240811200224810

1.5、常用函数式接口之Consumer

  • Consumer接口

    Consumer接口也被称为消费型接口,它消费的数据的数据类型由泛型指定

  • 常用方法

    Consumer:包含两个方法

    方法名说明
    void accept(T t)对给定的参数执行此操作
    default Consumer andThen(Consumer after)返回一个组合的Consumer,依次执行此操作,然后执行 after操作
package com.jdk8.functionalinterface;

import java.util.function.Consumer;

public class ConsumerDemo {
    public static void main(String[] args) {
        // 操作一
        operatorString("林青霞", s -> System.out.println(s));
        // 操作二
        operatorString("林青霞", s -> System.out.println(new StringBuilder(s).reverse().toString()));

        System.out.println("--------");
        // 传入两个操作使用andThen完成
        operatorString("林青霞", s -> System.out.println(s), s -> System.out.println(new StringBuilder(s).reverse().toString()));
    }

    // 定义一个方法,用不同的方式消费同一个字符串数据两次
    private static void operatorString(String name, Consumer<String> con1, Consumer<String> con2) {
//        con1.accept(name);
//        con2.accept(name);
        con1.andThen(con2).accept(name);
    }

    // 定义一个方法,消费一个字符串数据
    private static void operatorString(String name, Consumer<String> con) {
        con.accept(name);
    }
}

image-20240811200412880

案例需求

String[] strArray = {“林青霞,30”, “张曼玉,35”, “王祖贤,33”};

字符串数组中有多条信息,请按照格式:“姓名:XX,年龄:XX"的格式将信息打印出来

要求:

把打印姓名的动作作为第一个Consumer接口的Lambda实例

把打印年龄的动作作为第二个Consumer接口的Lambda实例

将两个Consumer接口按照顺序组合到一起使用

package com.jdk8.functionalinterface;

import java.util.function.Consumer;

public class ConsumerTest {
    public static void main(String[] args) {
        String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33"};

        printInfo(strArray, str -> System.out.print("姓名:" + str.split(",")[0]),
                str -> System.out.println(",年龄:" + Integer.parseInt(str.split(",")[1])));
    }

    private static void printInfo(String[] strArray, Consumer<String> con1, Consumer<String> con2) {
        for (String str : strArray) {
            con1.andThen(con2).accept(str);
        }
    }
}

image-20240811200533444

1.6、常用函数式接口之Predicate

Predicate接口

Predicate接口通常用于判断参数是否满足指定的条件

常用方法

方法名说明
boolean test(T t)对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
default Predicate negate()返回一个逻辑的否定,对应逻辑非
default Predicate and(Predicate other)返回一个组合判断,对应短路与
default Predicate or(Predicate other)
package com.jdk8.functionalinterface;

import java.util.function.Predicate;

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午8:14
 * @Description:
 */

public class PredicateDemo {
    public static void main(String[] args) {
        m1();
        System.out.println("---------------");
        m2();
    }

    public static void m2() {
        boolean b1 = checkString("hello", s -> s.length() > 8);
        System.out.println(b1);
        boolean b2 = checkString("helloworld", s -> s.length() > 8);
        System.out.println(b2);

        boolean b3 = checkString("hello", s -> s.length() > 8, s -> s.length() < 15);
        System.out.println(b3);

        boolean b4 = checkString("helloworld", s -> s.length() > 8, s -> s.length() < 15);
        System.out.println(b4);
    }

    // 同一个字符串给出两个不同的判断条件,最后把这两个判断的结果做逻辑与运算的结果作为最终的结果
    private static boolean checkString(String s, Predicate<String> pre1, Predicate<String> pre2) {
        return pre1.or(pre2).test(s);
    }

    // 判断给定的字符串是否满足要求
    private static boolean checkString(String s, Predicate<String> pre) {
        return pre.test(s);
    }

    public static void m1() {
        boolean b1 = checkString_m1("hello", s -> s.length() > 8);
        System.out.println(b1);

        boolean b2 = checkString_m1("helloworld", s -> s.length() > 8);
        System.out.println(b2);

    }

    // 判断给定的字符串是否满足要求
    private static boolean checkString_m1(String s, Predicate<String> pre) {
//        return !pre.test(s);
        return pre.negate().test(s);
    }
}

image-20240811201906223

练习描述

  • String[] strArray = {“林青霞,30”, “柳岩,34”, “张曼玉,35”, “貂蝉,31”, “王祖贤,33”};

  • 字符串数组中有多条信息,请通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中,并遍历ArrayList集合

  • 同时满足如下要求:姓名长度大于2;年龄大于33

  • 分析

    • 有两个判断条件,所以需要使用两个Predicate接口,对条件进行判断

    • 必须同时满足两个条件,所以可以使用and方法连接两个判断条件

package com.jdk8.functionalinterface;

import java.util.ArrayList;
import java.util.function.Predicate;

public class PredicateTest {
    public static void main(String[] args) {
        String[] strArray = {"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"};

        ArrayList<String> array = myFilter(strArray, s -> s.split(",")[0].length() > 2,
                s -> Integer.parseInt(s.split(",")[1]) > 33);

        for (String str : array) {
            System.out.println(str);
        }
    }

    // 通过Predicate接口的拼装将符合要求的字符串筛选到集合ArrayList中
    private static ArrayList<String> myFilter(String[] strArray, Predicate<String> pre1, Predicate<String> pre2) {
        // 定义一个集合
        ArrayList<String> array = new ArrayList<String>();

        // 遍历数组
        for (String str : strArray) {
            if (pre1.and(pre2).test(str)) {
                array.add(str);
            }
        }

        return array;
    }
}

image-20240811202026465

1.7、常用函数式接口之Function

  • Function接口

    Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值

  • 常用方法

    方法名说明
    R apply(T t)将此函数应用于给定的参数
    default Function andThen(Function after)返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
package com.jdk8.functionalinterface;

import java.util.function.Function;

public class FunctionDemo {
    public static void main(String[] args) {
        // 操作一
        convert("100", s -> Integer.parseInt(s));
        // 操作二
        convert(100, i -> String.valueOf(i + 566));

        // 使用andThen的方式连续执行两个操作
        convert("100", s -> Integer.parseInt(s), i -> String.valueOf(i + 566));
    }

    // 定义一个方法,把一个字符串转换int类型,在控制台输出
    private static void convert(String s, Function<String, Integer> fun) {
//        Integer i = fun.apply(s);
        int i = fun.apply(s);
        System.out.println(i);
    }


    // 定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
    private static void convert(int i, Function<Integer, String> fun) {
        String s = fun.apply(i);
        System.out.println(s);
    }


    // 定义一个方法,把一个字符串转换int类型,把int类型的数据加上一个整数之后,转为字符串在控制台输出
    private static void convert(String s, Function<String, Integer> fun1, Function<Integer, String> fun2) {

        String ss = fun1.andThen(fun2).apply(s);
        System.out.println(ss);
    }

}

image-20240811202216842

练习描述

  • String s = “林青霞,30”;

  • 请按照我指定的要求进行操作:

    1:将字符串截取得到数字年龄部分

    2:将上一步的年龄字符串转换成为int类型的数据

    3:将上一步的int数据加70,得到一个int结果,在控制台输出

  • 请通过Function接口来实现函数拼接

package com.jdk8.functionalinterface;

import java.util.function.Function;

public class FunctionTest {
    public static void main(String[] args) {
        String s = "林青霞,30";
        convert(s, ss -> ss.split(",")[1], Integer::parseInt, i -> i + 70);
    }

    private static void convert(String s, Function<String, String> fun1, Function<String, Integer> fun2, Function<Integer, Integer> fun3) {
        int i = fun1.andThen(fun2).andThen(fun3).apply(s);
        System.out.println(i);
    }
}

image-20240811202309722

二、Stream流

在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

2.1、引言

传统集合的多步遍历代码

几乎所有的集合(如Collection接口或Map接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:

package com.jdk8.stream;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午4:54
 * @Description:
 */

public class StreamDemo01 {
    public static void main(String[] args) {
        m1();

    }


    public static void m() {}


    /**
     * 传统的方式遍历一个集合
     */
    public static void m1() {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        for (String name : list) {
            System.out.println(name);
        }
    }
}

image-20240811171558541

这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。

循环遍历的弊端

Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现:

  • for循环的语法就是“怎么做
  • for循环的循环体才是“做什么

为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

试想一下,如果希望对集合中的元素进行筛选过滤:

  1. 将集合A根据条件一过滤为子集B
  2. 然后再根据条件二过滤为子集C

那怎么办?在Java 8之前的做法可能为:

/**
     * 对集合中的元素进行筛选过滤
     */
    public static void m2() {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        List<String> zhangList = new ArrayList<>();
        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }

        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                shortList.add(name);
            }
        }

        for (String name : shortList) {
            System.out.println(name);
        }
    }

image-20240811171803889

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?**不是。**循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。

那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

Stream的更优写法

下面来看一下借助Java 8的Stream API,什么才叫优雅:

 /**
     * 我们使用Stream流
     */
    public static void m3() {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        list.stream()
                .filter(name -> name.startsWith("张"))
                .filter(name -> name.length() == 3)
                .forEach(System.out::println);
    }

image-20240811172109991

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

代码合集:

package com.jdk8.stream;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午4:54
 * @Description:
 */

public class StreamDemo01 {
    public static void main(String[] args) {
        // m1();
        // m2();
        m3();
    }

    /**
     * 我们使用Stream流
     */
    public static void m3() {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        list.stream()
                .filter(name -> name.startsWith("张"))
                .filter(name -> name.length() == 3)
                .forEach(System.out::println);
    }

    /**
     * 对集合中的元素进行筛选过滤
     */
    public static void m2() {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        List<String> zhangList = new ArrayList<>();
        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }

        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                shortList.add(name);
            }
        }

        for (String name : shortList) {
            System.out.println(name);
        }
    }


    /**
     * 传统的方式遍历一个集合
     */
    public static void m1() {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        for (String name : list) {
            System.out.println(name);
        }
    }
}

Stream流的好处

  • 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印

  • Stream流把真正的函数式编程风格引入到Java中

2.2、流式思想概述

注意:请暂时忘记对传统IO流的固有印象!

整体来看,流式思想类似于工厂车间的“生产流水线”。

image-20240811172433627

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。

image-20240811172448664

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。

这里的filtermapskip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。

2.3、Stream流的常见生成方式

生成Stream流的方式

  • Collection体系集合

    使用默认方法stream()生成流, default Stream stream()

  • Map体系集合

    把Map转成Set集合,间接的生成流

  • 数组

    通过Stream接口的静态方法of(T… values)生成流

说明:

方式1 : 根据Collection获取流

首先,java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流。

方式2 : 根据Map获取流

java.util.Map接口不是Collection的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况.

方式3 : 根据数组获取流

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of,使用很简单:

package com.jdk8.stream;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午5:28
 * @Description:
 */

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

    }

    /**
     * 数组:通过Stream接口的静态方法of(T... values)生成流
     */
    public static void m3() {
        String[] strArray = {"hello", "world", "java"};
        Stream<String> strArrayStream = Stream.of(strArray);
        Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
        Stream<Integer> intStream = Stream.of(10, 20, 30);
    }

    /**
     * Map体系集合:把Map转成Set集合,间接的生成流
     */
    public static void m2() {
        Map<String, String> map = new HashMap<>();
        Stream<String> keyStream = map.keySet().stream();
        Stream<String> valueStream = map.values().stream();
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }


    /**
     * Collection体系集合
     * 使用默认方法stream()生成流, default Stream<E> stream()
     */
    public static void m1() {
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();

        Vector<String> vector = new Vector<>();
        Stream<String> stream3 = vector.stream();
    }
}

备注:of方法的参数其实是一个可变参数,所以支持数组。

2.4、常用方法

流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

  • 终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括countforEach方法。
  • 非终结方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)

函数拼接与终结方法

在上述介绍的各种方法中,凡是返回值仍然为Stream接口的为函数拼接方法,它们支持链式调用;而返回值不再为Stream接口的为终结方法,不再支持链式调用。如下表所示:

方法名方法作用方法种类是否支持链式调用
count统计个数终结
forEach逐一处理终结
filter过滤函数拼接
limit取用前几个函数拼接
skip跳过前几个函数拼接
map映射函数拼接
concat组合函数拼接

备注:本小节之外的更多方法,请自行参考API文档。

2.4.1、非终结操作方法的使用

常见方法

方法名说明
Stream filter(Predicate predicate)用于对流中的数据进行过滤
Stream limit(long maxSize)返回此流中的元素组成的流,截取前指定参数个数的数据
Stream skip(long n)跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static Stream concat(Stream a, Stream b)合并a和b两个流为一个流
Stream distinct()返回由该流的不同元素(根据Object.equals(Object) )组成的流
Stream sorted()返回由此流的元素组成的流,根据自然顺序排序
Stream sorted(Comparator comparator)返回由该流的元素组成的流,根据提供的Comparator进行排序
Stream map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流
IntStream mapToInt(ToIntFunction mapper)返回一个IntStream其中包含将给定函数应用于此流的元素的结果
2.4.1.1、forEach : 逐一处理

虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的

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

该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。例如:

 /**
     * foreach逐一处理
     */
    public static void m1() {
        Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
        stream.forEach(s -> System.out.println(s));
    }

image-20240811184039972

2.4.1.2、filter:过滤

可以通过filter方法将一个流转换成另一个子集流。方法声明:

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

该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

基本使用

 /**
     * filter过滤方法的使用
     */
    public static void m2() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 需求1:把list集合中以张开头的元素在控制台输出
        list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求2:把list集合中长度为3的元素在控制台输出
        list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求3:把list集合中以张开头的,长度为3的元素在控制台输出
        list.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(System.out::println);

    }

image-20240811184345876

2.4.1.3、limit & skip

limit:取用前几个

limit方法可以对流进行截取,只取用前n个。方法签名:

Stream<T> limit(long maxSize);

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。

skip:跳过前几个

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

Stream<T> skip(long n);

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。

/**
     * limit&skip代码演示
     */
    public static void m3() {
        //创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        //需求1:取前3个数据在控制台输出
        list.stream().limit(3).forEach(System.out::println);
        System.out.println("------------------------------------");
        //需求2:跳过3个元素,把剩下的元素在控制台输出
        list.stream().skip(3).forEach(System.out::println);
        System.out.println("------------------------------------");
        //需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
        list.stream().skip(2).limit(2).forEach(System.out::println);
    }

image-20240811185006470

2.4.1.4、concat & distinct
/**
     * static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
     * Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流
     */
    public static void m4() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 需求1:取前4个数据组成一个流
        Stream<String> s1 = list.stream().limit(4);
        // 需求2:跳过2个数据组成一个流
        Stream<String> s2 = list.stream().skip(2);
        // 需求3:合并需求1和需求2得到的流,并把结果在控制台输出
        // Stream.concat(s1, s2).forEach(System.out::println);
        //会报错
        // 需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
        Stream.concat(s1, s2).distinct().forEach(System.out::println);
    }

image-20240811185737216

2.4.1.5、sorted
 /**
     * Stream<T> sorted()返回由此流的元素组成的流,根据自然顺序排序
     */
    public static void m5() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("linqingxia");
        list.add("zhangmanyu");
        list.add("wangzuxian");
        list.add("liuyan");
        list.add("zhangmin");
        list.add("zhangwuji");

        // 需求1:按照字母顺序把数据在控制台输出
        list.stream().sorted().forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求2:按照字符串长度把数据在控制台输出
        list.stream().sorted((s1, s2) -> {
            int num = s1.length() - s2.length();
            int num2 = num == 0 ? s1.compareTo(s2) : num;
            return num2;
        }).forEach(System.out::println);
    }

image-20240811190028342

2.4.1.6、map & mapToInt
 /**
     * <R> Stream<R> map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流
     * IntStream mapToInt(ToIntFunction mapper)返回一个IntStream其中包含将给定函数应用于此流的元素的结果
     */
    public static void m6() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("10");
        list.add("20");
        list.add("30");
        list.add("40");
        list.add("50");

        // 需求:将集合中的字符串数据转换为整数之后在控制台输出
        // list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);
        // list.stream().map(Integer::parseInt).forEach(System.out::println);
        // list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
        System.out.println("------------------------------------");
        // int sum() 返回此流中元素的总和
        int result = list.stream().mapToInt(Integer::parseInt).sum();
        System.out.println(result);
    }

image-20240811190355821

代码合集:

package com.jdk8.stream;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午6:35
 * @Description:
 */

public class StreamDemo03 {
    public static void main(String[] args) {
        // m1();
        // m2();
        // m3();
        // m4();
        // m5();
        m6();
    }


    /**
     * <R> Stream<R> map(Function mapper)返回由给定函数应用于此流的元素的结果组成的流
     * IntStream mapToInt(ToIntFunction mapper)返回一个IntStream其中包含将给定函数应用于此流的元素的结果
     */
    public static void m6() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("10");
        list.add("20");
        list.add("30");
        list.add("40");
        list.add("50");

        // 需求:将集合中的字符串数据转换为整数之后在控制台输出
        // list.stream().map(s -> Integer.parseInt(s)).forEach(System.out::println);
        // list.stream().map(Integer::parseInt).forEach(System.out::println);
        // list.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
        System.out.println("------------------------------------");
        // int sum() 返回此流中元素的总和
        int result = list.stream().mapToInt(Integer::parseInt).sum();
        System.out.println(result);
    }


    /**
     * Stream<T> sorted()返回由此流的元素组成的流,根据自然顺序排序
     */
    public static void m5() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("linqingxia");
        list.add("zhangmanyu");
        list.add("wangzuxian");
        list.add("liuyan");
        list.add("zhangmin");
        list.add("zhangwuji");

        // 需求1:按照字母顺序把数据在控制台输出
        list.stream().sorted().forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求2:按照字符串长度把数据在控制台输出
        list.stream().sorted((s1, s2) -> {
            int num = s1.length() - s2.length();
            int num2 = num == 0 ? s1.compareTo(s2) : num;
            return num2;
        }).forEach(System.out::println);
    }


    /**
     * static <T> Stream<T> concat(Stream a, Stream b):合并a和b两个流为一个流
     * Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object) )组成的流
     */
    public static void m4() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 需求1:取前4个数据组成一个流
        Stream<String> s1 = list.stream().limit(4);
        // 需求2:跳过2个数据组成一个流
        Stream<String> s2 = list.stream().skip(2);
        // 需求3:合并需求1和需求2得到的流,并把结果在控制台输出
        // Stream.concat(s1, s2).forEach(System.out::println);
        // 会报错
        // 需求4:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
        Stream.concat(s1, s2).distinct().forEach(System.out::println);
    }


    /**
     * limit&skip代码演示
     */
    public static void m3() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 需求1:取前3个数据在控制台输出
        list.stream().limit(3).forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求2:跳过3个元素,把剩下的元素在控制台输出
        list.stream().skip(3).forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
        list.stream().skip(2).limit(2).forEach(System.out::println);
    }


    /**
     * filter过滤方法的使用
     */
    public static void m2() {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 需求1:把list集合中以张开头的元素在控制台输出
        list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求2:把list集合中长度为3的元素在控制台输出
        list.stream().filter(s -> s.length() == 3).forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求3:把list集合中以张开头的,长度为3的元素在控制台输出
        list.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(System.out::println);

    }

    /**
     * foreach逐一处理
     */
    public static void m1() {
        Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
        stream.forEach(s -> System.out.println(s));
    }

}

2.4.2、Stream流终结操作方法

概念

终结操作的意思是,执行完此方法之后,Stream流将不能再执行其他操作。

常见方法

方法名说明
void forEach(Consumer action)对此流的每个元素执行操作
long count()返回此流中的元素数
package com.jdk8.stream;

import java.util.ArrayList;

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午7:06
 * @Description:
 */

public class StreamDemo04 {
    public static void main(String[] args) {
        // 创建一个集合,存储多个字符串元素
        ArrayList<String> list = new ArrayList<String>();

        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");
        list.add("张敏");
        list.add("张无忌");

        // 需求1:把集合中的元素在控制台输出
        list.stream().forEach(System.out::println);
        System.out.println("------------------------------------");
        // 需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出
        long count = list.stream().filter(s -> s.startsWith("张")).count();
        System.out.println(count);
    }
}

image-20240811190756024

2.5、综合案例

2.5.1、综合案例1

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

  1. 第一个队伍只要名字为3个字的成员姓名;
  2. 第一个队伍筛选之后只要前3个人;
  3. 第二个队伍只要姓张的成员姓名;
  4. 第二个队伍筛选之后不要前2个人;
  5. 将两个队伍合并为一个队伍;
  6. 根据姓名创建Person对象;
  7. 打印整个队伍的Person对象信息。
package com.jdk8.stream;

public class Person {

    private String name;

    public Person() {
    }

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

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.jdk8.stream;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午7:10
 * @Description:
 */

public class StreamDemo05 {
    public static void main(String[] args) {
        m1();
        System.out.println("------------------");
        m2();
    }

    /**
     * 等效的Stream流式处理代码
     */
    public static void m2() {
        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("张天爱");
        two.add("张三");

        // 第一个队伍只要名字为3个字的成员姓名;
        // 第一个队伍筛选之后只要前3个人;
        Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

        // 第二个队伍只要姓张的成员姓名;
        // 第二个队伍筛选之后不要前2个人;
        Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);

        // 将两个队伍合并为一个队伍;
        // 根据姓名创建Person对象;
        // 打印整个队伍的Person对象信息。
        System.out.println("使用stream流的方式:");
        Stream.concat(streamOne, streamTwo).map(s -> new Person(s)).forEach(s -> System.out.println(s));
        //也可替换为方法引用的方式
        // Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
    }


    /**
     * 传统的方式
     */
    public static void m1() {
        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("张天爱");
        two.add("张三");

        // 第一个队伍只要名字为3个字的成员姓名;
        List<String> oneA = new ArrayList<>();
        for (String name : one) {
            if (name.length() == 3) {
                oneA.add(name);
            }
        }

        // 第一个队伍筛选之后只要前3个人;
        List<String> oneB = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            oneB.add(oneA.get(i));
        }

        // 第二个队伍只要姓张的成员姓名;
        List<String> twoA = new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("张")) {
                twoA.add(name);
            }
        }

        // 第二个队伍筛选之后不要前2个人;
        List<String> twoB = new ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {
            twoB.add(twoA.get(i));
        }

        // 将两个队伍合并为一个队伍;
        List<String> totalNames = new ArrayList<>();
        totalNames.addAll(oneB);
        totalNames.addAll(twoB);

        // 根据姓名创建Person对象;
        List<Person> totalPersonList = new ArrayList<>();
        for (String name : totalNames) {
            totalPersonList.add(new Person(name));
        }

        System.out.println("传统的方式:");
        // 打印整个队伍的Person对象信息。
        for (Person person : totalPersonList) {
            System.out.println(person);
        }
    }
}

image-20240811193805298

2.5.1、综合案例2

案例需求

现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作

  • 男演员只要名字为3个字的前三人

  • 女演员只要姓林的,并且不要第一个

  • 把过滤后的男演员姓名和女演员姓名合并到一起

  • 把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据

演员类Actor已经提供,里面有一个成员变量,一个带参构造方法,以及成员变量对应的get/set方法

package com.jdk8.stream;

public class Actor {
    private String name;

    public Actor(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package com.jdk8.stream;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午7:40
 * @Description:
 */

public class StreamDemo06 {
    public static void main(String[] args) {
        // 创建集合
        ArrayList<String> manList = new ArrayList<String>();
        manList.add("周润发");
        manList.add("成龙");
        manList.add("刘德华");
        manList.add("吴京");
        manList.add("周星驰");
        manList.add("李连杰");
        ArrayList<String> womanList = new ArrayList<String>();
        womanList.add("林心如");
        womanList.add("张曼玉");
        womanList.add("林青霞");
        womanList.add("柳岩");
        womanList.add("林志玲");
        womanList.add("王祖贤");


         /*
        //男演员只要名字为3个字的前三人
        Stream<String> manStream = manList.stream().filter(s -> s.length() == 3).limit(3);

        //女演员只要姓林的,并且不要第一个
        Stream<String> womanStream = womanList.stream().filter(s -> s.startsWith("林")).skip(1);

        //把过滤后的男演员姓名和女演员姓名合并到一起
        Stream<String> stream = Stream.concat(manStream, womanStream);

        //把上一步操作后的元素作为构造方法的参数创建演员对象,遍历数据
//        stream.map(Actor::new).forEach(System.out::println);
        stream.map(Actor::new).forEach(p -> System.out.println(p.getName()));
        */


        //链式编程
        Stream.concat(manList.stream().filter(s -> s.length() == 3).limit(3),
                        womanList.stream().filter(s -> s.startsWith("林")).skip(1)).map(Actor::new).
                forEach(p -> System.out.println(p.getName()));
    }
}

image-20240811194224074

2.6、Stream流的收集操作

概念:对数据使用Stream流的方式操作完毕后,可以把流中的数据收集到集合中。

  • 常用方法

    方法名说明
    R collect(Collector collector)把结果收集到集合中
  • 工具类Collectors提供了具体的收集方式

    方法名说明
    public static Collector toList()把元素收集到List集合中
    public static Collector toSet()把元素收集到Set集合中
    public static Collector toMap(Function keyMapper,Function valueMapper)把元素收集到Map集合中
package com.jdk8.stream;

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

/**
 * @Author: 史小创
 * @Time: 2024/8/11 下午7:44
 * @Description:
 */

public class StreamDemo07 {
    public static void main(String[] args) {
        // 创建List集合对象
        List<String> list = new ArrayList<String>();
        list.add("林青霞");
        list.add("张曼玉");
        list.add("王祖贤");
        list.add("柳岩");

        /*
        //需求1:得到名字为3个字的流
        Stream<String> listStream = list.stream().filter(s -> s.length() == 3);

        //需求2:把使用Stream流操作完毕的数据收集到List集合中并遍历
        List<String> names = listStream.collect(Collectors.toList());
        for(String name : names) {
            System.out.println(name);
        }
        */

        // 创建Set集合对象
        Set<Integer> set = new HashSet<Integer>();
        set.add(10);
        set.add(20);
        set.add(30);
        set.add(33);
        set.add(35);

        /*
        //需求3:得到年龄大于25的流
        Stream<Integer> setStream = set.stream().filter(age -> age > 25);

        //需求4:把使用Stream流操作完毕的数据收集到Set集合中并遍历
        Set<Integer> ages = setStream.collect(Collectors.toSet());
        for(Integer age : ages) {
            System.out.println(age);
        }
        */
        // 定义一个字符串数组,每一个字符串数据由姓名数据和年龄数据组合而成
        String[] strArray = {"林青霞,30", "张曼玉,35", "王祖贤,33", "柳岩,25"};

        // 需求5:得到字符串中年龄数据大于28的流
        Stream<String> arrayStream = Stream.of(strArray).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);

        // 需求6:把使用Stream流操作完毕的数据收集到Map集合中并遍历,字符串中的姓名作键,年龄作值
        Map<String, Integer> map = arrayStream.collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1])));

        Set<String> keySet = map.keySet();
        for (String key : keySet) {
            Integer value = map.get(key);
            System.out.println(key + "," + value);
        }
    }
}

image-20240811194550109

三、接口组成更新

3.1、接口组成更新概述

  • 常量

    public static final

  • 抽象方法

    public abstract

  • 默认方法(Java 8)

  • 静态方法(Java 8)

  • 私有方法(Java 9)

3.2、接口中默认方法

  • 格式

    public default 返回值类型 方法名(参数列表) { }

  • 范例

    public default void show3() { 
    }
    
  • 注意事项

    • 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字

    • public可以省略,default不能省略

3.3、接口中静态方法

  • 格式

    public static 返回值类型 方法名(参数列表) { }

  • 范例

    public static void show() {
    }
    
  • 注意事项

    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用

    • public可以省略,static不能省略

3.4、接口中私有方法

  • 私有方法产生原因

    Java 9中新增了带方法体的私有方法,这其实在Java 8中就埋下了伏笔:Java 8允许在接口中定义带方法体的默认方法和静态方法。这样可能就会引发一个问题:当两个默认方法或者静态方法中包含一段相同的代码实现时,程序必然考虑将这段实现代码抽取成一个共性方法,而这个共性方法是不需要让别人使用的,因此用私有给隐藏起来,这就是Java 9增加私有方法的必然性

  • 定义格式

    • 格式1

      private 返回值类型 方法名(参数列表) { }

    • 范例1

      private void show() {  
      }
      
    • 格式2

      private static 返回值类型 方法名(参数列表) { }

    • 范例2

      private static void method() {  
      }
      
  • 注意事项

    • 默认方法可以调用私有的静态方法和非静态方法
    • 静态方法只能调用私有的静态方法
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
StreamJava 8 提供的一个用于处理集合数据的函数式编程 API。Stream API 可以让我们以一种声明式的方式处理集合数据,避免了传统的迭代方式,使代码更加简洁和易读。 而(Stream)是一个来自数据源的元素队列并支持聚合操作。元素是特定类型的对象,形成一个队列。操作可以执行顺序或并行。Java 中的 Stream API 可以使得我们可以使用一种类似于 SQL 语句在集合中执行操作。 Stream API 的主要特点如下: - Stream 不存储数据,它们只是在源的基础上提供了一种视图。 - Stream 操作是延迟执行的,只有当终止操作调用时才会执行。 - Stream 可以操作集合、数组等数据源。 - Stream 提供了丰富的中间操作和终止操作,可以实现过滤、映射、排序、聚合等功能。 函数式接口(Functional Interface)是 Java 8 中引入的一个概念,它是只包含一个抽象方法的接口。Stream API 使用了函数式接口作为其操作的参数,例如 filter、map、reduce 等方法都接受函数式接口作为参数,以便进行相应的操作。 在 Stream API 中,常用的函数式接口有 Predicate、Function、Consumer、Supplier 等,它们可以通过 Lambda 表达式或方法引用来创建,并可以与操作相结合使用,实现各种数据处理操作。 希望这个回答能够解决你对 Stream 函数式接口的疑问。如果还有其他问题,请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值