一、stream
描述:stream是jdk1.8引入的新特性,通过stream可以极大提高开发效率以及代码的简洁度。
我们可以把流(stream)当成一个元素集合,数据在流管道上传输,管道上存在一些节点(处理逻辑),通过节点的过滤、转换、排序等操作后能方便的获取到我们要的数据。通常我们对集合进行for操作称为外部迭代,而使用流对集合、数组等进行操作称为内部迭代。
接下来我们通过流的运行机制、流的创建、流的分类、收集器的使用、并行操作等几个方面来认识 java8 的 stream
1、stream运行机制
- 流中的所有操作都是链式调用,一个元素只迭代一次
- 每个中间操作都会返回一个新的流,流里面有一个属性sourceStage指向同一个地方,就是Head
- Head->nextStage->nextStage->...->null 结束
- 有状态操作会把无状态操作给截断,有状态操作将单独执行
- 并行环境下,有状态的中间操作不一定能并行
- parallel/sequetial为中间操作,但是不创建流,只是修改Head的并行标识
2、流的创建
(1)通过集合创建流: Collection.stream(创建串行流)、Collection.parallelStream(创建并行流)
List<String> list = new ArrayList<>();
list.add("1");
//串行流
Stream<String> stream1 = list.stream();
//并行流
Stream<String> stream2 = list.parallelStream();
(2)通过数组:Arrays.stream
IntStream stream1 = Arrays.stream(new int[]{5,2});
(3)创建数字流: IntStream.range、IntStream.rangeClosed、LongStream.range、LongStream.rangeClosed、Random.ints()、Random.longs()、Random.doubles()
//创建数字流:
IntStream stream1 = IntStream.of(2,3,4);
//通过Random建无限流,如果没有limit会建无限制的流元素导致报错,加了limit表示建10个元素的流
IntStream stream2 = new Random().ints().limit(10);
(4)自己创建流:Stream.generate、Stream.iterate
Stream<Integer> stream = Stream.generate(()->new Random().nextInt()).limit(10);
3、流的分类:中间操作和终止操作
(1)流的中间操作(分无状态操作和有状态操作):
无状态:map、mapToxxx(mapToInt)、flatMap、flatMapToxxx、filter、peek、unordered
有状态:distinct、sorted、limit、skip
(1.1)map则用于数据转换,如下例子
List<String> list = new ArrayList<>();
list.add("1");
list.add("3");
list.add("5");
//filter会将元素3过滤掉,即满足不为3的元素才放行;
//map会将元素进行转换,即将集合中的元素前面都加上data,最后打印出 data1 data5
list.stream().filter(x -> !"3".equals(x)).map(
x -> "data" + x
).forEach(System.out::println);
//将集合中的元素转为Integer类型,然后生成新的集合
List<Integer> list1 = list.stream().map(
x -> {
Integer a = Integer.valueOf(x);
return a;
}
).collect(Collectors.toList());
(1.2)flatMap(扁平化操作)主要用于流中的元素是一个集合的情况,如流中的a元素含有b属性,且b属性也是个集合,即我们可以通过flatMap获取到所有a元素里的所有b属性集合
List<List<Integer>> list = new ArrayList<>();
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(3);
List<Integer> list2 = new ArrayList<>();
list2.add(6);
list2.add(7);
list.add(list1);
list.add(list2);
//flatMap 打印出了 1 3 6 7
list.stream().flatMap(x -> x.stream()).forEach(System.out::println);
//打印出两个集合对象引用
list.stream().map(x -> x.stream()).forEach(System.out::println);
String[] strs = {"12","34","56"};
// 通过map将数组映射成为Stream<String[]>,打印出三个对象引用
//执行map操作后得到一个包含多个字符串的流
Arrays.stream(strs)
.map(str -> str.split(""))
.forEach(System.out::println);
// 通过flatMap将数组映射成为Stream<String>,打印出1 2 3 4 5 6
//flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,再将这些小的流扁平化成一个由所有字符串构成的流Steam<String>
Arrays.stream(strs)
.map(str -> str.split("")) // 映射成为Stream<String[]>
.flatMap(Arrays::stream) // 扁平化为Stream<String>
.forEach(System.out::println);
(1.3)peek主要用于debug,其功能基本和forEach一样,只不过其是中间操作,forEach为终止操作
List<String> list = new ArrayList<>();
list.add("1");
list.add("3");
//每个元素都被进行了两次打印
list.stream().peek(System.out::println).forEach(System.out::println);
(1.4)limit限制流产生个数,主要用于无限流
new Random().ints().limit(10).forEach(System.out::println);
(1.5)sorted用于比较元素的大小,进行排序
//按数组中元素的长度进行从短到长排序
Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).peek(System.out::println)
.sorted((o1,o2) -> o1.length() - o2.length()).forEach(System.out::println);
说明:有状态操作都需要两个参数,类似比较等,无状态不用。所以在流中,多个无状态可以以流的方式执行,但是遇到有状态则等待该有状态操作执行完才能继续执行。如a->b->c->d->e: 四个流程,a、b、d、e为无状态,c为有状态,则开始a、b会以流的方式交替执行,但不会进入c,执行完a、b后才执行c,c执行完才d、e开始交替执行
(2)流的终止操作(分非短路操作和短路操作)
非短路操作:forEach、forEachOrdered、collect、toArray、reduce、min、max、count
短路操作:findFirst、findAny、allMatch、anyMatch、noneMatch
(2.1)forEach用于循环对流中所有元素进行操作,如下例子
int[] nums = {3,57,24,-45,9};
//使用串行流,foreach打印按元素位置进行
IntStream.of(nums).forEach(System.out::println);
//使用并行流,foreach打印元素的顺序乱的
IntStream.of(nums).parallel().forEach(System.out::println);
(2.2)collect为收集器,主要是将操作完的流转为集合等,下面一节会细讲
List<String> list = Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).collect(Collectors.toList())
(2.3)reduce用于对元素进行合并操作,同时返回的对象是Optional
//拼接所有元素,元素之间用|分隔,返回 Optional对象,其含有判断空等各种函数,str.orElse("xx") 表示str为空时的操作
Optional<String> str = Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).reduce((s1, s2)->s1+"|"+s2);
//计算所有元素加起来的总长度
Integer i = Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).map(s->s.length()).reduce(0,(s1,s2)->s1+s2);
(2.4)max用于获取元素中的某项最大值
//获取所有元素中最大元素的单词个数
Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).max((s1,s2)->s1.length()-s2.length());
(2.5)findFirst,获取集合中的第一个元素
//生成一个随机元素,短路表示执行到一个满足条件就结束,所以这里生成一个元素就结束执行
new Random().ints().findFirst();
4、收集器collect的使用
//将数组元素的长度转为一个list集合,toSet()
Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).map(s->s.length()).collect(Collectors.toList());
//将数组元素的长度转为一个TreeSet集合
Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).map(s->s.length()).collect(Collectors.toCollection(TreeSet::new));
//统计所有元素的长度情况,能获取最大,最小,平均等元素长度信息
//注意,s->s.length()可以改为String::length,不会多产生一个类似lambda$0的函数,提高执行效率
Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).collect(Collectors.summarizingInt(String::length));
//分块,分成长度是否2的两个组,特殊的分组,返回map
Arrays.stream(new String[]{"123","sdfs","asdfsadf","sd"}).collect(Collectors.partitioningBy(s->s.length()==2));
//分组,按元素长度分为多个组
5、并行操作讲解
- 在流操作过程中,调用parallel产生并行流,调用sequential产生串行流。
- 如果一个流同时使用了parallel、sequential ,则流的串并行方式以最后调到的一个为准
- 并行流默认的线程数为机器的cpu个数,且默认使用jdk的线程池(ForkJoinPool.commonPool)
- 系统中可以通过:System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","10"); 修改并行线程数
6、其他
惰性求值: 流在没有终止调用(即没有调终止操作)的情况下,所有中间操作不会执行,如 :
//正常执行流中语句
Arrays.stream(new int[]{3,4,8,2})
.map(n->
{
System.out.println(n);
return n;
})
.sum();
// 没有终止操作,所以打印语句等都不会执行到
Arrays.stream(new int[]{3,4,8,2})
.map(n->
{
System.out.println(n);
return n;
});
二、lambda表达式
1、引入
使用lambda表达式创建线程
public void testThread(){
//匿名函数创建线程,相当于在run方法写System.out.println("a")
new Thread(()->System.out.println("a")).start();
Runnable r = ()->System.out.println("a") ;
new Thread(r).start();
}
2、函数接口定义
// 函数接口定义(只能有一个未实现方法,可以有default的实现方法方法)
// 另外可以在接口上面写@FunctionalInterface 注解声明这就是一个函数接口,这样比较规范。
interface Inter1{
int numtest(int i);
}
//下面三种方式通过匿名类实现接口的写法等价:
//其中->前面是函数接口中要实现的方法的输入,->后面是输出,看接口方法输入输出都是int,如果有多条操作语句则{}圈起来,同时要return
Inter1 i1 = i -> i*2;
Inter1 i2 = (i) -> i*2;
Inter1 i3 = i -> { return i*2; }
@FunctionalInterface
interface IM{
int aa(int x,int y);
}
IM lambda = (x,y)->x+y;
3、方法中参数为接口函数,调用方法举例
@FunctionalInterface
interface IFT{
String f(int i);
}
public static void test(IFT ift){
ift.f(123);
}
//调用test方法:
//定义了匿名类,即iFT接口的 f方法定义为: "data" + i,
//注意输入i是int,这里是123;输出为 data:123
test(i->"data:" + i);
4、上面的例子可以不定义接口,直接在方法上定义参数为函数
//Function<Integer,String>左边泛型表示输入为Integer,右边表示输出为String;apply为Function定义的默认方法
public static void test(Function<Integer,String> mf){
mf.apply(123);
}
//调用(多个操作语句用{},且最后return 为string):
//Function<Integer,String> mf = i -> {System.out.println(i); return "data:" + i;}
Function<Integer,String> mf = i -> "data:" + i;
test(mf); //输出 data:123
//另外,Function函数可以做其他操作,如这里在mf执行完后,对返回string的前面加test
test(mf.andThen(s->"test"+s); //输出 testdata:123
5、函数式编程(lambda)对应的接口:
- 断言:Predicate<T>,输入参数T,返回Boolean类型
- 消费一个数据,消费者:Consumer<T>,输入参数T,没有返回
- 输入T输出R的函数:Function<T,R>,输入参数T,返回R
- 提供一个数据,生产者:Supplier<T>,没有输入,输出T
- 一元函数(输入输出类型相同):UnaryOperator<T>,输入参数T,返回T
- 2个输入的函数:BiFunction<T,U,R>,输入参数(T,U),返回R
- 二元函数(输出输入类型相同):BinaryOperator<T>,输入参数(T,T),返回T
举例:
(1)断言函数接口(输入T,输出boolean):
public void testPredicate(){
//Integer表示输入的i为int类型,另外输入是Integer,可以使用提供的 IntPredicate函数即可去掉泛型
//IntPredicate pre = i -> i>0;
Predicate<Integer> pre = i -> i > 0;
//调用
boolean preResult = pre.test(1);
}
(2)消费函数接口(输入T,无输出):
public void testConsumer(){
//消费一个String数据,不用返回,可以使用 IntConsumer 等
Consumer<String> con = s->System.out.println(s);
//调用
con.accept("输入");
}
5、通过lambda调用几种类型的方法:
(1)方法引用
匿名实现类使用到的变量需要定义为final,下面 s -> System.out.println(aa + s) 其实就是匿名实现类,这个过程用到的变量必须是final修饰的,即赋值后不可变
final String aa = "test";
Consumer<String> con = s -> System.out.println(aa + s);
(2)静态方法的方法引用
若类ABC中有个静态的test方法,该方法有一个String的参数,没有返回
Consumer<String> consumer = ABC::test;
consumer.accept("abc");
(3)非静态方法的方法引用
若类ABC中有个非静态的test方法,该方法有一个Integer的参数,输出为Integer
(3.1)使用对象实例调用:
ABC abc = new ABC();
//输入输出类型一样时可以使用UnaryOperator:
//UnaryOperator<Integer> fun = abc::test;
//等价于 IntUnaryOperator fun = abc::test; (调用: fun.applyAsInt(2);)
Function<Integer,Integer> fun = abc::test;
fun.apply(3);
(3.2)使用类名方法引用调用:
//两个输入参数(ABC,Integer),输出int
BiFunction<ABC, Integer, Integer> bfun= ABC::test();
bfun.apply(new ABC(),3);
(4)构造函数的方法引用
ABC为对应的类
(4.1)无参构造:
//Supplier无输入,ABC输出
Supplier<ABC> su = ABC::new;
su.get();
(4.2)带参构造(有一个String的参数):
Function<String,ABC> fun2 = ABC::new;
fun2.apply("23");
6、其他
(6.1)对于只有一个输入参数的函数,调用时可以使用 ::调用,如: Consumer<String> con = System.out::println ,尽量使用该方式调用,对性能有影响。
(6.2)级联表达式/柯里化(函数的输出还是一个函数):
把多个参数的函数转换为只有一个参数的函数 ,如(可以看出虽然有几个参数,实际每次都是调用一个参数的函数):
//第一层的调用中,输入为Integer类型的 x,输出为Function的 y->x+y
Function<Integer,Function<Integer,Integer>> fun = x->y->x+y;
//调用,输出5
fun.apply(2).apply(3);