重走Java基础之Streams(三)

来源:重走Java基础之Streams(三)
作者:知秋(极乐科技知乎专栏原创作者)
博客:一叶知秋


重走Java基础之Streams(二)

Creating a Stream

所有javaCollection都有stream()parallelStream()方法可以从中构造一个Stream:

Collection<String> stringList = new ArrayList<>();
Stream<String> stringStream = stringList.parallelStream();

可以使用以下两种方法之一从数组创建Stream:

String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream<String> stringStream = Arrays.stream(values);
Stream<String> stringStreamAlternative = Stream.of(values);

Arrays.stream()Stream .of()不同之处在于 Stream.of()有一个varargs参数,因此可以像下面这样使用:

Stream<Integer> integerStream = Stream.of(1, 2, 3);

还有一些primitive(原始的,原函数,看下面例子便知道什么意思了)Streams,你可以使用。 例如:

IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

这些primitive streams (原始流)也可以使用Arrays.stream()方法构造:

IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });

可以从具有指定范围的数组创建一个Stream。

int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);

注意任何primitive streams (原始流)可以使用boxed方法转换为boxed类型流:

Stream<Integer> integerStream = intStream.boxed();

这在某些情况下可能是有用的,如果你想收集数据,因为primitive streams (原始流)没有任何可以需要一个Collector来作为参数的collect方法。

再多举几个例子:

1.计算列表中的元素数

注意需

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

List<Integer> list = IntStream.range(1, 100).boxed().collect(Collectors.toList());
System.out.println(list.stream().count());

2.计算列表中元素的平均数

Double avarage = list.stream().collect(Collectors.averagingInt(item -> item));

3.对列表元素进行统计

List<Integer> list = IntStream.range(1, 100).boxed().collect(Collectors.toList());
IntSummaryStatistics iss = list.stream().collect(Collectors.summarizingInt(value -> value));
System.out.println(iss);

输出结果:

IntSummaryStatistics{count=99, sum=4950, min=1, average=50.000000, max=99}

4.根据List创建Map

List<Integer> list = IntStream.range(1, 100).boxed().collect(Collectors.toList());
Map<Integer, Integer> map = list.stream().collect(Collectors.toMap(p -> p, q->q*3));
System.out.println(map);

输出结果:

{1=3, 2=6, 3=9, 4=12, 5=15, 6=18, 7=21, 8=24, 9=27, 10=30, 11=33, 12=36, 13=39, 14=42, 15=45, 16=48, 17=51, 18=54, 19=57, 20=60, 21=63, 22=66, 23=69, 24=72, 25=75, 26=78, 27=81, 28=84, 29=87, 30=90, 31=93, 32=96, 33=99, 34=102, 35=105, 36=108, 37=111, 38=114, 39=117, 40=120, 41=123, 42=126, 43=129, 44=132, 45=135, 46=138, 47=141, 48=144, 49=147, 50=150, 51=153, 52=156, 53=159, 54=162, 55=165, 56=168, 57=171, 58=174, 59=177, 60=180, 61=183, 62=186, 63=189, 64=192, 65=195, 66=198, 67=201, 68=204, 69=207, 70=210, 71=213, 72=216, 73=219, 74=222, 75=225, 76=228, 77=231, 78=234, 79=237, 80=240, 81=243, 82=246, 83=249, 84=252, 85=255, 86=258, 87=261, 88=264, 89=267, 90=270, 91=273, 92=276, 93=279, 94=282, 95=285, 96=288, 97=291, 98=294, 99=297}

5.求列表元素的最大数

List<Integer> list = new Random().ints(-100,100).limit(250).boxed().collect(Collectors.toList());
Optional<Integer> max = list.stream().reduce(Math::max);
max.ifPresent(value -> System.out.println(value));

应该有些理解了吧。

重用a stream chain(一个流链)的中间操作

当终端操作被调用时,流被关闭。当我们的需求只有发生在终端操作的改变时,可以 重复使用中间操作流。 我们可以创建 a stream supplier(一个流供应者)来构造一个已经建立了所有中间操作的新流。

Supplier<Stream<String>> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();

  streamSupplier.get().filter(s ->   s.startsWith("A")).forEach(System.out::println);

// APPLE

  streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);

// BANANA
// BLACKBERRY
// BLUEBERRY

int []数组可以使用流转换为List

int[] ints = {1,2,3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());

通过 flatMap()来扁平化处理流

flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中

map和flatMap方法示意图:

此图各种上传不上,可以看此链接

可以看出map只转换

可以看出flatMap不仅转换,又进一步合并了一下,将多个子Stream合并为一个Stream。

由下面例子可以看出,大的 Stream中的子 Stream可以被扁平化处理为单个连续的 Stream:

Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));

List<Integer> allValues = map.values() // Collection<List<Integer>>
        .stream()                      // Stream<List<Integer>>
        .flatMap(List::stream)         // Stream<Integer>
        .collect(Collectors.toList());

System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]

含Map的List可以被扁平化处理成一个连续的Stream:

List<Map<String, String>> list = new ArrayList<>();
Map<String,String> map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");

Map<String,String> map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);

Set<String> output= list.stream()  //  Stream<Map<String, String>>
    .map(Map::values)              // Stream<List<String>>
    .flatMap(Collection::stream)   // Stream<String>
    .collect(Collectors.toSet());  //Set<String>
[one, two, three,four]

使用Streams实现数学函数

Streams,尤其是 IntStreams,是一种实现求和项(Σ)的优雅方法。Stream可以用作求和的的范围边界。

E.g., Madhava的Pi近似值由公式给出(Source: https://en.wikipedia.org/wiki/Approximations_of_%CF%80):

这可以以任意精度计算。 例如,101项次幂:

double pi = Math.sqrt(12) * 
            IntStream.rangeClosed(0, 100)
                     .mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
                     .sum();

Note: 使用double的精度,选择29的上限就足以获得与Math.Pi大概一致的结果.

使用IntStream迭代索引

streams的元素通常不允许访问当前项的索引值。 要通过访问索引来迭代数组或ArrayList,使用IntStream.range(start,endExclusive)。

String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };

IntStream.range(0, names.length)
    .mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
    .forEach(System.out::println);

range(start,endExclusive) 方法返回另一个 ÌntStream并和 mapToObj(mapper)返回一个String。

Output:

这非常类似于使用带有计数器的正常的for循环,但是具有流水线和并行化的优点:

for (int i = 0; i < names.length; i++) {
    String newName = String.format("#%d %s", i + 1, names[i]);
    System.out.println(newName);
}

连接流

变量声明示例:

Collection<String> abc = Arrays.asList("a", "b", "c");
Collection<String> digits = Arrays.asList("1", "2", "3");
Collection<String> greekAbc = Arrays.asList("alpha", "beta", "gamma");

Example 1 - 连接两个流

final Stream<String> concat1 = Stream.concat(abc.stream(), digits.stream());

concat1.forEach(System.out::print);
// prints: abc123

Example 2 -连接多个流

final Stream<String> concat2 = Stream.concat(
    Stream.concat(abc.stream(), digits.stream()),
    greekAbc.stream());

System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

或者为了简化嵌套的concat()语法我们也可以使用flatMap():

final Stream<String> concat3 = Stream.of(
    abc.stream(), digits.stream(), greekAbc.stream())
    .flatMap(s -> s);
// or `.flatMap(Function.identity());` (java.util.function.Function)

System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

在从重复连接构造Streams 时要小心,因为访问深度并置的Stream的元素可能导致深层调用链或者甚至是StackOverflowException(本就是栈操作)。

IntStream to String

Java没有* Char Stream *,所以当使用Strings并构造一个Character的Characters时,一个选项是使用String.codePoints()方法获取一个IntStream** ,所以IntStream可以得到如下:

public IntStream stringToIntStream(String in) {
return in.codePoints();
}

更多的涉及做其他方式的转换,即IntStreamToString。 可以这样做:

public String intStreamToString(IntStream intStream) {
return intStream.collect(StringBuilder::new, 
StringBuilder::appendCodePoint, StringBuilder::append).toString();
}

使用流和方法引用来编写自己的文档化流程代码

通过方法引用来创建具有帅气风格的文档化代码,使用带有Stream的方法引用使得复杂的过程易于阅读和理解(所以才说流程)。 考虑下面的代码:

public interface Ordered {
default int getOrder(){
return 0;
    }
}

public interface Valued<V extends Ordered> {
boolean hasPropertyTwo();
V getValue();
}

public interface Thing<V extends Ordered> {
boolean hasPropertyOne();
Valued<V> getValuedProperty();
}

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    List<V> results = new ArrayList<V>();
for (Thing<V> thing : things) {
if (thing.hasPropertyOne()) {
            Valued<V> valued = thing.getValuedProperty();
if (valued != null && valued.hasPropertyTwo()){
                V value = valued.getValue();
if (value != null){
                    results.add(value);
                }
            }
        }
    }
    results.sort((a, b)->{
return Integer.compare(a.getOrder(), b.getOrder());
    });
return results;
}

最后使用Streams和方法引用重写的自定义的方法更易读,而且每个步骤都很容易理解 - 它不仅更短,还显示了哪些接口和类负责每个 步:

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
return things.stream()
        .filter(Thing::hasPropertyOne)
        .map(Thing::getValuedProperty)
        .filter(Objects::nonNull)
        .filter(Valued::hasPropertyTwo)
        .map(Valued::getValue)
        .filter(Objects::nonNull)
        .sorted(Comparator.comparing(Ordered::getOrder))
        .collect(Collectors.toList());
}  

未完待续……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容

  • 本文采用实例驱动的方式,对JAVA8的stream API进行一个深入的介绍。虽然JAVA8中的stream AP...
    浮梁翁阅读 25,757评论 3 50
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,672评论 18 139
  • 来源:重走Java基础之Streams(四)作者:知秋博客:一叶知秋转载请注明来源和作者! 接上篇重走Java基础...
    极乐君阅读 479评论 0 3
  • Java SE 8中的主要新语言功能是lambda表达式。您可以将lambda表达式视为匿名方法;类似方法,lam...
    ATG丶MapleLeaf阅读 1,037评论 0 1
  • 第一章 为什么要关心Java 8 使用Stream库来选择最佳低级执行机制可以避免使用Synchronized(同...
    谢随安阅读 1,493评论 0 4