Stream流

一 为什么要用Stream流

1.1 传统的方式,遍历,过滤集合中的数据

		//创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        //对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
        List<String> listA = new ArrayList<>();
        for(String s : list){
            if(s.startsWith("张")){
                listA.add(s);
            }
        }

        //对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
        List<String> listB = new ArrayList<>();
        for (String s : listA) {
            if(s.length()==3){
                listB.add(s);
            }
        }

        //遍历listB集合
        for (String s : listB) {
            System.out.println(s);
        }
    }

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

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

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

1.2 Stream流的方式,遍历,过滤集合中的数据

//创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        //对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
        //对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
        //遍历listB集合
        list.stream()
            .filter(name->name.startsWith("张"))
            .filter(name->name.length()==3)
            .forEach(name-> System.out.println(name));

1.3 流式思想概述

  • 流式思想类似于工厂车间的“生产流水线

元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。 数据源流的来源。 可以是集合,数组等当使用一个流的时候,通常包括三个基本步骤:

获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

二 Stream流的基础使用

2.1 两种获取Stream流的方式

所有的 Collection 集合都可以通过 stream 默认方法获取流;
Stream 接口的静态方法 of 可以获取数组对应的流。

/*
    java.util.stream.Stream<T>是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
    获取一个流非常简单,有以下几种常用的方式:
        - 所有的Collection集合都可以通过stream默认方法获取流;
            default Stream<E> stream​()
        - Stream接口的静态方法of可以获取数组对应的流。
            static <T> Stream<T> of​(T... values)
            参数是一个可变参数,那么我们就可以传递一个数组
 */
public class Demo01GetStream {
    public static void main(String[] args) {
        //把集合转换为Stream流
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

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

        Map<String,String> map = new HashMap<>();
        //获取键,存储到一个Set集合中
        Set<String> keySet = map.keySet();
        Stream<String> stream3 = keySet.stream();

        //获取值,存储到一个Collection集合中
        Collection<String> values = map.values();
        Stream<String> stream4 = values.stream();

        //获取键值对(键与值的映射关系 entrySet)
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream5 = entries.stream();

        //把数组转换为Stream流
        Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
        //可变参数可以传递数组
        Integer[] arr = {1,2,3,4,5};
        Stream<Integer> stream7 = Stream.of(arr);
        String[] arr2 = {"a","bb","ccc"};
        Stream<String> stream8 = Stream.of(arr2);
    }
}

2.2 Stream流中的常用方法_forEach

forEach()

虽然名字叫forEach(),但和for循环中的for-each 不同

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

Consumer接口

java.util.function.Consumer 接口是一个函数型接口, 消费一个数据, 其数据类型由泛型决定。

Consumer 接口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。

2.3 Stream流中的常用方法_filter

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

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

该接口接受一个Predicate函数式接口参数作为筛选条件

Predicate接口:
java.util.function.Predicate接口
作用:对某种数据类型的数据进行判断,结果返回一个boolean

抽象方法:test:
Predicate接口中包含了一个抽象方法
boolean test(T t):用来对指定数据类型数据进行判断的方法
结果;
符合条件,返回true
不符合条件,返回false

eg:

	Integer[] arr = {1,2,3,4,5,6,7,8,9,10};
    Stream<Integer> stream1 = Stream.of(arr);
//  stream1.forEach(x -> System.out.println(x)); // stream 流使用过一次后即废弃
    stream1.filter(x -> x > 5).forEach(x -> System.out.println(x));

2.4 Stream流的特点_只能使用一次

  • stream流的注意事项, 只能使用一次

    若使用使用 已使用过的stream流,会在抛出如下错误
    IllegalStateException: stream has already been operated upon or closed

三 Stream流中的常用方法

3.1 map

  • map: 将一个流的数据映射到另一个流
	<R> Stream<R> map(Function<? super T, ? extends R> mapper);
	
	//eg
	Integer[] arr = {1,2,3,4,5,6,7,8,9,10};
	Stream<Integer> stream1 = Stream.of(arr);
	Stream<String> stream2 = stream1.map(x -> String.valueOf(x));

3.2 count

  • count : 统计流中元素的个数

3.3 limit

  • limit: 方法可以对流进行截取,只取用前n个
Stream<T> limit(long maxSize);

3.4 skip

  • skip: 返回跳过前n个元素的流
Stream<T> skip(long n);

3.5 concat

  • concat: 静态方法 合并两个流
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }

四 集合元素处理

/*
    练习:集合元素处理(传统方式)
        现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:
        1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
        3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
        4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        5. 将两个队伍合并为一个队伍;存储到一个新集合中。
        6. 根据姓名创建Person对象;存储到一个新集合中。
        7. 打印整个队伍的Person对象信息。
 */

 		//第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
        //1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        //2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
        Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);

        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");
        //3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
        //4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);

        //5. 将两个队伍合并为一个队伍;存储到一个新集合中。
        //6. 根据姓名创建Person对象;存储到一个新集合中。
        //7. 打印整个队伍的Person对象信息。
        Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(p-> System.out.println(p));

五 方法引用

5.1 方法引用简介

在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作。

如果我们在 Lambda 中所指定的操作方案,已经有地方存在相同方案,则没有必要再写重复逻辑。

那又是如何使用已经存在的方案的呢?通过方法引用来使用已经存在的方案。

5.2 方法引用符号

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

使用 Lambda,那么根据 “ 可推导就是可省略 ” 的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导。

使用方法引用,也是同样可以根据上下文进行推导,它是 Lambda 的孪生兄弟。

5.3 引用类的静态方法

格式:类名::方法名

注意:Lambda 表达式被类方法替代时,它的形参全部传递给静态方法作为参数。

示例: 将 String 类型数字转为 int 类型数字。

public interface StringConvert {
    int convertInt(String s);
}

测试:

public class Test {
    public static void main(String[] args) {
        // lambda方式
        useStringConvert(s -> Integer.parseInt(s));
 
        // 方法引用:引用类的静态方法,形参全部传递给静态方法作为参数
        useStringConvert(Integer::parseInt);
    }
 
    public static void useStringConvert(StringConvert sc) {
        int number = sc.convertInt("156");
        System.out.println("number = " + number);
    }
}

运行:

num = 156
num = 156

5.4 引用类的构造方法

格式:类名::new

注意:Lambda 表达式被构造器替代时,它的形参全部传递给构造器作为参数。

示例:创建学生。

public class Student {
    String name;
    int age;
    
	// 省略无参|有参构造方法、get|set方法、toString方法
}
public interface StudentBuilder {
    Student builder(String name, int age);
}

测试:

public class Test {
    public static void main(String[] args) {
        // lambda方式
        useStudentBuilder((name, age) -> new Student(name, age));
 
        // 方法引用:引用类的构造方法,形参全部传递给构造器作为参数
        useStudentBuilder(Student::new);
    }
 
    public static void useStudentBuilder(StudentBuilder sb) {
        Student student = sb.builder("张三", 23);
        System.out.println("student = " + student);
    }
}

运行:

student = Student{name='张三', age=23}
student = Student{name='张三', age=23}

5.5 引用类的实例方法

格式:类名::成员方法

注意:Lambda 表达式被类的实例方法替代时,第一个参数作为方法调用者,后面其余参数全部传递给该方法作为参数

示例:截取字符串,返回一个子串。

public interface StringMethod {
    String mySubString(String string, int begin, int end);
}

测试:

public class Test {
    public static void main(String[] args) {
        // lambda方式
        useStringMethod((string, begin, end) -> string.substring(begin, end));
 
        // 方法引用:引用类的实例方法(成员方法),第一个参数作为方法调用者,后面其余参数全部传递给该方法作为参数
        useStringMethod(String::substring);
    }
 
    public static void useStringMethod(StringMethod sm) {
        String string = sm.mySubString("HelloWord!", 3, 6);
        System.out.println("string = " + string);
    }
}

运行:

string = loW
string = loW

5.6 引用对象的实例方法

格式:对象::成员方法

说明:引用对象的实例方法就是引用类中的成员方法

注意:Lambda 表达式被对象的实例方法替代时,它的形参全部传递给该方法作为参数

示例:

public interface Printer {
    void printUpper(String s);
}
public class PrintString {
    void printUpperCase(String s) {
        String string = s.toUpperCase();
        System.out.println("string = " + string);
    }
}

测试:

public class Test {
    public static void main(String[] args) {
        // lambda方式
        usePrinter(s -> System.out.println(s.toUpperCase()));
 
        // 方法引用:引用类对象的实例方法(成员方法),形参全部传递给该方法作为参数
        PrintString ps = new PrintString();
        usePrinter(ps::printUpperCase);
    }
 
    public static void usePrinter(Printer p) {
        p.printUpper("HelloWord!");
    }
}

运行:

HELLOWORD!
string = HELLOWORD!
posted @ 2022-08-11 18:01  Firewooood  阅读(48)  评论(0)    收藏  举报