Function
Predicate
流与集合相比的优点
流的延迟执行特点
集合、映射或数组获取流
常用的流操作
流进行并发操作:并行流
流中内容收集到集合中
collect
将流中内容收集到数组中
toArray();
Predicate接口
Function接口
Stream流
需要对某种类型的数据进行判断,从而得到一个boolean值结果。则可以使用java.util.function.Predicate
接口。
boolean test(T t) 根据参数t执行判断,返回true或false
test
方法示例:
定义方法:参数是Predicate
接口类型,返回值是boolean
类型
在main
方法调用方法判断字符串长度是否大于5
public class PredicateDemo01 {
public static void main(String[] args){
// 使用匿名内部类调用
testPredicate(new Predicate() {
@Override
public boolean test(String s) {
return s.length() > 5;
}
});
// 使用lambda表达式简化
testPredicate(s -> s.length()>5);
}
public static void testPredicate(Predicate predicate){
boolean b = predicate.test("helloworld");
System.out.println(b);
}
}
条件判断,则存在与、或、非三种常见逻辑关系。将两个Predicate
条件使用“与”逻辑连接起来实现“and”效果时,可以使用default方法and
。其JDK源码为:
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
and
方法演示示例
判断一个字符串包含大写“H”,且包含大写“W”
public class PredicateDemo02 {
public static void main(String[] args) {
// 定义字符串
String str = "HelloWorld";
// 条件1:判断是否包含大写H
Predicate one = s -> s.contains("H");
// 条件2:判断是否包含大写W
Predicate two = s -> s.contains("W");
// 判断字符串是否同时满足条件1和条件2
// System.out.println(one.test(str) && two.test(str));
// 简化版
boolean b = one.and(two).test(str);
System.out.println(b);
}
}
与and
的“与”类似,默认方法or
实现逻辑关系中的“或”。JDK源码为:
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
or
方法演示示例
判断一个字符串包含大写H或者包含大写W”
public class PredicateDemo03 {
public static void main(String[] args) {
// 定义字符串
String str = "helloworld";
// 条件1:判断是否包含大写H
Predicate one = s -> s.contains("H");
// 条件2:判断是否包含大写W
Predicate two = s -> {
System.out.println("执行了吗");
return s.contains("W");
};
// 判断字符串是否满足条件1和条件2的其中一个
boolean b = one.or(two).test(str);
System.out.println(b);
}
}
默认方法negate
的JDK源代码为:
default Predicate negate() {
return (t) -> !test(t);
}
negate ! : 执行test方法之后,对结果boolean值进行“!”取反。要在test
方法调用之前调用negate
方法,
与and
和or
方法一样:
import java.util.function.Predicate;
public class Demo17PredicateNegate {
private static void method(Predicate predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s -> s.length() < 5);
}
}
1. 需求说明
数组中有多条“姓名+性别”的信息如下,通过Predicate
接口的拼装将符合要求的字符串筛选到集合ArrayList
中
满足两个条件:
必须为女生;
姓名为4个字。
public class DemoPredicate {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
}
}
2. 代码实现
public class PredicateDemo05 {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
// 条件1:必须是女生
// boolean test(String t)
Predicate one = s -> s.split(",")[1].equals("女");
// 条件2:姓名为4个字
Predicate two = s -> s.split(",")[0].length() == 4;
// 遍历数组
for (String str : array) {
// 判断str是否同时满足条件1和条件2
if(one.and(two).test(str)){
System.out.println(str);
}
}
}
}
java.util.function.Function
R apply(T t) 将参数t的类型从T类型转换为R类型。
apply
方法示例:将String
类型转换为Integer
类型。
public class FunctionDemo01 {
public static void main(String[] args){
// 字符串数字
String str = "100";
// 将字符串数字转换为整型数字
Function f = new Function() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
};
int num = f.apply(str);
System.out.println(num);
// 使用lambda简化匿名内部类
// Integer apply(String t)
Function f1 = s -> Integer.parseInt(s);
System.out.println(f1.apply("200"));
// 使用方法引用简化lambda表达式
Function f2 = Integer::parseInt;
System.out.println(f2.apply("300"));
}
}
Function
接口中有一个默认的andThen
方法,用来进行组合操作。JDK源代码如:
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
andThen
方法示例:第一个操作是将字符串解析成为int数字,第二个操作是乘以10。两个操作按照前后顺序组合到一起。
public class FunctionDemo02 {
public static void main(String[] args){
// 字符串
String str = "250";
// 操作1:将字符串转换基本数据类型:"250" ==> 250
Function one = Integer::parseInt;
// 操作2:将操作1执行的结果乘以10
Function two = num -> num * 10;
// 执行操作1和操作2
/* int num = one.apply(str);
int result = two.apply(num);*/
// 执行操作1和操作2:将前一个操作执行的结果作为后一个操作的参数
int result = one.andThen(two).apply(str);
System.out.println(result);
}
}
1. 需求说明
使用Function
进行函数模型拼接,按照顺序需要执行的多个函数操作为:
将字符串赵丽颖,20
截取数字年龄部分,得到字符串;
将上一步的字符串转换成为int类型的数字;
将上一步的int数字累加100,得到结果int数字。
2. 代码实现
public class FunctionDemo03 {
public static void main(String[] args){
// 字符串
String str = "赵丽颖,20";
// 操作1:"赵丽颖,20" ==> "20"
Function one = s -> s.split(",")[1];
// 操作2:"20" ==> 20
Function two = Integer::parseInt;
// 操作3:20 ==> 120
Function three = num ->{
System.out.println("执行了吗");
return num + 100;
};
// 按顺序执行三个操作
int result = one.andThen(two).andThen(three).apply(str);
// int result = three.compose(two).compose(one).apply(str);
System.out.println(result);
}
}
常用函数式接口中,方法可分为两种:
延迟方法:在拼接Lambda函数模型的方法,并不立即执行得到结果。
终结方法:根据拼好的Lambda函数模型,立即执行得到结果值的方法。
通常情况下,常用的函数式接口中唯一的抽象方法为终结方法,而默认方法为延迟方法。但这并不是绝对的。
表格中进行方法分类的整理:
接口名称 | 方法名称 | 抽象/默认 | 延迟/终结 |
---|---|---|---|
Supplier | get | 抽象 | 终结 |
Consumer | accept | 抽象 | 终结 |
andThen | 默认 | 延迟 | |
Predicate | test | 抽象 | 终结 |
and | 默认 | 延迟 | |
or | 默认 | 延迟 | |
negate | 默认 | 延迟 | |
Function | apply | 抽象 | 终结 |
andThen | 默认 | 延迟 | |
compose | 默认 | 延迟 |
备注:JDK中更多内置的常用函数式接口,请参考
java.util.function
包的API文档。
JDK1.8更新最大的亮点除lamada表达式之外,还有极其惊艳的Stream。但Stream不是IO里面的OutputStream或InputStream之类,而是对java中Collection类功能的增强。Stream API配合lamada表达式,极大的提升了编程效率和程序可读性。Stream中支持并行和串行两种方式。
有如下集合
List list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
按要求执行以下三个操作
首先筛选所有姓张的人。
然后筛选名字有三个字的人。
最后进行对结果进行打印输出。
public class StreamDemo01 {
public static void main(String[] args){
List list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
List oneList = new ArrayList<>();
// 使用jdk1.8之前的方式实现
for (String str : list) {
// 判断是否是姓张的
if(str.startsWith("张")){
oneList.add(str);
}
}
List twoList = new ArrayList<>();
for (String name : oneList) {
// 判断是否是三个字
if (name.length() == 3){
twoList.add(name);
}
}
/* // 打印输出twoList集合的元素
for (String name : twoList) {
System.out.println(name);
}
*/
}
}
jdk1.8前:
对集合中的元素进行操作的时,总需要对集合进行多次循环。尤其是:使用线性循环时,意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
jdk1.8后:
Lambda的衍生物Stream可直接遍历独个循环,未使用到的循环遍历可延迟执行.
public class StreamDemo02 {
public static void main(String[] args){
List list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
// 使用Stream流实现
list.stream()
.filter(s->{
return s.startsWith("张");
})
.filter(s->{
return s.length() == 3;
})
.forEach(System.out::println);
}
}
直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。
java.util.stream.Stream
是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流
常用方式:
所有的Collection
集合都可以通过stream
默认方法获取流;
Stream
接口的静态方法of
可以获取数组对应的流。
java.util.Collection
接口中加入了default方法stream
用来获取流,所以其所有实现类均可获取流。
调用集合对象的stream()方法获得Stream对象
java.util.Map接口不是
Collection`的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
// 获得键对应Stream流对象
Stream keyStream = map.keySet().stream();
// 获得值对应Stream流对象
Stream valueStream = map.values().stream();
// 获得Entry对象对应Stream流对象
Stream> entryStream = map.entrySet().stream();
如果使用的不是集合或映射而是数组,由于数组对象不添加默认方法.
所以Stream
接口中提供静态方法of
:
使用Demo:
* 通过Stream接口的静态方法of来获得
// 字符串数组
String[] strs = {"a"};
// 获得流关联数组
Stream arrayStream = Stream.of(strs);
public class StreamDemo01 {
public static void main(String[] args){
// 创建List集合
List list = new ArrayList<>();
// 获得流对象
Stream stream01 = list.stream();
// 创建Set集合
Set set = new HashSet<>();
Stream stream02 = set.stream();
// 创建Map集合
Map map = new HashMap<>();
// 获得键对应Stream流对象
Stream keyStream = map.keySet().stream();
// 获得值对应Stream流对象
Stream valueStream = map.values().stream();
// 获得Entry对象对应Stream流对象
Stream> entryStream = map.entrySet().stream();
// 字符串数组
String[] strs = {"a"};
// 获得流关联数组
Stream arrayStream = Stream.of(strs);
// 直接创建流对象
Stream a = Stream.of("a", "b");
}
}
流操作常用方法
分成两种:
终结方法
非终结方法
1.4.1.1 方法声明
Stream filter(Predicate predicate)
* 将流中满足条件的元素添加到另一个流中,返回新的流
1.4.1.2 Predicate接口
java.util.stream.Predicate
函数式接口,其中唯一的抽象方法为:
boolean test(T t);
该方法将会产生一个boolean值结果,判断指定的条件是否满足。条件结果为true,则Stream流的filter
方法将留用元素;条件结果为false,则filter
方法将会舍弃元素。
1.4.1.3 基本使用
Stream流中的filter
方法基本使用代码如:
public class StreamDemo02 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "bbxx", "abcde");
// Predicate函数式接口的抽象方法:boolean test(T t)
Stream stream02 = stream01.filter(s -> s.length() >= 4);
// 获得流的元素个数并输出
System.out.println(stream02.count());
}
}
1.4.2.1 方法声明如下
long count()
* 获得流中元素的个数
1.4.2.2 基本使用
Stream流中的count
方法基本使用代码如:
public class StreamDemo02 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "bbxx", "abcde");
// Predicate函数式接口的抽象方法:boolean test(T t)
Stream stream02 = stream01.filter(s -> s.length() >= 4);
// 获得流的元素个数并输出
System.out.println(stream02.count());
}
}
1.4.3.1 方法声明如下
Stream limit(long maxSize)
* 将流中前maxSize个元素获取到另一个流中
* maxSize等于零,则会获取一个空流。
* maxSize大于当前流中元素个数,则会将当前流的所有元素获取到另一个流中
* maxSize必须大于等于0
1.4.3.2 基本使用
Stream流中的limit
方法基本使用代码如:
public class StreamDemo03 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "dbc", "dde","abe");
// 获取前2个元素放到另一个流中
Stream stream02 = stream01.limit(5);
System.out.println(stream02.count());
}
}
1.4.4.1 方法声明如下
Stream skip(long n)
* 跳过当前流中的前n个元素,将前n个元素后的所有元素添加到另一流中,返回新流。
* n必须大于等于0
* n大于当前流中的元素个数,则会产生一个空流。
1.4.4.2 基本使用
public class StreamDemo04 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("abc", "dbc", "dde","abe");
// 跳过当前流中的前1个元素,
Stream stream02 = stream01.skip(5);
System.out.println(stream02.count());
}
}
使用map操作遍历集合中的每个对象,并对遍历结果进行处理,map之后用.collect(Collectors.toList())会得到操作后的集合。方法声明如下
* Stream map(Function mapper)
* 将当前流中元素的类型从T类型转换为R类型,存储到另一个流中返回
该接口需要一个Function
函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
1.4.5.1 Function接口
java.util.stream.Function
函数式接口,其中唯一的抽象方法为:
R apply(T t);
将一种T类型转换成为R类型,而这转换的过程,则称为“映射”。
1.4.5.2 基本使用
Stream流中的map
方法基本使用的代码如:
public class StreamDemo05 {
public static void main(String[] args){
// 创建流对象
Stream stream01 = Stream.of("aaa", "456", "789","12345");
// 将流中的字符串都转换为整型存储到另一个流中
// Function函数式接口的抽象方法:R apply(T t)
Stream stream02 = stream01.map(str -> {
System.out.println(str);
return Integer.parseInt(str);
});
System.out.println(stream02.count());
}
}
1.4.6.1 方法声明如下
concat方法的声明如下:
* static Stream oncat(Stream a, Stream b)
* 将流a和流b合并为一个流
1.4.6.2 基本使用
public class StreamDemo06 {
public static void main(String[] args){
// 创建流对象
Stream streamA = Stream.of("aaa", "456", "789","12345");
Stream streamB = Stream.of("aaa", "456", "789","12345");
// 合并流中元素产生新的流
Stream streamC = Stream.concat(streamA, streamB);
// 将流中的元素传递给消费者逐一处理
streamC.forEach(System.out::println);
}
}
1.4.7.1 方法声明如下
void forEach(Consumer c);
* 将流中的元素传递给消费者逐一处理
1.4.7.2 基本使用
public class StreamDemo07 {
public static void main(String[] args){
// 创建流对象
Stream streamA = Stream.of("aaa", "456", "789","12345");
// 将流中的元素传递给消费者逐一处理
streamA.forEach(System.out::println);
}
}
方法引用System.out::println便是一个
Consumer`函数式接口的实例。
在上述介绍的各种方法中,凡是返回值仍然为Stream
接口的为非终结方法(函数拼接方法),它们支持链式调用;而返回值不再为Stream
接口的为终结方法,不再支持链式调用。如下表所示:
方法名 | 方法作用 | 方法种类 | 是否支持链式调用 |
---|---|---|---|
count | 统计个数 | 终结 | 否 |
forEach | 逐一处理 | 终结 | 否 |
filter | 过滤 | 函数拼接 | 是 |
limit | 取用前几个 | 函数拼接 | 是 |
skip | 跳过前几个 | 函数拼接 | 是 |
map | 映射 | 函数拼接 | 是 |
concat | 组合 | 函数拼接 | 是 |
案例说明
现在有两个ArrayList
集合存储队伍当中的多个成员姓名
两个队伍(集合)的代码如下:
import java.util.ArrayList;
import java.util.List;
public class DemoArrayListNames {
public static void main(String[] args) {
List one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// ....
}
}
而Person
类的代码如下
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
要求如下:
使用传统的for循环(或增强for循环)依次进行以下若干操作步骤
使用Stream方式依次进行以下若干操作步骤
1. 第一个队伍只要名字为3个字的成员姓名;
2. 第一个队伍筛选之后只要前3个人;
3. 第二个队伍只要姓张的成员姓名;
4. 第二个队伍筛选之后不要前2个人;
5. 将两个队伍合并为一个队伍;
6. 根据姓名创建Person对象;
7. 打印整个队伍的Person对象信息。
public class StreamDemo01 {
public static void main(String[] args) {
List one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
// ....
// 1. 第一个队伍只要名字为3个字的成员姓名;
// 2. 第一个队伍筛选之后只要前3个人;
Stream oneStream = one.stream()
.filter(s -> s.length() == 3)
.limit(3);
// 3. 第二个队伍只要姓张的成员姓名;
// 4. 第二个队伍筛选之后不要前2个人;
Stream twoStream = two.stream()
.filter(s -> s.startsWith("张"))
.skip(2);
// 5. 将两个队伍合并为一个队伍;
// 6. 根据姓名创建Person对象;
// 7. 打印整个队伍的Person对象信息。
Stream.concat(oneStream, twoStream)
.map(Person::new)
.forEach(System.out::println);
}
}
对流操作完成之后,则需要将其结果进行收集.
Stream流提供collect
方法,其参数需要一个java.util.stream.Collector
接口对象来指定收集到哪种集合中。java.util.stream.Collectors 类提供一些方法,可以作为
Collector`接口的实例:
public static Collector> toList():转换为List集合。
public static Collector> toSet():转换为Set集合。
基本使用
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Demo15StreamCollect {
public static void main(String[] args) {
Stream stream = Stream.of("10", "20", "30", "40", "50");
List list = stream.collect(Collectors.toList());
Set set = stream.collect(Collectors.toSet());
}
}
Stream提供toArray
方法来将结果放到一个数组中,由于泛型擦除的原因,返回值类型是Object[]的:
Object[] toArray();
基本使用
import java.util.stream.Stream;
public class Demo16StreamArray {
public static void main(String[] args) {
Stream stream = Stream.of("10", "20", "30", "40", "50");
Object[] objArray = stream.toArray();
}
}
解决泛型数组问题
有了Lambda和方法引用之后,可以使用toArray
方法的另一种重载形式传递一个IntFunction
的函数,继而从外面指定泛型参数。方法声明如下:
A[] toArray(IntFunction generator);
上例代码中不再局限于Object[]
结果,则可以得到String[]
结果:
import java.util.stream.Stream;
public class Demo17StreamArray {
public static void main(String[] args) {
Stream stream = Stream.of("10", "20", "30", "40", "50");
String[] strArray = stream.toArray(String[]::new);
}
}
既然数组是有构造器的,则传递一个数组的构造器引用即可。
以上例子中使用的是串行流,即单线程执行的,其实Stream API中提供有并行流,即多线程执行操作。
java.util.Collection
新添加两个默认方法
default Stream stream() : 返回串行流
default Stream parallelStream() : 返回并行流
stream()
和parallelStream()
方法返回的均是java.util.stream.Stream
类型的对象,则说明它们在功能的使用上是没差别的。唯一的差别就是单线程和多线程的执行。
Stream
的父接口java.util.stream.BaseStream
中定义一个parallel
方法:
S parallel();
在流上调用一下无参数的parallel
方法,则当前流即可改变成为支持并发操作的流,返回值仍然为Stream
类型。例如:
import java.util.stream.Stream;
public class Demo13StreamParallel {
public static void main(String[] args) {
Stream stream = Stream.of(10, 20, 30, 40, 50).parallel();
}
}
在通过集合获取流时,可以直接调用parallelStream
方法来直接获取支持并发操作的流。方法定义为:
default Stream parallelStream() {...}
应用代码为:
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Stream;
public class Demo13StreamParallel {
public static void main(String[] args) {
Collection coll = new ArrayList<>();
Stream stream = coll.parallelStream();
}
}
/**
* @author pkxing
* @version 1.0
* @description 并发流
* @date 2018/3/5
*/
public class StreamDemo02 {
@Test
public void testStream() {
List list = new ArrayList<>();
// 将1000-1存入list中
for (int i = 1000; i >= 1; i--) {
list.add(i);
}
// 使用串行流:不开线程执行,在当前线程执行
// List result = list.stream()
// .map((i)-> {
// System.out.println("i = " +Thread.currentThread().getName());
// return String.valueOf(i);
// }).collect(Collectors.toList());
// 使用并发流:开多个线程执行
List result = list.parallelStream()
.map((i)-> {
System.out.println("i = " +Thread.currentThread().getName());
return String.valueOf(i);
}).collect(Collectors.toList());
System.out.println(result);
}
}