Stream流与方法引用

介绍了Stream流和方法引用的基本使用

Stream流

使用案例

 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 class Demo01 {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>(List.of("张三丰","张无忌","张翠山","王二麻子","张良","谢广坤"));

        // 传统遍历
        //遍历list1把以张开头的元素添加到list2中
        ArrayList<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if(s.startsWith("张")){
                list2.add(s);
            }
        }
        //遍历list2集合,把其中长度为3的元素,再添加到list3中
        ArrayList<String> list3 = new ArrayList<>();
        for (String s : list2) {
            if(s.length() == 3){
                list3.add(s);
            }
        }
        for (String s : list3) {
            System.out.println(s);
        }

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

        // Stream流
        list1.stream().filter(s->s.startsWith("张"))
                .filter(s->s.length() == 3)
                .forEach(s-> System.out.println(s));
    }
}

简介

  • Stream流的思想

    image-20251026222510764

  • Stream流的三类方法

    • 获取Stream流
      • 创建一条流水线,并把数据放到流水线上准备进行操作
    • 中间方法
      • 流水线上的操作
      • 一次操作完毕之后,还可以继续进行其他操作
    • 终结方法
      • 一个Stream流只能有一个终结方法
      • 是流水线上的最后一个操作,之后不能再执行其他操作

Stream流的常见生成方式

常见生成方式简介

  • Collection体系集合

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

  • Map体系集合

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

  • 数组

    通过Arrays中的静态方法stream生成流

  • 同种数据类型的多个数据

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

获取方式 方法名 说明
单列集合 default Stream stream() Collection 中的默认方法
双列集合 无法直接使用 stream 流
数组 public static Stream stream(T[] array) Arrays 工具类中的静态方法
一堆零散数据 public static Stream of(T… values) Stream 接口中的静态方法

代码演示

 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
public class Demo02 {
    public static void main(String[] args) {
        // Collection体系的集合可以使用默认方法stream()生成流
        List<String> list = new ArrayList<String>();
        Collections.addAll(list, "1","2","3");
        Stream<String> listStream = list.stream();

        Set<String> set = new HashSet<String>();
        Collections.addAll(set, "1","2","3");
        Stream<String> setStream = set.stream();


        // Map体系的集合间接的生成流
        Map<String,Integer> map = new HashMap<String, Integer>();
        map.put("张三", 18);
        map.put("李四", 19);
        // 单独获取key的流或value的流
        Stream<String> keyStream = map.keySet().stream();
        Stream<Integer> valueStream = map.values().stream();
        // 同时获取key和value的流
        Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream();


        // 数组可以通过Arrays中的静态方法stream生成流
        String[] strArray = {"hello","world","java"};
        Stream<String> strArrayStream = Arrays.stream(strArray);


        // 同种数据类型的多个数据可以通过Stream接口的静态方法of(T... values)生成流
        Stream<String> strArrayStream2 = Stream.of("hello", "world", "java");
        Stream<Integer> intStream = Stream.of(10, 20, 30);
        /*
          注意:
          Stream.of()方法的形参可以传入多个零散的数据,也能传递数组
          但是数组必须是应用数据类型的,如果传递的是基本数据类型,会把数组转换成对象
          (System.out::println是方法引用,后面会介绍)
         */
        Stream.of(strArray).forEach(System.out::println); // hello world java
        Stream.of(new int[]{1, 2, 3}).forEach(System.out::println); // [I@41629346
    }
}

Stream流的中间操作方法

常见中间操作简介

方法名 说明
Stream filter(Predicate predicate) 用于对流中的数据进行过滤
Stream limit(long maxSize) 返回此流中的元素组成的流,截取前指定参数个数的数据
Stream skip(long n) 跳过指定参数个数的数据,返回由该流的剩余元素组成的流
static Stream concat(Stream a, Stream b) 合并a和b两个流为一个流,尽量保证a和b数据类型一致
Stream distinct() 去重,返回由该流的不同元素(根据Object.equals(Object) )组成的流
Stream map(Function<T, R> mapper) 转换流中的数据类型
  • 中间方法,返回新的Stream流,原来的Stream流只能使用一次,建议使用链式编程
  • 修改Stream流中的数据,不会影响原来集合或数组中的数据

代码演示

  • filter方法:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo03 {
    public static void main(String[] args) {
        // filter方法演示
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三丰","张无忌","张翠山","王二麻子","张良","谢广坤");

        // 需求:打印list集合中,姓张且长度为3的元素
        list.stream().filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(s-> System.out.println(s));

        /*

        // 注意点1:Stream流只能使用一次
        Stream<String> stream = list.stream().filter(s -> s.startsWith("张"));
        Stream<String> temp1 = stream.filter(s -> s.length() == 3);
        stream.forEach(s-> System.out.println(s)); // 报错,流已被使用

        // 注意点2:修改Stream流中的数据,不会影响原来集合或数组中的数据
        System.out.println(list); // [张三丰, 张无忌, 张翠山, 王二麻子, 张良, 谢广坤]

        */
    }
}
  • limitskip方法:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Demo04 {
    public static void main(String[] args) {
        // limit和skip方法演示
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三丰","张无忌","张翠山","王二麻子","张良","谢广坤");

        // 需求:打印list集合中前3个元素
        // System.out::println 和 s-> System.out.println(s) 效果一样,此为方法引用后续会介绍,此后不多赘述
        list.stream().limit(3).forEach(System.out::println);

        // 需求:跳过list集合中前3个元素,打印剩余元素
        list.stream().skip(3).forEach(System.out::println);
    }
}
  • concatdistinct方法:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Demo05 {
    public static void main(String[] args) {
        // concat和distinct方法演示
        ArrayList<String> list1 = new ArrayList<>();
        Collections.addAll(list1, "张三","张三","王二麻子","赵六");
        ArrayList<String> list2 = new ArrayList<>();
        Collections.addAll(list2, "李四", "王五");

        // 需求:将list1和list2两个集合的元素进行拼接后打印
        Stream.concat(list1.stream(), list2.stream()).forEach(System.out::println);

        // 需求:将list1集合去重后再打印
        list1.stream().distinct().forEach(System.out::println);
    }
}
  • map方法:
 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 class Demo06 {
    public static void main(String[] args) {
        // map方法演示
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三-18", "李四-19", "王五-20", "赵六-21", "孙七-22", "周八-23");

        // 需求:只获取年龄并打印
        // 其中 s 依次表示流中每一个元素
        // s.split("-")将每个元素切割成数组
        // s.split("-")[1]表示获取每个元素中的年龄
        list.stream().map(s -> Integer.parseInt(s.split("-")[1]))
                .forEach(System.out::println);


        /*
        
        // 非lambda方式
        // Function<String, Integer>的第一次参数是传入类型,第二个参数是返回类型
        list.stream().map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                // 形参s:依次表示流中每一个元素
                String[] arr = s.split("-");
                String age = arr[1];
                return Integer.parseInt(age);
            }
        }).forEach(System.out::println);
        
        */
    }
}

Stream流的终结操作方法

常见终结方法简介

方法名 说明
void forEach(Consumer action) 对此流的每个元素执行操作
long count() 返回此流中的元素数
A[] toArray() 收集流中数据,放入数组中
R collect(Collector collector) 收集流中数据,放入集合中
  • 工具类Collectors提供了具体的收集到集合的方式
    方法名 说明
    public static Collector toList() 把元素收集到List集合中
    public static Collector toSet() 把元素收集到Set集合中
    public static Collector toMap(Function keyMapper,Function valueMapper) 把元素收集到Map集合中

代码演示

  • forEachcounttoArray方法:
 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
public class Demo07 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三丰","张无忌","张翠山","王二麻子","张良","谢广坤");

        // foreach方法
        list.stream().forEach(s -> System.out.println(s));

        // count方法
        long count = list.stream().count();
        System.out.println(count);

        // toArray方法
        // 不传入参数,返回Object[]
        Object[] arr = list.stream().toArray();
        System.out.println(Arrays.toString(arr));

        // 传入参数,返回指定类型的数组
        String[] array = list.stream().toArray(value -> new String[value]);
        System.out.println(Arrays.toString(array));

        /*

        // toArray方法传入参数的非lambda表达式方式
        // IntFunction的泛型:指定类型的数组
        // apply的参数:数组的长度
        // apply的返回值:指定类型的数组
        String[] array1 = list.stream().toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });
        System.out.println(Arrays.toString(array1));

        */
    }
}
  • collect方法:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Demo08 {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三-男-18", "李四-女-19", "王五-男-20", "赵六-女-21", "孙七-男-22", "周八-女-23");

        // 收集到List集合当中
        // 需求:把所有男性收集到一个List集合中
        List<String> maleList = list.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toList());
        System.out.println(maleList);

        // 收集到Set集合当中
        // 需求:把所有女性收集到一个Set集合中
        Set<String> femaleSet = list.stream().filter(s -> "女".equals(s.split("-")[1]))
                .collect(Collectors.toSet());
        System.out.println(femaleSet);

        // 收集到Map集合当中
        // 需求:把所有信息收集到一个Map集合中,姓名为key,年龄为value
        Map<String, Integer> map = list.stream()
                .collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
        System.out.println(map);
    }
}

注意:收集到Map集合当中时,键不能重复,否则会报错

方法引用

入门体验

 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
public class Demo1 {
    public static void main(String[] args) {
        // 需求:创建一个数组,进行倒序排序
        Integer[] arr = {3, 5, 1, 2, 4, 6};

        // 匿名内部类
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        System.out.println(Arrays.toString(arr));

        // Lambda表达式
        Arrays.sort(arr, (o1, o2) -> {
            return o2 - o1;
        });
        System.out.println(Arrays.toString(arr));

        // Lambda表达式简化格式
        Arrays.sort(arr, (o1, o2) -> o2 - o1);
        System.out.println(Arrays.toString(arr));

        // 方法引用
        // 1.引用处需要是函数式接口
        // 2.被引用的方法需要已经存在
        // 3.被引用方法的形参和返回值类型必须和函数式接口的抽象方法一致
        // 4.被引用方法的功能要满足当前需求
        Arrays.sort(arr, Demo1::descending); // Demo1::descending表示引用Demo1类中的descending方法
        System.out.println(Arrays.toString(arr));
    }

    // 引用的方法可以是Java或自己写好的,也可以是第三方工具类中的方法
    public static int descending(int num1, int num2) {
        return num2 - num1;
    }
}

方法引用符

  • 方法引用符

    :: 该符号为引用运算符,而它所在的表达式被称为方法引用

  • 推导与省略

    • 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
    • 如果使用方法引用,也是同样可以根据上下文进行推导
    • 方法引用是Lambda的孪生兄弟

引用类方法

引用类方法,其实就是引用类的静态方法

  • 格式

    类名::静态方法

  • 范例

    Integer::parseInt

    Integer类的方法:public static int parseInt(String s) 将此String转换为int类型数据

  • 代码演示

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    public class Demo2 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            Collections.addAll(list, "1", "2", "3", "4", "5");
    		// 将字符串形式的数字转化为整型数字并打印
            list.stream()
                    .map(Integer::parseInt)
                    .forEach(System.out::println);
        }
    }
    

特殊情况:引用类中的成员方法

  • 格式

    类名::成员方法

  • 范例

    String::toUpperCase

  • 代码演示

     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
    
    public class Demo5 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            Collections.addAll(list, "aaa", "bbb", "ccc", "ddd");
            // 需求:将集合中的字符串转换成大写
            // 方法引用
            list.stream()
                    .map(String::toUpperCase) // 相当于拿着流中每一个数据调用String类中的toUpperCase方法,方法的返回值就是转换后的结果
                    .forEach(System.out::println);
    
            // 原始形式
            list.stream().map(new Function<String, String>() {
                @Override
                // 第一个参数是String类型,则只能引用String类中的成员方法
                // 只有一个形参,则只能调用String类中的无参成员方法
                public String apply(String s) {
                    return s.toUpperCase();
                }
            }).forEach(System.out::println);
    
            /* 下方是String类中的toUpperCase方法
            public String toUpperCase() {
                return toUpperCase(Locale.getDefault());
            }
             问题来了,明明说被引用方法的形参和返回值类型必须和函数式接口的抽象方法一致,但是这里却可以调用String类中的toUpperCase方法
             因为这里是特殊规则:被引用方法的形参需要跟抽象方法的第二个形参到最后一个形参保持一致,返回值需要跟抽象方法的返回值一致
             抽象方法形参介绍:
             第一个参数:表示被引用方法的调用者,决定了可以引用哪些类中的方法
                       在Stream流当中,第一个参数一般都表示流里面的每一个数据
                       假设流里面的数据是字符串,那么使用这种方式进行方法引用,只能引用String类中的方法
             第二个参数到最后一个参数:跟被引用方法的形参保持一致,如果没有第二个参数,则被引用方法需要是无参的成员方法
            */
        }
    }
    

引用对象的实例方法

引用对象的实例方法,其实就引用类中的成员方法

  • 格式

    其他类:其他类对象::成员方法

    本类:this::方法名

    父类:super::方法名

    (静态方法中没有this和super,所以后两个不能在静态方法中使用)

  • 代码演示

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    public class Demo3 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            Collections.addAll(list, "1", "2", "a", "b", "c");
            // 需求:将集合中字符串数字进行过滤并输出
            list.stream()
                    .filter(new Demo()::isNumber)
                    .forEach(System.out::println);
        }
    }
    
    public class Demo {
        // 判断字符串是否是数字
        public boolean isNumber(String str) {
            if (str == null || str.trim().isEmpty()) {
                return false; // 空字符串或null直接返回false
            }
            String regex = "^-?\\d+(\\.\\d+)?$";
            return str.matches(regex);
        }
    }
    

引用构造器

引用构造器,其实就是引用构造方法

  • 格式

    一般类的构造方法:类名::new

    数组的构造方法:数据类型[]::new

  • 范例

    Student::new

    Integerp[]::new

  • 代码演示

    一般类的构造方法:

     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
    46
    47
    48
    49
    50
    
    public class Demo4 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            Collections.addAll(list, "张三,18", "李四,19", "王五,20", "赵六,21");
            // 需求:将集合中的字符串转换成Student对象
            List<Student> studentList = list.stream()
                    .map(Student::new)
                    .collect(Collectors.toList());
            System.out.println(studentList);
            // [Student{name='张三', age=18}, Student{name='李四', age=19}, Student{name='王五', age=20}, Student{name='赵六', age=21}]
        }
    }
    
    public class Student {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        // 被引用方法的形参和返回值类型必须和函数式接口的抽象方法一致
        // 抽象方法是Student apply(String s) { ... return new Student(name, age);}
        // 构造方法只要保证结束时生成的对象与抽象方法的返回值保持一致即可
        public Student(String str) {
            String[] split = str.split(",");
            this.name = split[0];
            this.age = Integer.parseInt(split[1]);
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    数组的构造方法:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    public class Demo6 {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            Collections.addAll(list, 1, 2, 3, 4, 5);
            // 将集合中的整数收集到数组中
            // 注意:数组的类型需要跟流中数据的类型保持一致
            Integer[] array = list.stream().toArray(Integer[]::new);
            System.out.println(Arrays.toString(array)); // [1, 2, 3, 4, 5]
        }
    }
    
本站于2025年3月26日建立
使用 Hugo 构建
主题 StackJimmy 设计