[Java1.8]_[Stream]

Java 8 Stream教程

Stream不同于之前的I/O流,而是Java 8引入的函数式编程。

函数式编程

简单说,”函数式编程”是一种”编程范式”(programming paradigm),也就是如何编写程序的方法论。
它属于”结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。

Stream 简单例子

用一个遍历数组的例子看看Stream的作用。

传统写法

List<String> myList =
        Arrays.asList("a1", "a2", "b1", "c2", "c1");

List<String> newList = new ArrayList<String>();

//遍历
for (String name : myList)
{
    //筛选出字母开头是c的
    if (name.startsWith("c"))
    {
        //转换成大写
        String newName = name.toUpperCase();
        newList.add(newName);
    }
}

//排列
Collections.sort(newList);

//遍历输出
newList.forEach(System.out::println);

Stream写法

List<String> myList =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
    .stream()                           //开始流操作
    .filter(s -> s.startsWith("c"))     //筛选出开头字母是C的
    .map(String::toUpperCase)           //转换成大写
    .sorted()                           //排序
    .forEach(System.out::println);      //遍历输出

//C1
//C2

Stream操作这里有用到Lambda表达式,大多数的Steam操作都可以用Lamdba表达式。Stream操作简化了很多,主要体现在逻辑清晰,没有过多的if/else判断语句。

Stream的种类

Stream分为sequential Stream(顺序流)和parallel stream(平行流),上面的例子是属于顺序流,通过stream()方法创建的流都是顺序流。

Stream

一般的集合如List和Set都支持Stream操作,但可以不通过创建集合来使用Stream操作,可以通过Stream.of()方法使用,代码如下:

Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);  // a1
Primitive streams

Stream是处理对象的操作,primitive streams是处理处理传统类型的操作,如IntStream、LongStream 、DoubleStream分别处理对应的类型,这里展示InStream的简单使用:

IntStream.range(1, 4)
    .forEach(System.out::println);

// 1
// 2
// 3

Primitive streams和Streams的使用基本一样,只是有一些专门处理基本类型的方法,像上面的range(),还有如下例子:

Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println);  // 5.0

average() 方法求平均值,Primitive streams和Stram都有方法讲对象和基本类型互相转换,代码如下:

Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3
Stream操作的顺序

先看下面例子代码:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });

该例子实际上是不会输出任何东西,因为Stream没有终端操作,只有中间操作。

再看看加入终端操作后的结果:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach: " + s));

输出结果:

filter:  d2
forEach: d2
filter:  a2
forEach: a2
filter:  b1
forEach: b1
filter:  b3
forEach: b3
filter:  c
forEach: c

这里的输出结果可能会让你觉得很诧异,是数组元素按顺序执行完整个流操作,即要第一个元素执行完整个流操作才轮到第二个。

我们可以将元素重新排序然后再执行流操作,通过sort()方法,代码如下:

Stream.of("d2", "a2", "b1", "b3", "c")
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s\n", s1, s2);
        return s1.compareTo(s2);
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

输出结果:

sort:    a2; d2
sort:    b1; a2
sort:    b1; d2
sort:    b1; a2
sort:    b3; b1
sort:    b3; d2
sort:    c; b3
sort:    c; d2
filter:  a2
map:     a2
forEach: A2
filter:  b1
filter:  b3
filter:  c
filter:  d2
Reusing Streams

Java 8的Stream不能被重复使用直到该Stream已经结束,其实Stream可以返回一个实例,看实例代码:

Stream<String> stream =
    Stream.of("d2", "a2", "b1", "b3", "c")
        .filter(s -> s.startsWith("a"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception

该结果会出现异常,因为stream的anyMatch()方法还没执行完就执行noneMatch(),异常如下:

java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
    at com.winterbe.java8.Streams5.test7(Streams5.java:38)
    at com.winterbe.java8.Streams5.main(Streams5.java:28)

可以通过创建Supplier来实现重复执行,代码例子如下:

Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok
较为复杂的Stream操作

前面只是举例说明了一下,常用的map()、filter()、foreach()等操作很容易看明白,还有很多操作可以看这个链接
Java stream doc

首先创建一个Person类方便说明,如下:

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name;
    }
}

List<Person> persons =
    Arrays.asList(
        new Person("Max", 18),
        new Person("Peter", 23),
        new Person("Pamela", 23),
        new Person("David", 12));
Collect

Collect可以在最后将结果转化成一个集合返回,代码如下:

List<Person> filtered =
    persons
        .stream()
        .filter(p -> p.name.startsWith("P"))
        .collect(Collectors.toList());

System.out.println(filtered);    // [Peter, Pamela]

可以看到还是比较简单实用的,通过Collectors.toList()方法直接返回一个List,如果需要set就用toSet(),
使用非常方便。

还可以生成Map指点类集合和求平均值,代码例子分别如下:

Map<Integer, String> map = persons
    .stream()
    .collect(Collectors.toMap(
        p -> p.age,
        p -> p.name,
        (name1, name2) -> name1 + ";" + name2));

System.out.println(map);
Double averageAge = persons
    .stream()
    .collect(Collectors.averagingInt(p -> p.age));

System.out.println(averageAge);     // 19.0
FlatMap

如果集合或数组的元素本身也是集合或数组,而且我们就是要求访问集里面的集合的时候就要用到FlatMap,代码例子如下:

class Foo {
    String name;
    List<Bar> bars = new ArrayList<>();

    Foo(String name) {
        this.name = name;
    }
}

class Bar {
    String name;

    Bar(String name) {
        this.name = name;
    }
}
foos.stream()
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));

// Bar1 <- Foo1
// Bar2 <- Foo1
// Bar3 <- Foo1
// Bar1 <- Foo2
// Bar2 <- Foo2
// Bar3 <- Foo2
// Bar1 <- Foo3
// Bar2 <- Foo3
// Bar3 <- Foo3
Reduce

Reduce的作用就是将流操作的多个结果汇集成一个,例子如下:

Person result =
    persons
        .stream()
        .reduce(new Person("", 0), (p1, p2) -> {
            p1.age += p2.age;
            p1.name += p2.name;
            return p1;
        });

System.out.format("name=%s; age=%s", result.name, result.age);

parallel stream

parallelStream不同于Stream的地方是它会让中间流操作利用ForkJoinPool的线程进行操作,而使用的线程是不确定的,代码例子如下:

Arrays.asList("a1", "a2", "b1", "c2", "c1")
    .parallelStream()
    .filter(s -> {
        System.out.format("filter: %s [%s]\n",
            s, Thread.currentThread().getName());
        return true;
    })
    .map(s -> {
        System.out.format("map: %s [%s]\n",
            s, Thread.currentThread().getName());
        return s.toUpperCase();
    })
    .sorted((s1, s2) -> {
        System.out.format("sort: %s <> %s [%s]\n",
            s1, s2, Thread.currentThread().getName());
        return s1.compareTo(s2);
    })
    .forEach(s -> System.out.format("forEach: %s [%s]\n",
        s, Thread.currentThread().getName()));

输出结果:

filter: b1 [main]
filter: a1 [ForkJoinPool.commonPool-worker-3]
filter: a2 [ForkJoinPool.commonPool-worker-1]
map: a2 [ForkJoinPool.commonPool-worker-1]
filter: c1 [ForkJoinPool.commonPool-worker-2]
forEach: A2 [ForkJoinPool.commonPool-worker-1]
map: a1 [ForkJoinPool.commonPool-worker-3]
map: b1 [main]
forEach: A1 [ForkJoinPool.commonPool-worker-3]
filter: c2 [ForkJoinPool.commonPool-worker-1]
map: c1 [ForkJoinPool.commonPool-worker-2]
map: c2 [ForkJoinPool.commonPool-worker-1]
forEach: B1 [main]
forEach: C2 [ForkJoinPool.commonPool-worker-1]
forEach: C1 [ForkJoinPool.commonPool-worker-2]
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值