lambda 表达式

简介

在我看来 lambda 表达式就是简化了以前的一大堆繁琐的操作,让我们代码看起来更加简洁,让以前五六行甚至更多行的代码只需要两三行就能解决,但是对于 Java 初学者可能不是特别友好,可能一下子理解不过来该代码想表达什么。

lambda 表达式是一段可以传递的代码,因此它可以被执行一次或多次

lambda 表达式的语法

我们先来看看老版本的排序字符串的办法,这里我们不按照字典序,而按照字符串的大小来排序

  1. Comparator<String> comparator = new Comparator<String>() {
  2. @Override
  3. public int compare(String o1, String o2) {
  4. return Integer.compare(o1.length(),o2.length());
  5. }
  6. };
  7. List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa");
  8. Collections.sort(list,comparator);// [a, aa, aaa, aaaa, aaaaa]

老版本的排序我们会先创建一个自定义比较器,然后按照比较器的规则进行排序。

现在我们来看看 lambda 表达式如何来实现的

  1. List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa");
  2. Collections.sort(list,(String o1,String o2)->{
  3. return Integer.compare(o1.length(),o2.length());
  4. });//[a, aa, aaa, aaaa, aaaaa]

可以看到,代码浓缩了不少,但是可读性没有原来好,原来需要先创建一个比较器然后将比较器传到 Collections 工具类进行排序,典型的面向对象编程,但是 lambda 表达式确是将代码传进去然后直接进行比较。如果你以为这样就是最简便的,那你就错了,有更简便的。

  1. List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa");
  2. Collections.sort(list,(String o1,String o2)
  3. ->Integer.compare(o1.length(),o2.length()));

如果返回值只有一行,可以省略大括号和 return 关键字。

  1. List<String> list = Arrays.asList("aaaa", "aaa", "aa", "a", "aaaaa");
  2. Collections.sort(list,(o1,o2)->Integer.compare(o1.length(),o2.length()));

如果是带泛型的容器,参数的类型可以省略,JVM 会自己进行上下文判断出类型。

我们可以看到从原来的那么多行代码浓缩成了一行,看着清爽了很多,但是可读性却没有原来那么友好了。

变量作用域

  • 访问局部变量

    可以在 lambda 表达式中访问外部的局部变量

    1. int number = 10;
    2. Converter<String,Integer> converter = num->Integer.valueOf(num + number);
    3. System.out.println(converter.convert("123"));//12310

    在匿名内部类中外部的局部变量必须声明为 final。而我们这里不需要。

    但是要注意的是这的 number 不能被后面的代码修改,否则编译不通过,也就是具有隐性的 final 语义。

  • 访问 字段和静态变量

    我们对 lambda 表达式中的实例字段和静态字段变量都有读写访问权限。

    1. class Lambda4 {
    2. static int outerStaticNum;
    3. int outerNum;
    4. void testScopes() {
    5. Converter<Integer, String> stringConverter1 = (from) -> {
    6. outerNum = 23;
    7. return String.valueOf(from);
    8. };
    9. Converter<Integer, String> stringConverter2 = (from) -> {
    10. outerStaticNum = 72;
    11. return String.valueOf(from);
    12. };
    13. }
    14. }
  • 无法在 lambda 表达式中访问默认接口方法。

函数式接口

在 Java 中有许多已有的接口都选哦封装成代码块,比如 Runnable 或者 Comparator 。 lambda 表达式与这些接口是像后兼容的。

对于只包含一个抽象方法的接口,但是可以有多个非抽象方法,(非抽象方法也是 java 8 新特性,我们后面会讲到),我们可以通过 lambda 表达式来创建该接口的对象。这种接口被称为函数式接口。

Java 8 新增加了一种特殊的注解 @FunctionalInterface,该接口会自动判断你的接口中是否只有一个抽象方法,如果多于一个抽象方法就会报错。

现在我们来自定义一个函数式接口:

  1. @FunctionalInterface
  2. interface Converter<F,T>{
  3. T convert(F num);
  4. }
  1. // 将数字形式的字符串转化成整型
  2. Converter<String,Integer> converter = (num -> Integer.valueOf(num));
  3. System.out.println(converter.convert("123").getClass());//class java.lang.Integer

现在来解释下该代码,在该代码中我们的函数式接口中定义了一个方法,该方法能够实现传入一个 F 类型的参数,我们可以对这个类型的参数进行各种处理,最后返回一个 T 类型的结果。在这里我只是简单的将传进来的 string 转成了 integer。这里的 F 与 T 都是泛型类型,可以为任何实体类。

java 8 帮我们实现了很多函数式接口,大部分都不需要我们自己写,这些接口在 java.util.function 包 里,可以自行进行查阅。

上面的代码可以写的更加简单:

  1. Converter<String,Integer> converter = Integer::valueOf;
  2. System.out.println(converter.convert("123").getClass());//class java.lang.Integer

java 8 可以通过 ** : : **来传递方法或者构造函数的引用。上面的演示了如果引用静态方法,引用对象方法也相差不大,只是需要声明一个对象:

  1. class Demo{
  2. public Integer demo(String num){
  3. return Integer.valueOf(num);
  4. }
  5. }
  6. public class Main {
  7. public static void main(String[] args) {
  8. Demo demo = new Demo();
  9. Converter<String,Integer> converter = demo::demo;
  10. System.out.println(converter.convert("123").getClass());
  11. //class java.lang.Integer
  12. }
  13. }

内置函数式接口

  • Predicates

    Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)

    1. @FunctionalInterface
    2. public interface Predicate<T> {
    3. // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.
    4. boolean test(T t);
    5. ....
    6. }
  • Functions

    Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen)

    1. @FunctionalInterface
    2. public interface Function<T, R> {
    3. //将Function对象应用到输入的参数上,然后返回计算结果。
    4. R apply(T t);
    5. ...
    6. }
  • Suppliers

    Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。

  • Consumers

    Consumer 接口表示要对单个输入参数执行的操作。

  • Comparators

    Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法

默认方法

前面已经有写地方提到了接口的默认方法,这里对其做下介绍。接口的默认方法也是 java 8 新出的功能。能够通过使用 default 关键字向接口添加非抽象方法实现。

  1. interface Formula{
  2. double calculate(int a);
  3. default double sqrt(int a) {
  4. return Math.sqrt(a);
  5. }
  6. }

Formula 接口中除了抽象方法计算接口公式还定义了默认方法 sqrt。 实现该接口的类只需要实现抽象方法 calculate。 默认方法sqrt 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。

  1. public class Main {
  2. public static void main(String[] args) {
  3. // TODO 通过匿名内部类方式访问接口
  4. Formula formula = new Formula() {
  5. @Override
  6. public double calculate(int a) {
  7. return sqrt(a * 100);
  8. }
  9. };
  10. System.out.println(formula.calculate(100)); // 100.0
  11. System.out.println(formula.sqrt(16)); // 4.0
  12. }
  13. }

formula 是作为匿名对象实现的。该代码非常容易理解,6行代码实现了计算 sqrt(a * 100)

不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。

Stream(流)

Stream 是在 java.util 下的。Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回 Stream 本身,这样我们就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection 的子类:List 或者 Set。Map 不支持。Stream 的操作可以串行执行或者并行执行。

当我们使用 Stream 时,我们将通过三个阶段来建立一个操作流水线。

  1. 创建一个 Stream。
  2. 在一个或多个步骤中,指定将初始 Stream 转换成为另一个 Stream 的中间操作。
  3. 使用一个终止操作来产生一个结果。该操作会强制它之前的延迟操作立即执行。

在这之后 stream 就不会再被使用了。

创建 stream

通过 Java 8 在 Collection 接口中新提娜佳的 stram 方法,可以将任何集合转化为一个 Stream。如果我们面对的是一个数组,也可以用静态的 Stream.of 方法将其转化为一个 Stream。

  1. @Test
  2. public void test1(){
  3. List<String> stringList = new ArrayList<>();
  4. stringList.add("ddd2");
  5. stringList.add("aaa2");
  6. stringList.add("bbb1");
  7. stringList.add("aaa1");
  8. stringList.add("bbb3");
  9. stringList.add("ccc");
  10. stringList.add("bbb2");
  11. stringList.add("ddd1");
  12. Stream<String> stream = stringList.stream();
  13. //Stream<String> stringStream = stringList.parallelStream();
  14. }

我们可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。

Filter(过滤)

过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作。(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

  1. stringList
  2. .stream()
  3. .filter(s->s.startsWith("a"))
  4. .forEach(System.out::println);

forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。

Sorted(排序)

排序是一个中间操作,返回的是排序好的 Stream 。如果我们不指定一个自定义的 Comparator 则会使用默认排序。

  1. stringList
  2. .stream()
  3. .sorted((o1,o2)->Integer.compare(o1.length(),o2.length()))
  4. .forEach(System.out::println);

需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的。

Map(映射)

中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。

map返回的 Stream 类型是根据我们 map 传递进去的函数的返回值决定的。

  1. stringList
  2. .stream()
  3. .map(String::toUpperCase)
  4. .sorted((o1,o2)->Integer.compare(o1.length(),o2.length()))
  5. .forEach(System.out::println);

Match(匹配)

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最终操作 ,并返回一个 boolean 类型的值。

  1. boolean anyStartsWithA =
  2. stringList
  3. .stream()
  4. .anyMatch((s) -> s.startsWith("a"));
  5. System.out.println(anyStartsWithA); // true
  6. boolean allStartsWithA =
  7. stringList
  8. .stream()
  9. .allMatch((s) -> s.startsWith("a"));
  10. System.out.println(allStartsWithA); // false
  11. boolean noneStartsWithZ =
  12. stringList
  13. .stream()
  14. .noneMatch((s) -> s.startsWith("z"));
  15. System.out.println(noneStartsWithZ); // true

Count(计数)

计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long

  1. long count = stringList
  2. .stream()
  3. .map(String::toUpperCase)
  4. .sorted((o1, o2) -> Integer.compare(o1.length(), o2.length()))
  5. .count();
  6. System.out.println(count);

Parallel Stream(并行流)

Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。

下面使用串行流和并行流为一个大容器进行排序,比较两者性能。

串行排序

  1. @Test
  2. public void test1(){
  3. int max = 1000000;
  4. List<String> list = new ArrayList<>(max);
  5. for (int i = 0; i < max; i++) {
  6. UUID uuid = UUID.randomUUID();
  7. list.add(uuid.toString());
  8. }
  9. long startTime = System.nanoTime();
  10. long count = list.stream().sorted().count();
  11. System.out.println(count);
  12. long endTime = System.nanoTime();
  13. long millis = TimeUnit.NANOSECONDS.toMillis(endTime-startTime);
  14. System.out.println(millis);
  15. //1000000
  16. //877
  17. }

并行排序

  1. @Test
  2. public void test2(){
  3. int max = 1000000;
  4. List<String> list = new ArrayList<>(max);
  5. for (int i = 0; i < max; i++) {
  6. UUID uuid = UUID.randomUUID();
  7. list.add(uuid.toString());
  8. }
  9. long startTime = System.nanoTime();
  10. long count = list.parallelStream().sorted().count();
  11. System.out.println(count);
  12. long endTime = System.nanoTime();
  13. long millis = TimeUnit.NANOSECONDS.toMillis(endTime-startTime);
  14. System.out.println(millis);
  15. }
  16. //1000000
  17. //512

可以明显看出在大数据量的情况下并行排序比串行来的快。但是小数据量的话却是串行排序比较快,原因是并行需要涉及到上下文切换。

Collector 和 Collectors

Collector 是专门用来作为 Stream 的 collect 方法的参数的。而 Collectors 是作为生产具体 Collector 的工具类。

  • toList():将流构造成 list

    1. List<String> collect = list.stream().collect(Collectors.toList());
  • toSet():将流构造成set

    1. Set<String> set = list.stream().collect(Collectors.toSet());
    2. Set<String> treeSet = list.stream().collect(Collectors.toCollection(TreeSet::new));
  • joining():拼接流中所有字符串

    1. String collect = list.stream().collect(Collectors.joining());
    2. String collect = list.stream().collect(Collectors.joining(";"));
  • toMap():将流转成 map

    1. Map<String, String> collect = list
    2. .stream()
    3. .collect(Collectors
    4. .toMap(e -> "key" + e, e -> "v" + e,(a,b)->b,HashMap::new));

    上面的 e -> "key" + e 定义了 map 的 key 的生成规则,e -> "v" + e 定义了 map 的 value 的生成规则,(a,b)->b 表示冲突的解决方案,如果键 a 和 键 b 冲突了则该键键值取 b 的,HashMap::new 定义了生成的 map 为 hashmap。

Map 新方法

Map 虽然不支持 Stream 但是我们可以通过 map.keySet().stream(),map.values().stream()map.entrySet().stream() 来通过过去键、值的集合再转换成流进行处理。

Java 8 中 map 新方法:

    1. putIfAbsent(key, value)//有则不加,无则加
    1. map.forEach((key, value) -> System.out.println(value));//循环打印
    1. map.computeIfPresent(3, (num, val) -> val + num);//当key 存在则执行后面方法
    1. map.computeIfAbsent(23, num -> "val" + num);//当key 不存在时执行后面方法
    1. map.getOrDefault(42, 1);//有则获取,无则置 1
    1. map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
    2. //如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

新的日期与时间 API

  • LocalTime(本地时间)

    LocalTime 定义了一个没有时区信息的时间

    方法 描述
    now,of 这些静态方法可以根据当前时间或指定的年、月、日来创建一个 LocalTime 对象
    getHour,getMinute,getSecond,getNano 获得当前 LocalTime 的小时、分钟、秒钟及微妙值
    isBefore,isAfter 比较两个LocalTime
  • LocalDate(本地日期)

    LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。

    方法 描述
    now,of 这些静态方法可以根据当前时间或指定的年、月、日来创建一个LocalDate对象
    getDayOfMonth 获取月份天数(在 1~ 31 之间)
    getDayOfYear 获取年份天数(在1~366之间)
    getMonth,getMonthValue 获得月份,或者为一个 Month 枚举的值,或者是 1 ~ 12 之间的一个数字
    getYear 获取年份
    isBefore,isAfter 比较两个LocalDate

上面这些方法是比较常用的,其余的可以自行查阅。

参考资料

Java 新特性总结——简单实用的更多相关文章

  1. 主流的单元测试工具之-JAVA新特性-Annotation 写作者:组长 梁伟龙

    1:什么是Annotation?Annotation,即“@xxx”(如@Before,@After,@Test(timeout=xxx),@ignore),这个单词一般是翻译成元数据,是JAVA的一 ...

  2. 主流的单元测试工具之-JAVA新特性-Annotation

    1:什么是Annotation?Annotation,即“@xxx”(如@Before,@After,@Test(timeout=xxx),@ignore),这个单词一般是翻译成元数据,是JAVA的一 ...

  3. Hi java新特性

    java新特性 1995.5.23 java语言 1996 jdk1.0 250个类在API 主要用在桌面型应用程序1997 jdk1.1 500 图形用户界面编程1998 jdk1.2 2300 J ...

  4. java新特性stream

    java新特性stream,也称为流式编程. 在学习stream之前先了解一下java内置的四大函数 第一种函数式函数,后面是lambda表达式写法 /*Function<String,Inte ...

  5. Java 新特性(5) - Java EE 5 新特性

    Java EE 5 由 Java Community Process 通过 Java Specification Request 244 发布,这个 “总纲” JSR 指出了详细描述 Java EE ...

  6. Java 新特性(3) - JDK7 新特性

    http://www.ibm.com/developerworks/cn/java/j-lo-jdk7-1/ JSR292:支持动态类型语言(InvokeDynamic) 近 年来越来越多的基于 JV ...

  7. Java 新特性(2) - JDK6 新特性

    http://freesea.iteye.com/blog/160133 JDK6的新特性之一_Desktop类和SystemTray类 JDK6的新特性之二_使用JAXB2来实现对象与XML之间的映 ...

  8. Java 新特性(7) - Java EE 7 新特性

    http://www.ibm.com/developerworks/cn/java/j-lo-javaee7/ 新特性主要集中在: 1. 提高开发人员的生产力 2. 加强对 HTML5 动态可伸缩应用 ...

  9. 【Java新特性】Lambda表达式典型案例,你想要的的都在这儿了!!

    写在前面 不得不说,有些小伙伴的学习热情真高,学完了Lambda表达式的语法,想来几个典型案例再强化下.于是问冰河能否给几个Lambda表达式的典型使用示例.于是乎,便有了这篇文章. 案例一 需求 调 ...

随机推荐

  1. 图像滤镜艺术---Swirl滤镜

    原文:图像滤镜艺术---Swirl滤镜 Swirl Filter Swirl 滤镜是实现图像围绕中心点(cenX,cenY)扭曲旋转的效果,效果图如下: 原图 效果图 代码如下:         // ...

  2. Android零基础入门第56节:翻转视图ViewFlipper打造引导页和轮播图

    原文:Android零基础入门第56节:翻转视图ViewFlipper打造引导页和轮播图 前面两期学习了 ViewAnimator及其子类ViewSwitcher的使用,以及ViewSwitcher的 ...

  3. asp.net 调用带证书的webservice解决办法

    最近在朋友弄一个调整省政府政务工作流的程序.. 需要把当前的信息推送到政务网上,采用的是带证书的https webservice.. 下面说一下实现过程 第一步,引用webservice地址,删除we ...

  4. MySQL数据库MHA+keepalive实现

    MHA(Master High Availability)目前在MySQL高可用方面是一个相对成熟的解决方案,它由日本DeNA公司youshimaton(现就职于Facebook公司)开发,是一套优秀 ...

  5. Android零基础入门第85节:Fragment使用起来非常简单

    Fragment创建完成后并不能单独使用,还需要将Fragment加载到Activity中,在Activity中添加Fragment的方式有两种:静态加载和动态加载,接下来分别进行学习. 一.静态加载 ...

  6. 简析TCP的三次握手与四次分手(TCP协议头部的格式,数据从应用层发下来,会在每一层都会加上头部信息,进行封装,然后再发送到数据接收端)good

    2014-10-30 分类:理论基础 / 网络开发 阅读(4127) 评论(29)  TCP是什么? 具体的关于TCP是什么,我不打算详细的说了:当你看到这篇文章时,我想你也知道TCP的概念了,想要更 ...

  7. Android应用开机自启动问题

    本文主要介绍Android应用如何实现开机自启动.自启动失败的原因以及通过ADB命令模拟发送BOOT_COMPLETED开机广播. 1.Android应用如何实现开机自启动 (1) 实现一个广播类,接 ...

  8. Windows 7 频繁提示:计算机的内存不足

    最近由于同时打开的程序比较多,Windows 7 频繁提示:计算机的内存不足,如下图: 问题原因: 经过一番尝试,得出一个大概的结论:当虚拟内存空间的大小小于物理内存空间的大小时,一旦程序开的太多,物 ...

  9. java设计模式-单例(singleton)

    单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一个类只有一个对象实例 如何保证对象唯一性呢? 思想: ...

  10. 如何有效预防XSS?这几招管用!!!

    原文链接 预防XSS,这几招管用 最近重温了一下「黑客帝国」系列电影,一攻一防实属精彩,生活中我们可能很少有机会触及那么深入的网络安全问题,但工作中请别忽略你身边的精彩 大家应该都听过 XSS (Cr ...