java8新特性(stream/lambda)

一、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)对应的接口:

  1. 断言:Predicate<T>,输入参数T,返回Boolean类型
  2. 消费一个数据,消费者:Consumer<T>,输入参数T,没有返回
  3. 输入T输出R的函数:Function<T,R>,输入参数T,返回R
  4. 提供一个数据,生产者:Supplier<T>,没有输入,输出T
  5. 一元函数(输入输出类型相同):UnaryOperator<T>,输入参数T,返回T
  6. 2个输入的函数:BiFunction<T,U,R>,输入参数(T,U),返回R
  7. 二元函数(输出输入类型相同):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); 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值