美文网首页
JAVA8新特性:Stream与Lambda表达式

JAVA8新特性:Stream与Lambda表达式

作者: z8756413 | 来源:发表于2018-06-03 20:21 被阅读0次

    一、Stream学习

    在学习lambda表达式之前,我们需要先了解Stream这个java8的新特性。

    Stream(流): 是一个来自数据源的元素队列并支持聚合操作

    元素队列:元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
    数据源:流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
    聚合操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
    和以前的Collection操作不同, Stream操作还有两个基础的特征:
    Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
    内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
    下面我们可以看一个比较简单的Stream表达式以及相关的对应操作,这里可以看出,Stream流一般配合lambda表达式以及方法引用使用

    List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
    // 为队列strings建立流,并通过filter筛选需要的非空成员,最后成为新的list
    List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    

    从上面的例子我们可以看到,流的操作流程一般可以分成三类:
    创建流:创建一个Stream。
    中间方法:在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作。一般是对数据集的整理(过滤、排序、匹配、抽取等)。
    包括:filter()、distinct()、sorted()、map()、flatMap()等
    终止方法:往往是完成对数据集中数据的处理,通过终止(terminal)方法来产生一个结果。该操作会强制它之前的延时操作立即执行,这之后该Stream就不能再被使用了。
    如forEach(),还有allMatch()、anyMatch()、findAny()、 findFirst(),数值计算类的方法有sum、max、min、average等等。终止方法也可以是对集合的处理,如reduce()、 collect()等等。reduce()方法的处理方式一般是每次都产生新的数据集,而collect()方法是在原数据集的基础上进行更新,过程中不产生新的数据集。

    二、Stream相关方法

    forEach
    Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。

    // 为队列strings建立流,并通过foreach遍历并输出所有元素
    //注意,该处::为方法引用,作为stream参数含义为,将方法传递给stream,将其内部每个成员都作为参数执行一遍该方法。
    strings.stream().forEach(System.out::print);
    

    map

    Stream提供的方法之一,将stream内每个元素都根据lambda表达式或者方法引用的逻辑映射到对应的结果

    //将string队列中的元素每个都计算其长度并输出到一个新的list中
    List<Integer> lengthList = strings.stream().map((String s)->(s.length())).collect(Collectors.toList());
    

    filter
    Stream提供的方法之一,根据提供的lambda表达式,将流中的每个元素过一遍条件,最终stream中只留存满足条件的成员

    // 为队列strings建立流,并通过filter筛选需要的非空成员,最后成为新的list
    List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
    

    limit
    Stream提供的方法之一,只保留流内前n个成员

    sorted
    Stream提供的方法之一,对流内元素进行排序。可以传入Compator比较器,如不传入参数,成员需实现Comparable接口,默认为自然序升序,采用经过优化的归并排序。

    List<Integer> numList = Lists.newArrayList(5,3,1,7,1);
    //升序输出
    List<Integer> sortedList = numList.stream().sorted().collect(Collectors.toList());
    //降序输出,利用Comparator的reverse
    List<Integer> reverseSortedList = numList.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
    

    二、lambda表达式
    lambda表达式为java8提供的一套语法糖,本质上为由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。作为java8的一种新特性,lambda表达式可以让java代码表现的更加简单、优雅,同时可以更加符合函数式编程的特性。但最好不要到处都用,对于一些关键的业务逻辑,传统的方式更加易于维护以及调试。大数量下的遍历以及数据处理,增强的for-each也比lambda表达式效率更高。

    Lambda表达式的语法
    基本语法:
    (parameters) -> expression


    (parameters) ->{ statements; }

    可以通过简单的例子来了解lambda表达式的特性。下面是几个简单的lambda表达式,

    // 1. 不需要参数,返回值为 5 
    () -> 5 
       
    // 2. 接收一个参数(数字类型),返回其2倍的值 
    x -> 2 * x 
       
    // 3. 接受2个参数(数字),并返回他们的差值 
    (x, y) -> x – y 
       
    // 4. 接收2个int型整数,返回他们的和 
    (int x, int y) -> x + y 
       
    // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void) 
    (String s) -> System.out.print(s)
    

    以上就是一些简单的lambda表达式,下面我们可以通过一个遍历list的例子,来看看lambda表达式是如何使用的。

    String[] atp = {"Rafael Nadal", "Novak Djokovic", 
           "Stanislas Wawrinka", 
           "David Ferrer","Roger Federer", 
           "Andy Murray","Tomas Berdych", 
           "Juan Martin Del Potro"}; 
    List<String> players =  Arrays.asList(atp); 
       
    // 以前的循环方式 
    for (String player : players) { 
         System.out.print(player + "; "); 
    } 
       
    // 使用 lambda 表达式以及函数操作(functional operation) 
    players.forEach((player) -> System.out.print(player + "; ")); 
        
    // 在 Java 8 中使用双冒号操作符(double colon operator) 
    players.forEach(System.out::println);
    

    在上面的表达式中,后面使用了三种java8中新加入的语法糖,foreach、lambda以及双冒号操作符,双冒号操作符在java8中的意义为方法引用。

    本质上,lambda表达式所返回的是“编译器认为该处不会导致歧义并应该返回的格式”,下面针对匿名内部类的例子具体讲解

    //test.foreach()方法中,我们需要一个BiConsumer<? super K, ? super V>类型的参数,那么构建这个参数本来如下所示
    Consumer<Integer> consumer = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.println(integer + ";");
        }
    };
    test.forEach(consumer);
     
    //从上面我们可以看到,这里是使用了一个匿名内部类Consumer,
    //在jdk1.8中,Consumer已经被注解为了一个函数式接口(除了与Object的public方法签名一致的方法外,只有一个抽象方法)
    //为什么Object的public方法可以豁免,是因为当实现接口时,所有实现类都会继承Object父类,因此不会出现歧义,因此在注解成为函数式接口时,编译器可以跳过Object的public签名一致的接口
    //那么,当我们在对匿名类实例化时,编译器会自动实现相关的抽象方法,当我们加入lambda表达式时,编译器可以判断出我们是想覆盖这个唯一的抽象方法。
    test.forEach((player) -> System.out.print(player + "; "));
     
     
    //这里也是一样
    Collections.sort(test, (o1, o2)->o1-o2);**
    

    相关文章

      网友评论

          本文标题:JAVA8新特性:Stream与Lambda表达式

          本文链接:https://www.haomeiwen.com/subject/wcthsftx.html