什么是 Stream?
streamreduce用法_stream()reduce
streamreduce用法_stream()reduce
streamreduce用法_stream()reduce
streamreduce用法_stream()reduce
Stream(流)是一个来自数据源的元素队列并支持聚合作
元素是特定类型的对象,形成一个队列。 Ja中的Stream并不会存储元素,而是按需计算。
数据源 流的来源。 可以是,数组,I/O channel, 产生器generator 等。
聚合作 类似SQL语句一样的作, 比如filter, map, reduce, find, match, sorted等。
可以试试这个输出什么:
String[] strarr = {"abc", "defg", "vwxyz"};
int iSum = Arrays.stream(strarr)
.mapToInt(s -> s.length())
.sum();
System.out.println("长度和: "+iSum);
将映射转成int类型 相当于 聪输入的数据是int
返回一个 int 类型的stream
Returns an {@code IntStream} consisting of the results of applying the
given function to the elements of this stream.
map输出时分割符的参数是stream.map.output.field.separator,它可以指定map输出时的分割符 。
同时可以用stream.num.map.output.key.fields指定输出按照分隔符切割后,key所占有的列数。
同样,map.output.key.field.separator用来指定map输出
num.key.fields.for.partition用来指定分桶时,按照分隔符切割后,用于分桶key所占的列数。
reduce的时候,与map类似,分别用于reduce输出时分隔符以及key占有的列数即
stream.reduce.output.field.separator
stream.num.reduce.output.key.field
想要看Spliterator的实现,可以直接去看JDK对于框架的实现,很多实现类可以在Spliterators中找到的,也可以直接去对应的stream方法中找到,比如ArrayList点进去的是Collection的默认实现,只需要提供一个Spliterator的实现,然后用StreamSupport就可以构造一个Stream了,相当方便
default Stream
return StreamSupport.stream(spliterator(), false);
}@Override
default Spliterator
return Spliterators.spliterator(this, 0);
}
我们在创建线程并启动时可以使用匿名内部类的写法:
可以使用Lambda的格式对其进行修改。修改后如下:
现有方法定义如下,其中IntBinaryOperator是一个接口。先使用匿名内部类的写法调用该方法。
Lambda写法:
现有方法定义如下,其中IntPredicate是一个接口。先使用匿名内部类的写法调用该方法。
Lambda写法:
现有方法定义如下,其中Function是一个接口。先使用匿名内部类的写法调用该方法。
Lambda写法:
现有方法定义如下,其中IntConsumer是一个接口。先使用匿名内部类的写法调用该方法。
Lambda写法:
Ja8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对或数组进行链状流式的作。可以更方便的让我们对或数组作。
我们可以调用getAuthors方法获取到作家的。现在需要打印所有年龄小于18的作家的名字,并且要注意去重。
单列: 对象.stream()
数组:Arrays.stream(数组)或者使用Stream.of来创建
双列:转换成单列后再创建
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
例如:
打印所有姓名长度大于1的作家的姓名
可以把对流中的元素进行计算或转换。
例如:
打印所有作家的姓名
可以去除流中的重复元素。
例如:
打印所有作家的姓名,并且要求其中不能有重复元素。
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。
可以对流中的元素进行排序。
例如:
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
注意:如果调用空参的sorted()方法,需要流中的元素是实现了Comparable。
可以设置流的长度,超出的部分将被抛弃。
例如:
对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄的两个作家的姓名。
跳过流中的前n个元素,返回剩下的元素
例如:
打印除了年龄的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
例一:
打印所有书籍的名字。要求对重复的元素进行去重。
例二:
打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
对流中的元素进行遍历作,我们通过传入的参数去指定对遍历到的元素进行什么具体作。
例子:
输出所有作家的名字
可以用来获取当前流中元素的个数。
例子:
打印这些作家的所出书籍的数目,注意删除重复元素。
可以用来或者流中的值。
例子:
分别获取这些作家的所出书籍的分和分并打印。
把当前流转换成一个。
例子:
获取一个存放所有作者名字的List。
获取一个所有书名的Set。
获取一个Map,map的key为作者名,value为List
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
例子:
判断是否有年龄在29以上的作家
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。
例子:
判断是否所有的作家都是成年人
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
例子:
判断作家是否都没有超过100岁的。
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的个元素。
例子:
获取任意一个年龄大于18的作家,如果存在就输出他的名字
获取流中的个元素。
例子:
获取一个年龄小的作家,并输出他的姓名。
对流中的数据按照你指定的计算方式计算出一个结果。(缩减作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算。
reduce两个参数的重载形式内部的计算方式如下:
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
例子:
使用reduce求所有作者年龄的和
使用reduce求所有作者中年龄的值
使用reduce求所有作者中年龄的小值
reduce一个参数的重载形式内部的计算
如果用一个参数的重载方法去求小值代码如下:
我们在编写代码的时候出现多的就是空指针异常。所以在很多情况下我们需要做各种非空的判断。
例如:
尤其是对象中的属性还是一个对象的情况下。这种判断会更多。
而过多的判断语句会让我们的代码显得臃肿不堪。
所以在JDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常。
并且在很多函数式编程相关的API中也都用到了Optional,如果不会使用Optional也会对函数式编程的学习造成影响。
Optional就好像是包装类,可以把我们的具体数据封装Optional对象内部。然后我们去使用Optional中封装好的方法作封装进去的数据就可以非常优雅的避免空指针异常。
我们一般使用 Optional 的 静态方法ofNullable 来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。
你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下getAuthor方法,让其的返回值就是封装好的Optional的话,我们在使用时就会方便很多。
而且在实际开发中我们的数据很多是从数据库获取的。Mybatis从3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己作。
如果你 确定一个对象不是空 的则可以使用 Optional 的 静态方法of 来把数据封装成Optional对象。
但是一定要注意,如果使用of的时候传入的参数必须不为null。(尝试下传入null会出现什么结果)
如果一个方法的返回值类型是Optional类型。而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。这时则可以使用 Optional 的 静态方法empty 来进行封装。
所以后你觉得哪种方式会更方便呢? ofNullable
我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其 ifPresent 方法对来消费其中的值。
这个方判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安全了。
例如,以下写法就优雅的避免了空指针异常。
如果我们想获取值自己进行处理可以使用get方法获取,但是不。因为当Optional内部的数据为空的时候会出现异常。
如果我们期望安全的获取值。我们不使用get方法,而是使用Optional提供的以下方法。
我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现Optional的好处, 更使用ifPresent方法 。
Optional还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全。
例如我们想获取作家的书籍。
只有一个抽象方法 的接口我们称之为函数接口。
JDK的函数式接口都加上了 @FunctionalIntece 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
我们在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。我们只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
类名或者对象名::方法名
其实就是引用类的静态方法
如果我们在重写方法的时候,方法体中 只有一行代码 ,并且这行代码是 调用了某个类的静态方法 ,并且我们把要重写的 抽象方法中所有的参数都按照顺序传入了这个静态方法中 ,这个时候我们就可以引用类的静态方法。
例如:
如下代码就可以用方法引用进行简化
注意,如果我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则。
优化后如下:
如果我们在重写方法的时候,方法体中 只有一行代码 ,并且这行代码是 调用了某个对象的成员方法 ,并且我们把要重写的 抽象方法中所有的参数都按照顺序传入了这个成员方法中 ,这个时候我们就可以引用对象的实例方法
例如:
优化后:
如果我们在重写方法的时候,方法体中 只有一行代码 ,并且这行代码是 调用了个参数的成员方法 ,并且我们把要 重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中 ,这个时候我们就可以引用类的实例方法。
例如:
优化后如下:
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
如果我们在重写方法的时候,方法体中 只有一行代码 ,并且这行代码是 调用了某个类的构造方法 ,并且我们把 要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中 ,这个时候我们就可以引用构造器。
例如:
优化后:
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。
例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。
当流中有大量元素时,我们可以使用并行流去提高作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
parallel方法可以把串行流转换成并行流。
也可以通过parallelStream直接获取并行流对象。
这个是没有现成的收集器的,你可以用两种办法
种使用 stream 中的 reduce 进行收集 U reduce(U identity, BiFunction accumulator,BinaryOperator combiner) 进行规约,并在 accumulator 中进行计算个数进行合并
第二种 使用 reactor 库的 buffer 作符 进行作
如 Flux.fromStream(lines).buffer(100) 将每个元素收集成为 100 个每组的list 进行处理
第二种方法比较好
Ja 8引入了全新的Stream API。这里的Stream和I/O流不同,它更像具有Iterable的类,但行为和类又有所不同。
Stream API引入的目的在于弥补Ja函数式编程的缺陷。对于很多支持函数式编程的语言,map()、reduce()基本上都内置到语言的标准库中了,不过,Ja 8的Stream API总体来讲仍然是非常完善和强大,足以用很少的代码完成许多复杂的功能。
创建一个Stream有很多方法,简单的方法是把一个Collection变成Stream。我们来看基本的几个作:
public static void main(String[] args) {
List
Stream
stream.filter((x) -> {
return x % 2 == 0;
}).map((x) -> {
return x x;
}).forEach(System.out::println);
}类新增的stream()方法用于把一个变成Stream,然后,通过filter()、map()等实现Stream的变换。Stream还有一个forEach()来完成每个元素的迭代。
为什么不在类实现这些作,而是定义了全新的Stream API?Oracle给出了几个重要原因:
一是类持有的所有元素都是存储在内存中的,非常巨大的类会占用大量的内存,而Stream的元素却是在访问的时候才被计算出来,这种“延迟计算”的特性有点类似Clojure的lazy-seq,占用内存很少。
二是类的迭代逻辑是调用者负责,通常是for循环,而Stream的迭代是隐含在对Stream的各种作中,例如map()。
要理解“延迟计算”,不妨创建一个无穷大小的Stream。
如果要表示自然数,显然用类是不可能实现的,因为自然数有无穷多个。但是Stream可以做到。
自然数的规则非常简单,每个元素都是前一个元素的值+1,因此,自然数发生器用代码实现如下:
class NaturalSupplier implements Supplier
long value = 0;
public Long get() {
this.value = this.value + 1;
return this.value;
}}
反复调用get(),将得到一个无穷数列,利用这个Supplier,可以创建一个无穷的Stream:
public static void main(String[] args) {
Stream
natural.map((x) -> {
return x x;
}).limit(10).forEach(System.out::println);
}对这个Stream做任何map()、filter()等作都是完全可以的,这说明Stream API对Stream进行转换并生成一个新的Stream并非实时计算,而是做了延迟计算。
当然,对这个无穷的Stream不能直接调用forEach(),这样会无限打印下去。但是我们可以利用limit()变换,把这个无穷Stream变换为有限的Stream。
利用Stream API,可以设计更加简单的数据接口。例如,生成斐波那契数列,完全可以用一个无穷流表示(受限Ja的long型大小,可以改为BigInteger):
class FibonacciSupplier implements Supplier
long a = 0;
long b = 1;
@Override
public Long get() {
long x = a + b;
a = b;
b = x;
return a;
}}
public class FibonacciStream {
public static void main(String[] args) {
Stream
fibonacci.limit(10).forEach(System.out::println);
}}
如果想取得数列的前10项,用limit(10),如果想取得数列的第20~30项,用:
List
后通过collect()方法把Stream变为List。该List存储的所有元素就已经是计算出的确定的元素了。
用Stream表示Fibonacci数列,其接口比任何其他接口定义都要来得简单灵活并且高效。
版权声明:本文内容由互联。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发 836084111@qq.com 邮箱删除。