jdk8新特性之Stream

Stream

说到Stream很容易想到IO流,而实际上,谁规定流一定就要是IO流呢?

在Java8中,得益于Lambda带来的函数式编程,引用了一个全新的Stream概念,用于解决已有集合框架既有的弊端。

引言

传统集合的多步遍历代码

public class Demo {
    public static void main(String[] args) {
        List list = Lists.asList("李小龙", "张国荣" ,"高渐离", "李白");
        for(String str : list) {
            System.out.println(str);
        }
    }
}

循环遍历的弊端

Java8的Lambda让我们可以更加专注于做什么,而不是怎么做:

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

为什么这样讲呢?

先思考一个问题,那就是为什么要用循环?因为要进行遍历。但是循环难道是遍历的唯一方式吗?遍历是指对每一个元素都进行处理,而不是指从第一个到最后一个顺序处理。

前者是目的,后者是方式。

如果现在有一个需求,需要对集合中的元素进行过滤筛选:

  1. 将集合A根据条件一过滤为集合B;
  2. 将集合B根据条件二过滤为集合C。
// 传统做法
public class Demo {
    public static void main(String[] args) {
        List list = Lists.asList("李小龙", "张国荣" ,"高渐离", "李白");
        
        List<String> list1 = new ArrayList<>();
        for (String str : list) {
            if (str.startWith("李"))
                list1.add(str);
        }
        
        List<String> list2 = new ArrayList<>();
        for (String str : list1) {
            if (str.length() > 2) 
                list2.add(str);
        }
    }
}

这段代码中含有两个循环,每个作用不同,线性循环意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。

Stream的优雅

对于上面同样的需求,Stream可以更加优雅的解决:(其实这个例子不是很恰当,但是get到这个意思就好,蛤蛤蛤蛤。。嗝)

public class Demo {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("李小龙", "张国荣" ,"高渐离", "李白");

        list.stream()
                .filter(s -> s.startsWith("李"))  // 参数是Predicate接口
                .filter(s -> s.length() > 2)
                .forEach(System.out::println); // 参数是Consumer接口
    }
}

流式思想概述

请暂时忘记对传统IO流的固有印象。

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

在生产线中,生产饮料会分为多个步骤,比如:放瓶子、洗瓶子、装饮料、封口、装箱。

在这里插入图片描述

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案。

图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换到另一个流模型,而最右侧的数字3就是最终的结果。

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

这得益于lambda的延迟执行特性,所谓的lambda延迟执行是指:lambda体指定的只是一个函数模型,并非实际调用执行。

也就是说,filter、map、skip中指定的lambda体只是一个函数模型,而并不是实际执行的操作,最终方法才是执行的标志。

Stream流其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身当然也并不存储任何元素或引用值。

  • Stream不会存储元素,它只是想流水生产线一样去完成既有的功能。
  • Stream流的来源可以是集合、数组等。
  • Pipeline:中间操作都会返回流对象(filter、map、skip等都返回一个新的stream对象,原有stream对象不变),以此来链式编程。

获取流

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

根据Collection获取流

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

// Collection接口获取流
public class Demo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3);
        list.stream();
        
        // 由于Map不是Collection的子接口,所以Map的子类获取Stream需要通过Set
        HashMap<Object, Object> map = new HashMap<>();
        
        Set<Object> keys = map.keySet();
        keys.stream();
        Collection<Object> values = map.values();
        values.stream();

        Set<Map.Entry<Object, Object>> entries = map.entrySet();
        entries.stream();
    }
}

Stream流的特点

每个stream只能被使用一次,每次使用完后,该stream就被关闭了,数据会流转到下一个stream上。

// 代码示例
public class Demo {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Stream<Integer> stream2 = stream.filter(num -> num == 1);

        stream2.forEach(System.out::println);
        // 此时stream已经被关闭了,再次使用会报错
        stream.forEach(System.out::println);
    }
}

程序日志:

1
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at stream.Demo4.main(Demo4.java:19)

常用方法

在这里插入图片描述

stream的API可以分为两种:

  • 延迟方法:返回值仍然是Stream接口自身类型的方法,因此支持链式调用。除了终结方法外,其余方法均为延迟方法。注意这里说的是Stream类型,但不是同一个Stream对象。
  • 终结方法:返回值类型不是Strean接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。我们常接触的终结方法包括countforEach方法。

逐一处理:forEach

// Stream接口源码
void forEach(Consumer<? super T> action);

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

// 基本使用
public class Demo2 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "2", "3");
        stream.forEach(System.out::println);
    }
}

过滤:filter

// Stream接口源码
Stream<T> filter(Predicate<? super T> predicate);

filter方法接收一个Predicate参数,以此作为筛选条件,它可以将一个流过滤为另一个子集流。

// 基本使用
public class Demo3 {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("1", "2", "3");
        list.stream()
                .filter("3"::equals)
                .filter(s -> s.length() > 0)
                .forEach(System.out::println);
    }
}

映射:map

// Stream接口源码
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的前置类型数据转换为另一种类型的流。

在这里插入图片描述

// 基本使用
public class Demo5 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1", "2", "3", "4", "5", "6");
        stream.map(Integer::parseInt)
                .filter(s -> s instanceof Integer)
                .forEach(System.out::println);
    }
}

日志打印:

1
2
3
4
5
6

统计个数:count

// Stream接口源码
long count();
// 基本使用
public class Demo6 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        long count = stream.filter(num -> num == 1)
                .count();
        System.out.println(count);
    }
}

日志打印:

1

取前几个:limit

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

// Stream接口源码
Stream<T> limit(long maxSize);

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

在这里插入图片描述

// 基本使用
public class Demo {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        stream.limit(3)
                .forEach(System.out::println);
    }
}

日志打印:

1
2
3
// 参数值大于元素个数
public class Demo {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        stream.limit(6)
                .forEach(System.out::println);
    }
}

日志打印:

1
2
3
4
5

跳过前几个:skip

如果想跳过前几个元素,可以使用skip方法取后几个的新stream。

// Stream接口源码
Stream<T> skip(long n);

参数是一个long型,如果集合当前长度大于参数值,则跳过前几个,否则会得到一个长度为0的空stream。

// 基本使用
public class Demo {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        stream.skip(1)
                .forEach(System.out::println);
    }
}

日志打印:

2
3
4
5
// 参数值大于等于集合长度
public class Demo8 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        stream.skip(5)
                .forEach(System.out::println);
    }
}

日志打印:

// 什么都没有

组合:concat

如果想将两个流合并为一个流,可以使用Stream接口的静态方法concat

// Stream接口源码
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));
}
// 基本使用
public class Demo9 {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
        Stream<String> stream2 = Stream.of("6", "7", "8");
        Stream<? extends Serializable> newStream = Stream.concat(stream, stream2);
        newStream.forEach(System.out::println);
    }
}

日志打印:

1
2
3
4
5
6
7
8
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值