• Java8新特性你知道哪些?


    一、lambda 表达式介绍

    lambda 表达式是 Java 8 的一个新特性,可以取代大部分的匿名内部类,简化了匿名委托的使用,让你让代码更加简洁,优雅。

    比较官方的定义是这样的:

    lambda 表达式是一个可传递的代码块(或者匿名函数),可以在以后执行一次或多次。

    这个匿名函数没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。 lambda 表达式也可称为 闭包 。

    在 Java 中传递一个代码段并不容易,你不能直接传递代码段。Java 是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。接下来就看看 Java 是怎么来处理代码块的。

    二、lambda 表达式的语法

    ava 中有一个 Comparator 接口用来排序。这是 Java 8 以前的代码形式:

    1. class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">public class LengthComparator implements Comparator<String> {
    2. @Override
    3. public int compare(String a, String b) {
    4. return a.length() - b.length();
    5. }
    6. }
    7. String[] strArr = new String[]{"abcde", "qwer"};
    8. Arrays.sort(strArr, new LengthComparator());
  • 我们需要定义一个实现了 Comparator 接口的类,并实现里面的 compare() 方法,然后把这个类当做参数传给 sort 方法。

    而我们使用 lambda 表达式就可以这样来写:

    <pre class="prettyprint hljs livescript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">Arrays.sort(strArr, (String a, String b) -> a.length() - b.length());pre>
    

    其中的 (String a, String b) -> a.length() - b.length() 就是一个 lambda 表达式。

    lambda 表达式就是一个代码块,以及必须传入代码的变量规范

    lambda 表达式的一些例子:

    1. class="prettyprint hljs rust" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">// 1\. 不需要参数,返回值为 5 
    2. () -> 5
    3. // 2\. 接收一个参数(数字类型),返回其2倍的值
    4. x -> 2 * x
    5. // 3\. 接受2个参数(数字),并返回他们的差值
    6. (x, y) -> x – y
    7. // 4\. 接收2个int型整数,返回他们的和
    8. (int x, int y) -> x + y
    9. // 5\. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
    10. (String s) -> System.out.print(s)

再看一个例子加深理解:

  1. class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">// 用匿名内部类的方式来创建线程 
  2. new Thread(new Runnable() {
  3. @Override
  4. public void run() {
  5. System.out.println("hello world");
  6. }
  7. });
  8. // 使用Lambda来创建线程
  9. new Thread(() -> System.out.println("hello world"));

注意:

如果一个 lambda 表达式只在某些分支返回一个值,而另外一些分支不返回值,这是不合法的。

例如,(int x) -> { if (x>= 0) return 1; } 就不合法

三、函数式接口

Java 中有很多封装代码块的接口,比如上面的 Comparator 或 ActionListener ,lambda 表达式与这些接口是兼容的。

但并不是所有的接口都可以使用 lambda 表达式来实现。 lambda 规定接口中只能有一个需要被实现的方法(只包含一个抽象方法),不是规定接口中只能有一个方法。 这种接口就称为 函数式接口 。

Java 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

上面的 Comparator 和 ActionListener ,包括 Runnable 就是只有一个需要被实现的方法的接口。即 函数式接口 。

  1. class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@FunctionalInterface
  2. public interface Runnable {
  3. /**
  4. * When an object implementing interface Runnable is used...
  5. */
  6. public abstract void run();
  7. }

我们来观察下 Runnable 接口,接口上面有一个注解 @FunctionalInterface 。

通过观察 @FunctionalInterface 这个注解的源码,可以知道这个注解有以下特点:

  1. 该注解只能标记在 有且仅有一个抽象方法 的接口上。

  2. JDK8 接口中的静态方法和默认方法,都不算是抽象方法。

  3. 接口默认继承 java.lang.Object,所以如果接口显示声明覆盖了 Object 中方法,那么也不算抽象方法。

  4. 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

我们再来看一下 Comparator 接口的源码:

  1. class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">@FunctionalInterface
  2. public interface Comparator {
  3. int compare(T o1, T o2);
  4. boolean equals(Object obj);
  5. default Comparator reversed() {
  6. return Collections.reverseOrder(this);
  7. }
  8. default Comparator thenComparing(Comparatorsuper T> other) {
  9. Objects.requireNonNull(other);
  10. return (Comparator & Serializable) (c1, c2) -> {
  11. int res = compare(c1, c2);
  12. return (res != 0) ? res : other.compare(c1, c2);
  13. };
  14. }
  15. public static Comparator nullsFirst(Comparatorsuper T> comparator) {
  16. return new Comparators.NullComparator<>(true, comparator);
  17. }
  18. }

这里只贴出来了部分代码,可以看到排除掉接口的中的静态方法、默认方法和覆盖的 Object 中的方法之后,就剩下一个抽象方法 int compare(T o1, T o2); 符合 lambda 函数式接口的规范。

JDK 中提供一些其他的函数接口如下:

四、方法引用

Java awt 包中有一个 Timer 类,作用是经过一段时间就执行一次。 用 lambda 表达式来处理:

class="prettyprint hljs vbscript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">Timer timer = new Timer(1000, event -> System.out.println("this time is " + new Date()));

这里面的 lambda 表达式可以这样表示:

<pre class="hljs vbscript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">Timer timer = new Timer(1000, System.out::println);pre>

表达式 System.out::println 就是一个 方法引用(method reference) ,它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。

方法引用需要用 :: 运算符分隔方法名与对象或类名。主要有3种情况:

  1. <pre class="prettyprint hljs less" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">1. object::instanceMethod
  2. 2. Class::instanceMethod
  3. 3. Class::staticMethod
  4. pre>

注意:

只有当 lambda 表达式的体只调用一个方法而不做其他操作时,才能把 lambda 表达式重写为 方法引用

五、构造器引用

构造器引用与方法引用很类似,只不过方法名 new。例如,Person::new 是 Person 构造器的一个引用。

假如有一个字符串列表。可以把它转换为一个 Person 对象数组,为此要在各个字符串上调用构造器:

  1. <pre class="prettyprint hljs xml" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">ArrayList<String> names = ... ;
  2. Stream<Persion> stream = names.stream().map(Person::new);
  3. List<Person> people = stream.collect(Collectors.toList());pre>

六、变量作用域

看下面这个例子:

  1. class="prettyprint hljs cs" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto;">public static void repeatMessage(String text, int delay){
  2. ActionListener listener = event ->
  3. {
  4. System.out.printLn(text);
  5. };
  6. new Timer(delay, listener).start();
  7. }
  8. // 调用
  9. repeatMessage("Hello", 1000);

可以看到, lambda 表达式可以捕获外围作用域中变量的值。在 Java 中,要确保所捕获的值是明确定义的,这里有一个重要的限制。在 lambda 表达式中, 只能引用值不会改变的变量 。这是为了保证并发执行过程的安全。

lambda 表达式中捕获的变量必须实际上是 事实最终变量 。就是这个变量初始化之后就不会再为它赋新值。

 

  • 相关阅读:
    OpenStack常用命令
    回归分析处理
    MTK Logo 逆向解析汇总
    ldap服务安装,客户端安装,ldap用户登录验证测试
    2022年11月21日13:32:00——T5——JS对象与Date日期函数
    2300: 输出点的坐标
    Oracle 存储过程数据插入临时表慢以及SQL语句查询慢
    利用XXXXXXXXMIND管理。
    python执行cmd命令——控制电脑连接wifi——程序打包
    资深程序员-5年大厂经验代码究竟写的如何-H5-WEBgl-typeScript的笔记代码之一
  • 原文地址:https://blog.csdn.net/Java_ttcd/article/details/126135168