Stream流

Stream流

1、为什么有Stream流

流出现的原因是现在的项目发展的缘故,项目中已经不止使用一种数据库了,可能使用的有MySQL、Oracle、MongDB等等,从这些多数据源中查询出来的数据,还需要程序员手动的对这些数据来进行处理。将这里数据进行合并,所以这里Stream流出现的本质原因。所以这里首先体现出来的就是数据量的问题。

所以从这里可以判断出,stream流主要是用于对集合的处理。对于集合来进行处理,再次判断,集合中的元素都是相同的,不可能再会是Object类型的了,应该用一个泛型来进行指定。

Stream流最终会形成一个容器,来装在数据的。现实生活中河流是有开始和结束的,同样的,stream也是有开始和结束的,在中间的这一段经历了什么?我们可以通过需求来进行操作。

1.1、重点

根据上面的说明,所以stream流不是用来存数据的,而是对里面的数据来进行操作的。

2、获取Stream流的几种方式

2.1、集合获取得到Stream流

上面说的,用于集合。列出常见的集合:

List、Set、Map

得到流对象:

直接使用集合引用:list.stream(),set.stream(),map.stream(),即可获取得到一个流容器

例子如下:

public class Demo1 {
    public static void main(String[] args) {
        // 后去得到stream流。这里没有具体的数据,在之后进行讲解
        List<Integer> list = new ArrayList<>();
        Stream<Integer> stream = list.stream();

        Set<Integer> integers = new HashSet<>();
        Stream<Integer> stream1 = integers.stream();

        Map<String,String> map = new HashMap<>();
        Stream<String> stream2 = map.keySet().stream();
        Stream<String> stream3 = map.values().stream();
        Stream<Map.Entry<String, String>> stream4 = map.entrySet().stream();
    }
}

2.2、使用流类中的静态方法获取

使用Stream.of(T... t):得到流对象

Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5);
Stream<String> stringStream = Stream.of("hello","world");

还有其他方式获取得到stream流,这里选取常用的,其他的可以去网上查下资料。

3、操作stream流

首先看一下,一个大概的流程图:

上面已经说过了怎么来获取得到Stream对象,这里介绍下里面的操作:

3.1、Stream流方向性

1、Stream流是不可逆转的,只能一直向前走,每次操作后都是一个新的Stream流容器。而每个流处理之后是不能再次被使用的

public class Demo2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Stream<Integer> stream1 = list.stream();
        Stream<Integer> stream2 = stream1.peek((n) -> System.out.println(n));
        Stream<Integer> stream3 = stream1.limit(5);
    }
}

启动就报错,错误日志显示stream已经被终止了或者是已经被操作做了:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
	at java.util.stream.ReferencePipeline.<init>(ReferencePipeline.java:94)
	at java.util.stream.ReferencePipeline$StatefulOp.<init>(ReferencePipeline.java:647)
	at java.util.stream.SliceOps$1.<init>(SliceOps.java:120)
	at java.util.stream.SliceOps.makeRef(SliceOps.java:120)
	at java.util.stream.ReferencePipeline.limit(ReferencePipeline.java:401)
	at com.guang.stream.Demo2.main(Demo2.java:16)

从上面可以知道,处理过后就不会再次被利用了。stream has already been operated upon or closed

那么这里换一下:

public class Demo2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Stream<Integer> stream1 = list.stream();
        Stream<Integer> stream2 = stream1.peek((n) -> System.out.println(n));
        // 将1换成2即可得到新的
        Stream<Integer> stream3 = stream2.limit(5);
    }
}

3.2、Stream流之懒加载

懒加载类似mybatis中的懒加载以及单例模式中的懒汉模式,在使用的时候才会进行加载。那么stream流中的懒加载是什么意思?

stream流中的懒加载就是没有遇到终止节点之前,都不去执行

看下面的例子来说明:

public class Demo2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Stream<Integer> stream1 = list.stream();
        Stream<Integer> integerStream = stream1.filter((n) -> {
            if (n > 2) {
                System.out.println(n);
                return true;
            } else {
                System.out.println(n);
                return false;
            }
        });
    }
}

可以看到上面的流在经过处理之后,没有任何的效果。因为还是一个流,既然是在流的状态下,那么就不会有任何输出。

3.3、流之中间节点、终止节点

这里就牵扯出来了:如何来判断是中间节点还是终止节点。

一句话:只要返回值不是一个stream及其子类,那么都是终止节点。
public class Demo2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        Stream<Integer> stream1 = list.stream();

        Stream<Integer> integerStream = stream1.filter((n) -> {
            if (n > 2) {
                System.out.println(n);
                return true;
            } else {
                System.out.println(n);
                return false;
            }
        });
    }
}

上面的代码如果不是懒加载节点,那么就应该会输出,但是执行的时候并没有进行输出,因为没有遇到终止节点。所以懒加载节点并不是进行就直接进行执行,而是遇到了终止节点才执行。

3.3、Stream流中元素顺序

流中的元素是一个一个的进入到后面的流容器中的,而不是将最开始流中的数据直接全部推送到后续的容器中去的。

public class Demo3 {
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        list.add(new Apple("red",23D));
        list.add(new Apple("red",3D));
        list.add(new Apple("blue",12D));
        list.add(new Apple("blue",13D));
        list.stream().peek((n)-> System.out.println(n.getColor()))
                .peek((n)-> System.out.println(n.getWeight()))
                .toArray();
    }
}

最终控制台输出:

red
23.0
red
3.0
blue
12.0
blue
13.0

再来一个案例:

        Stream.of("one", "two", "three", "four")
                .filter(e -> e.length() > 3)
                .peek(e -> System.out.println("Filtered value: " + e))
                .map(String::toUpperCase)
                .peek(e -> System.out.println("Mapped value: " + e))
                .collect(Collectors.toList());

查看控制台输出:

Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

在流中的每一个元素,每一个元素经过所有的处理之后,才会执行到下一个元素的处理。

如果中间被过滤掉了,如同上面的filter过滤,那么才会经过让下一个元素进来继续执行。

所以流中元素是一个一个进去的

4、Stream流操作方法

4.1、forEach方法

从名字意思可以看出是一个遍历方法,看下源码说明:

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

这是一个对流中方法执行操作的方法,通过返回值类型来看,没有返回值。但是如果是引用类型,我们如果在其中做了一些操作的话,那么将会改变引用类型中的属性值。

看一下Consumer函数式接口:

@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.对给定的元素进行操作
     *
     * @param t the input argument。给定的参数!所以参数是外部传入进来的
     */
    void accept(T t);
    // 排除掉默认方法。函数式接口
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

例子如下,对流中元素进行遍历:

public class Demo3 {
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        list.add(new Apple("red",23D));
        list.add(new Apple("red",3D));
        list.add(new Apple("blue",12D));
        list.add(new Apple("blue",13D));
        // 并非只有这一种操作!一开始都是被这种操作给迷惑了!
        list.stream().forEach(n-> System.out.println(n));
    }
}

4.2、filter方法

从名字可以看到是过滤方法。那么过滤掉的将会进行拦截,没有过滤的将如何处理?所以从名字就可以推断出是一个中间节点。

/**
 *返回一个流中的经过条件匹配的元素。
 * @return the new stream:返回的是一个新的流容器对象。
 */
Stream<T> filter(Predicate<? super T> predicate);

看下Predicate

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.对给定的参数进行执行判断。
     * @return {@code true} if the input argument matches the predicate,如果满足匹配,那么进行返回
     * otherwise {@code false}:不满足匹配,返回的是false
     */
    boolean test(T t);

通过注释,翻译一下:如果是true,那么表示条件满足,该元素会留在流中;如果是false,条件不满足,那么不会留在流中。

public class Demo3 {
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        list.add(new Apple("red",23D));
        list.add(new Apple("red",3D));
        list.add(new Apple("blue",12D));
        list.add(new Apple("blue",13D));
        // 给定的条件是每个元素是否等于red,如果是red,那么是true,留下来;如果是false,不满足匹配,过滤掉
        list.stream().filter(n->n.getColor().equals("red")).forEach(n-> System.out.println(n));
    }
}

4.2、limit和skip方法

limit方法

   /**
     * Returns a stream consisting of the elements of this stream, truncated
     * to be no longer than {@code maxSize} in length.
		截断流中的不超过最大个数的元素,返回不超过最大个数元素所组成的流
     */
    Stream<T> limit(long maxSize);

也就是说限制了maxsize最大个数之前的元素,maxsize之前的元素可以留下来,maxsize之后的元素不会留下来。

public class Demo3 {
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        list.add(new Apple("red",23D));
        list.add(new Apple("red",3D));
        list.add(new Apple("blue",12D));
        list.add(new Apple("blue",13D));
		// 限制了两个元素,所以只能留下两个元素。看输出台打印即可看出。
        list.stream().limit(2).forEach(n-> System.out.println(n));
    }
}

skip方法

Stream<T> skip(long n);

跳过指定的参数n个元素,将第n+1个元素作为一个流元素。

public class Demo3 {
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        list.add(new Apple("red",23D));
        list.add(new Apple("red",3D));
        list.add(new Apple("blue",12D));
        list.add(new Apple("blue",13D));
		// 跳过前两个元素,将第三个元素作为第一个元素。看下控制台打印。
        list.stream().skip(2).forEach(n-> System.out.println(n));
    }
}

4.3、map方法

map是我们最常见的方法,K-V键值对结构。

/**
 * Returns a stream consisting of the results of applying the given
 * function to the elements of this stream.
 *
 * @param <R> The element type of the new stream:新的流中元素类型
 * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
 *               <a href="package-summary.html#Statelessness">stateless</a>
 *               function to apply to each element
 * @return the new stream:返回一个新的流
 */
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.将函数用在给定的参数上
     * @param t the function argument:函数参数
     * @return the function result:返回函数执行完成后的结果
     */
    R apply(T t);

通过一系列的翻译可以知道,map就是利用一个函数,将流中的元素进行计算,返回新的新的元素。

所以这个是一个转换方法。将流中的元素通过函数来转换成对应的元素。

public class Demo3 {
    public static void main(String[] args) {
        List<Apple> list = new ArrayList<>();
        list.add(new Apple("red",23D));
        list.add(new Apple("red",3D));
        list.add(new Apple("blue",12D));
        list.add(new Apple("blue",13D));
        // 自定义一个函数来进行实现。
        list.stream().map(n->{
            if (n.getColor().equals("red")){
                return 1;
            }else {
                return 2;
            }
        }).forEach(n-> System.out.println(n));
    }
}

如果流中元素的颜色是red,那么返回1;如果不是,那么返回2;也就是说流中的元素和现在流中的元素完全是不搭边的。但是现在流中的元素是通过算法得到的。与原来流中元素有很多的不同。

map也就是我们经常说的映射关系,通过这个映射关系,我们得到我们想要的值,然后将值收集起来,形成对应的集合即可。

4.4、 flatMap方法

关于这个方法,理解起来是真的不好理解,所以我这里多写几个例子来进行说明:

demo1:

将字符串数组{"hello","world"}对应的输出其中的每个字符即可。

    @Test
    public void testListFour(){
        String[] strings = {"hello","world"};
        Stream.of(strings).flatMap(t-> Arrays.stream(t.split(""))).forEach(System.out::println);
    }

demo2:

将每个list集合中的元素放入到一个大的集合中来,然后从大的集合中来获取得到对应的每个元素

    @Test
    public void testListTwo(){
        User user1 = User.builder().id(1).address("深圳").name("xuan").build();
        User user2 = User.builder().id(2).address("深圳").name("huang").build();
        User user3 = User.builder().id(3).address("深圳").name("chen").build();
        List<User> userList1 = new ArrayList<>();
        List<User> userList2 = new ArrayList<>();
        List<User> userList3 = new ArrayList<>();
        List<List<User>> users = new ArrayList<>();
        userList1.add(user1);
        userList2.add(user2);
        userList3.add(user3);
        users.add(userList1);
        users.add(userList2);
        users.add(userList3);
        //流中的元素都来进行扁平化操作。也就是说会把相当类型的元素收集起来进行汇总
        List<User> collect = users.stream().flatMap(t->t.stream()).collect(Collectors.toList());
        collect.forEach(System.out::println);
    }

从上面的两个结果来看,似乎都是容器包含容器的关系,容器中的数据都是一样的,那么用一个大容器来盛放所有的数据,然后从大的容器中来获取得到对应的值即可。

4.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));
    }

Demo来进行测试:

public class Demo4 {
    public static void main(String[] args) {
        Stream<Integer> s1 = Stream.of(1,2,5,6,3,9,10);
        Stream<Integer> s2 = Stream.of(11,22,52,63,34,94,130);
        Stream<Integer> concat = Stream.concat(s1, s2);
        concat.forEach(n-> System.out.println(n));
    }
}

4.6、collect方法

用来收集的方法,这个方法使用比较丰富。

最常见的使用方式:

分组、转换成另外一种集合等等方法。这个需要在使用的时候用到。

public class Demo4 {
    public static void main(String[] args) {
        Stream<Integer> s1 = Stream.of(1,2,5,6,3,9,10);
        Stream<Integer> s2 = Stream.of(11,22,52,63,34,94,130);
        Stream<Integer> concat = Stream.concat(s1, s2);
        // 统计个数等等操作。
        Long collect = concat.collect(Collectors.counting());
        System.out.println(collect);
    }
}

类似java原生API中的addAll方法。

4.7、sorted

排序算法不得不去介绍一下java原生API中的

        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("rose", 18));
        list.add(new Student("jack", 16));
        list.add(new Student("abc", 20));
        Collections.sort(list,((o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
           return o1.getAge()-o2.getAge();
        }));
        System.out.println("-----------------");
        list.forEach(System.out::println);

查看输出结果:

Student(name=jack, age=16)
Student(name=rose, age=18)
Student(name=abc, age=20)
Student(name=jack, age=16)
Student(name=abc, age=20)
Student(name=rose, age=18)
-----------------
Student(name=jack, age=16)
Student(name=rose, age=18)
Student(name=abc, age=20)

通过结果可以看到,每一个后来的元素,都会和之前已经排序好的来进行比较。所以有n!次

对于Comparator函数式接口来说,我们可以自定义排序规则。因为返回值决定了排序规则。如果是返回值是正数,代表的是由小到大;如果返回值是负数,那么就是由大到小。

        Collections.sort(list,((o1, o2) -> {
            System.out.println(o1);
            System.out.println(o2);
           return o2.getAge()-o1.getAge();
        }));

修改一下代码,可以看到顺序就是相反的。那么来一下Stream流中的排序

    /**
     * https://blog.csdn.net/qq_36763236/article/details/111469653
     */
    @Test
    public void testCompared2() {
        // 创建四个学生对象 存储到集合中
        ArrayList<Student> list = new ArrayList<Student>();
        list.add(new Student("rose", 18));
        list.add(new Student("jack", 16));
        list.add(new Student("abc", 20));
        // list.stream().sorted(Comparator.comparing(Student::getAge).thenComparing(Student::getName).thenComparing(Student::getAge)).forEach(System.out::println);
        list.stream().sorted(Comparator.comparing(Student::getAge,Comparator.reverseOrder())).forEach(System.out::println);
    }

参考API即可。

4.8、分组函数

@Data
@AllArgsConstructor
public class Person {
    Integer classNo;
    String name;
    Integer age;
}

对应的测试方法:

    @Test
    public void testGroup(){
        List<Person>  list = new ArrayList<>();
        list.add(new Person(1,"张三",12));  // 1班 张三  12岁
        list.add(new Person(1,"李四",13));
        list.add(new Person(2,"王五",12));
        list.add(new Person(2,"贼六",11));
        list.add(new Person(3,"对七",12));
        Map<Integer, List<Person>> collect = list.stream().collect(Collectors.groupingBy(Person::getClassNo));
        collect.forEach((k,v)->{
            System.out.println(k+"==========="+v);
        });
    }

查看输出:

1===========[Person(classNo=1, name=张三, age=12), Person(classNo=1, name=李四, age=13)]
2===========[Person(classNo=2, name=王五, age=12), Person(classNo=2, name=贼六, age=11)]
3===========[Person(classNo=3, name=对七, age=12)]

4.9、anyMatch方法

    /**
     * 如果又多个,匹配到了一个,那么就返回true;
     * 如果一个都没有,那么返回false;
     */
    @Test
    void contextLoads() {
        Person stu1 = new Person( "19", "张三");
        Person stu2 = new Person( "23", "李四");
        Person stu3 = new Person( "28", "王五");
        Person stu4 = new Person( "23", "李四");
        List<Person> list = new ArrayList<>();
        list.add(stu1);
        list.add(stu2);
        list.add(stu3);
        list.add(stu4);
        // 判断学生年龄是否有大于27岁的
        boolean anyMatchFlag = list.stream().anyMatch(t->t.getName().equals("李四1"));
        System.out.println(anyMatchFlag);
    }

断言函数来进行断言。如果匹配到了,那么就直接返回true;否则返回false。

4.10、Collectors.toMap

参考链接:https://zhangzw.com/posts/20191205.html

List 转 Map 操作这个使用起来还是比较爽的,先看看以前的用法。

@Accessors(chain = true) 
@lombok.Data
class User {
    private String id;
    private String name;
}

然后准备一下集合数据:

List<User> userList = Lists.newArrayList(
        new User().setId("A").setName("张三"),
        new User().setId("B").setName("李四"),
        new User().setId("C").setName("王五")
);

我们希望转成 Map 的格式为:

A-> 张三 
B-> 李四 
C-> 王五 

过去使用的方式:

Map<String, String> map = new HashMap<>();
for (User user : userList) {
    map.put(user.getId(), user.getName());
}

使用JDK8提供的方法来进行操作,一行代码搞定:

userList.stream().collect(Collectors.toMap(User::getId, User::getName));

对应的代码是:

    @Test
    public void testOne(){
        List<UserVO> userList = Lists.newArrayList(
                new UserVO().setId("A").setName("张三"),
                new UserVO().setId("B").setName("李四"),
                new UserVO().setId("C").setName("王五")
        );
        final Map<String, String> collect = userList.stream().collect(Collectors.toMap(UserVO::getId, UserVO::getName));
        collect.forEach((k,v)-> System.out.println(k+"->"+v));
    }

如果希望value是自身的时候,也可以这么来进行操作:

userList.stream().collect(Collectors.toMap(User::getId, t -> t));
 或:
userList.stream().collect(Collectors.toMap(User::getId, Function.identity()));

Collectors.toMap 有三个重载方法:

toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper);
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
        BinaryOperator<U> mergeFunction);
toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,
        BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier);

参数含义分别是:

  1. keyMapper:Key 的映射函数
  2. valueMapper:Value 的映射函数
  3. mergeFunction:当 Key 冲突时,调用的合并方法
  4. mapSupplier:Map 构造器,在需要返回特定的 Map 时使用

还是用上面的例子,如果 List 中 userId 有相同的,使用上面的写法会抛异常:

List<User> userList = Lists.newArrayList(
        new User().setId("A").setName("张三"),
        new User().setId("A").setName("李四"), // Key 相同 
        new User().setId("C").setName("王五")
);
userList.stream().collect(Collectors.toMap(User::getId, User::getName));

// 异常:
java.lang.IllegalStateException: Duplicate key 张三 
    at java.util.stream.Collectors.lambda$throwingMerger$114(Collectors.java:133)
    at java.util.HashMap.merge(HashMap.java:1245)
    at java.util.stream.Collectors.lambda$toMap$172(Collectors.java:1320)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
    at Test.toMap(Test.java:17)
    ...

这时就需要调用第二个重载方法,传入合并函数,如:

userList.stream().collect(Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1 + n2));

// 输出结果:
A-> 张三李四 
C-> 王五 

第四个参数(mapSupplier)用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序的,可以使用如下写法:

List<User> userList = Lists.newArrayList(
        new User().setId("B").setName("张三"),
        new User().setId("A").setName("李四"),
        new User().setId("C").setName("王五")
);
userList.stream().collect(
    Collectors.toMap(User::getId, User::getName, (n1, n2) -> n1, TreeMap::new)
);

// 输出结果:
A-> 李四 
B-> 张三 
C-> 王五 

4.11、group函数

这个使用也是很简单的,所以需要记录一下:

4、方法引用

为什么会存在着方法引用??

因为我们在lambda表达式中,我们最终需要体中写我们自定义的逻辑来进行实现。

但是有时候想调用已经存在的方法,可以作为参数或者是作为调用者,都是可以的。

jdk8提供了方法引用。我们最常见的写法就是类名::函数名来进行调用。

为什么可以这样子来操作?因为我们最终想要调用的是流中元素,也就是对象来调用方法的。

比如最常见的:

List<String> stringList = Arrays.asList("1", "2", "3", "4");
stringList.forEach(System.out::println);

这里我们只是想要将流中的对象作为参数来进行输出,除了流中对象能够做参数之外,还可以作为方法的调用者。

List<String> stringList = Arrays.asList("a", "b", "c", "d");
stringList.stream().map(String::toUpperCase).forEach(System.out::println);

从这里看到参数在方法引用中既可以充当调用者,还可以充当参数的存在。所以我们可以在很多地方都这样来操作。

5、总结

对于流中的方法操作来说,可以看到流中的对象总是一个对象。注意:要和基本的数据类型区分开来,因为要避免掉字符串和基本类型的参数,因为我们操作的时候,如果peek和map,都需要改变对象,如果对象的地址已经发生改变了,那么流中相当于是不会有任何数据来进行输出了。

posted @ 2021-07-27 22:38  写的代码很烂  阅读(301)  评论(0编辑  收藏  举报