`
wen866595
  • 浏览: 268967 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java 8 新特性之 接口改进和Lambdas

    博客分类:
  • java
阅读更多

 

本文首先发表在 码蜂笔记:http://coderbee.net/index.php/java/20130914/467 

 

测试环境

$ java -version
java version "1.8.0-ea"
Java(TM) SE Runtime Environment (build 1.8.0-ea-b106)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b48, mixed mode)

IntelliJ IDEA 12.1.4

 

接口改进

以前Java的接口里只能声明方法和定义常量,现在可以在接口里定义静态方法和默认方法。

 

定义静态方法

定义静态(static)方法带来的好处就是可以减少创建工具类的需求了。比如 java.util.Collection 接口定义了一个集合,对于此接口实例操作的很多通用方法都是通过工具类 java.util.Collections 来提供的,现在可以在接口里定义静态方法,我们就可以把工具类里的静态方法直接移到接口类里,不需要独立创建工具类了。

 

下面是Java8里添加到接口 java.util.Comparator 的一个静态方法:

public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
    return (Comparator<T>)   Comparators.NaturalOrderComparator.INSTANCE;
}

定义默认方法

另一个改进是,可以在不破坏现有接口实现的前提下添加默认(default)方法。比如 java.lang.Iterable 接口现在拥有一个默认的 forEach 方法:

public default void forEach(Consumer<? super T> action) {
     Objects.requireNonNull(action);
     for (T t : this) {
          action.accept(t);
     }
}

默认方法不能覆盖 equals, hashCode, toString 方法

一个接口不能提供Object类里的任何方法的默认实现。这意味着不能在接口里提供 equals, hashCode, toString 方法的默认实现。

 

关于这个,Brian Goetz 给出了四个理由,具体见: response to “Allow default methods to override Object’s methods”

 

这里列出一个具有足够说服力的理由:

It would become more difficult to reason about when a default method is invoked. Right now it’s simple: if a class implements a method, that always wins over a default implementation. Since all instances of interfaces are Objects, all instances of interfaces have non-default implementations of equals/hashCode/toString already. Therefore, a default version of these on an interface is always useless, and it may as well not compile.

大意是:由于所有接口的实例都是Objects,那么所有实例都已有 equals/hashCode/toString 这些方法的非默认实现。因此,接口上这些方法的默认版本是没有用的,可能不会被编译。

 

函数接口

函数接口是Java8引入的一个核心概念。如果一个接口只定义了一个抽象方法,它就是函数接口。例如java.lang.Runnable就是一个函数接口,因为它只定义了一个抽象方法: public void run()

默认方法不是抽象的,所以一个函数接口可以定义任意多的默认方法。

 

新引入的注解 @FunctionalInterface 用来表明接口准备成为函数接口,但不管有没有这个注解,编译器都认为只有一个抽象方法的接口是函数接口。

 

Lambdas

函数接口一个极具价值的属性是可以在lambdas里实例化。下面是lambdas的一些实例:

(param1 ParamType1,  param2 ParamTeyp2, ... )  ->  {  statements;  }          //  lambdas 完整形式

(int x, int y) -> { return x + y; }          //  完整输入参数类型,具有返回语句的语句块

(x, y) -> x + y       //  自动类型推导参数类型,语句块只有一条 return 语句时可以省略大括号和return关键字,

x  ->  x * x          //  只有一个输入参数,可以省略入参列表的小括号。

()  ->  x            //  如果没有入参,小括号不能省略。

x  ->  { System.out.println(x); }     //  没有返回语句,语句块的大括号不能省略。

 

方法引用

静态方法引用: String::valueOf

非静态方法引用: Object::toString

构造函数引用: ArrayList::new

捕获方法引用: x::toString

 

方法引用等价的lambda表达式:

String::valueOf          x -> String.valueOf(x)
Object::toString         x -> x.toString()
x::toString                 () -> x.toString()                    //  这个好像不行
ArrayList::new           () -> new ArrayList<>()

当然,在Java里可以重载方法。类可以有很多有同样名字但不同参数的方法,构造函数也一样。用哪个方法取决于它是用于哪个函数接口的。

 

一个lambda与一个函数接口有相同的“形状”时被认为是兼容的。这里的“形状”是指:输入输出的类型和声明的受检查异常。

 

举例

List<String> strList = new ArrayList<>();
strList.add("no");
strList.add("hello");
strList.add("world");

long count = strList
          .stream()
          .filter((String str) -> { return str != null; })     //  完整的lambda表达式
          .filter(str -> { return str.length() > 0; })     //  参数类型自动推导;只有一个输入参数,省略小括号
          .filter(str -> str.length() > 3)     //  语句块只是返回一个表达式的值,省略return语句直接返回表达式的值;省略语句块的花括号。
          .map(String::length)                 //  用方法代替lambda表达式
          .count();
System.out.println("\n\ncount:" + count);


Runnable r = () -> { System.out.println("Running!"); };
Comparator<String> c = (a, b) -> Integer.compare(a.length(), b.length());

 

捕获与非捕获lambdas

如果lambda表达式访问一个定义在lambda体之外的非静态变量或对象,则说Lambda表达式是“捕获的”。在下面的例子里,return返回的lambda捕获了变量 x

int x = 5;
return y -> x + y;

为了让这个lambda声明是合法的,它捕获的变量必须是“effectively final”。这样,它们必须标记为 final 或 赋值(被lambda捕获)之后就不能再修改。

 

一个lambda捕获与否对性能有影响。一个非捕获的lambda一般比一个捕获的更高效。虽然这没有在任何规范里定义,你不能依赖于程序员的正确性。一个非捕获的lambda只需要计算一次。然后,它将返回一个完全相同的实例。捕获的lambda在每次遇到时都需要进行计算,当前的动作与实例化匿名内部类非常类似。

 

举例

private static int count = 0;
public static void main(String[] args) {
//          basic ();
     testCapture();
}

public static void testCapture() {
     Runnable run = capture();
     count++;
     Runnable run2 = capture();
     new Thread(run).start();
     new Thread(run2).start();
}

public static Runnable capture() {
     int x = 5;
     Runnable r = () -> { System.out.println("x is:" + x); System.out.println("count is:" + count); };
     // 在这里给x赋值也会报错  Variable used in lambda expression should be effectively final
     return r;
}

执行后的输出是:

x is:5
count is:1
x is:5
count is:1

对于x的值都没有问题,但count的值就要注意了。进入testCapture()方法的时候,count的值是0,第一次捕获的时候也是0,然后进行加1,再次进行捕获,然后用两个线程计算捕获的值,得到的值是1和1,而不是0和1,因为,lambda捕获只是捕获了变量的引用,而不是变量的值。

 

lambdas 不能做的

有些约束是lambdas不提供的,为了简单和由于时间约束。

 

非final变量捕获

如果一个变量被赋予新值,它就不能用在lambda里。“final”关键字不是必须的,但变量必须是“effectively final”(如前所述)。下面的代码将报编译错误:

int count = 0;
List<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> {
    count++; // error: can't modify the value of count
});

注意,如果count是个非局部变量,它将是可以修改的。

 

异常透明

如果lambda代码体会抛出受检查的异常,函数接口必须声明它会抛出的受检查异常。异常不会传播到包含方法。下面的代码不能编译:

void appendAll(Iterable<String> values, Appendable out)
        throws IOException { // doesn't help with the error
    values.forEach(s -> {
        out.append(s); // error: can't throw IOException here
        // Consumer.accept(T) doesn't allow it
    });
}

简单说就是lambda的代码块不能抛出受检查异常。

 

流程控制(break,early return)

在上面的forEach例子里,传统的“continue”可以是通过lambda体内的“return;”语句来达到。然而,没有方法来中断loop循环或从lambda内返回一个结果到包含方法。举例:

import java.util.Arrays;

public class Lambdas2 {
     public static void main(String[] args) {
        Arrays.asList(args).forEach(s -> {
            if (s.length() > 3) {
                return;     //  这个return语句并不能终止loop循环。
            }
        });
     }
}

上面的代码生成的字节码是这样的:

$ javap -c Lambdas2.class
Compiled from "Lambdas2.java"
public class Lambdas2 {
  public Lambdas2();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: aload_0
       1: invokestatic  #2                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
       4: invokedynamic #3,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
       9: invokeinterface #4,  2            // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
      14: return
}

结合前面 java.lang.Iterable 接口的forEach方法实现可知,目前的lambda应该是编译成一个对象的,然后再对每个元素调用此对象的accept方法,也就是说lambda代码块被编译成Consumer的accept方法的代码,所以不能在lambda代码块里控制forEach的loop流程。

 

为什么抽象类不能用lambda实例化?

一个抽象类,即使它只有一个抽象方法,也不能用lambda实例化。

 

大多数反对这个的争论是这会增加阅读lambda的困难。以这种方式实例化一个抽象类会导致执行额外的隐藏代码:抽象类构造器里的代码。

 

另一个理由是这抛弃了优化lambda的可能。在将来,lambda可能将不再实例化为对象。允许用户以lambda的形式声明抽象类将阻止这类优化。

 

更多解释见:response to “Allow lambdas to implement abstract classes”

 

 

2
3
分享到:
评论

相关推荐

    黑马程序员_java8新特性详解笔记和源码

    Lambda表达式是Java 8中最显著的新特性之一,它提供了一种简洁、易读的方式来表示匿名函数。Lambda表达式可以被赋值给一个变量,作为参数传递,或者作为返回值。它们通常与函数式接口(只有一个抽象方法的接口)一起...

    Java8 新特性

    Java8是Java编程语言的一次重大更新,引入了许多新的特性和功能,极大地提升了开发效率和代码的可读性。以下是一些主要的Java8新特性详解: 1. **Lambda表达式**:Lambda表达式是Java8中最显著的新特性,它为Java...

    java8新特性代码

    总结起来,Java8的Lambda表达式、函数式接口、默认方法和静态方法这些新特性,极大地改进了Java的编程模型,让代码更加简洁、易读和高效。通过熟练掌握这些特性,开发者可以编写出更加优雅和强大的程序,同时也为...

    java8新特性-最新

    以上只是Java 8新特性的一部分,其他还包括日期时间API的改进、新的Optional类以防止空指针异常、类型接口的改进等。这些特性共同提升了Java的现代性和开发效率,使得Java 8成为Java开发者不可或缺的一部分。通过...

    java8新特性

    1. **Lambda表达式**:这是Java 8最显著的新特性之一,它为Java引入了函数式编程的概念。Lambda表达式允许我们将函数作为方法参数或者存储在变量中,使得代码更加简洁、易读。例如,可以使用`()-&gt;System.out.println...

    java8新特性包含 lambda 函数式接口,stream 流

    Java 8 是 Java 语言发展的一个重要里程碑,它引入了许多创新特性和改进,极大地提升了开发效率和代码质量。其中,lambda 函数式接口和 Stream API 是两大核心特性,它们为 Java 带来了函数式编程的元素,使得代码...

    Java 8新特性之Lambda与函数式编程.zip

    Java 8是Java语言发展的一个重要里程碑,引入了许多新的特性,其中最为显著的当属Lambda表达式和函数式编程的支持。这些新特性极大地提升了Java在处理并发和简化代码方面的效率,使得Java更加现代化,能够更好地适应...

    Java 8 新特性详细介绍Lambda表达式、Stream API、接口的默认方法

    1. **Lambda表达式**:Lambda表达式是Java 8的核心特性之一,它引入了函数式编程的概念。Lambda允许以更简洁的语法定义匿名函数,无需创建额外的类或方法。这种表达式使得代码更紧凑,减少了样板代码,尤其在处理...

    java_8的新特性和改进总览 word版实用

    ### Java 8的新特性和改进总览 #### 一、引言 随着技术的不断进步和发展,编程语言也在不断地更新迭代以适应新的需求和技术趋势。Java作为一种广泛使用的编程语言,其每一次版本更新都受到了开发者们的广泛关注。...

    尚硅谷Java8新特性下载

    Lambda表达式是Java 8中最重要的一项新特性。它允许开发者将行为作为方法参数传递,从而使得代码更加简洁、易于理解。Lambda表达式的引入也意味着Java开始支持函数式编程风格。例如,使用Lambda表达式可以将`...

    JAVA8新特性讲解

    Lambda表达式是Java 8中最显著的新特性,它允许我们将函数作为一个方法参数,或者以匿名函数的形式创建。Lambda表达式简洁且易于理解,使得处理集合和事件变得更加方便。例如,我们可以使用`Collections.sort(list,...

    《Java 8 in Action》是一本关于 Java 编程语言的书籍,重点介绍了 Java 8 中引入的新特性和改进

    该书由 Mario Fusco、Alan Mycroft 和 Raoul-Gabriel Urma 合著,旨在帮助读者深入了解 Java 8,并掌握其中的关键...其他新特性: 简要介绍 Java 8 中引入的其他新特性,如接口的默认方法、方法引用、Optional 类型等。

    Java 8 的新特性和改进总览

    总之,Java 8 的新特性,尤其是接口的增强、函数式接口和 Lambda 表达式,极大地提升了 Java 作为现代编程语言的竞争力,让开发者能够编写更简洁、更高效的代码。随着这些改进,Java 8 成为了 Java 开发者的首选版本...

    Java 8新特性终极指南

    Lambda表达式是Java 8最显著的特性之一,它允许以更简洁的方式处理函数式编程。Lambda表达式可以被理解为没有名字的方法,它可以作为参数传递,也可以存储在变量中。例如,下面的代码展示了如何使用Lambda来遍历...

    java8新特性导师讲授精简版

    在本精简版的Java 8新特性导师讲授中,我们将重点关注四大核心概念:Lambda表达式、Stream流、方法引用以及一些其他相关的重要更新。 1. Lambda表达式: Lambda表达式是Java 8中最引人注目的特性之一,它为函数式...

    Java8-JDK8-新特性学习

    8. **改进的枚举和泛型**:Java 8允许在枚举类中定义接口和实现方法,同时泛型现在支持类型推断,使得编写代码更加简洁。 9. **平行数组操作**:`Arrays.parallelSort()`提供了对数组的并行排序,利用多核处理器...

    java8 lambda demo

    Lambda表达式是Java 8的核心特性,它允许我们将匿名函数作为方法参数传递,或者在不需创建额外类的情况下实现接口。下面将详细介绍Lambda表达式以及相关的Java 8新特性。 一、Lambda表达式 Lambda表达式的基本语法...

Global site tag (gtag.js) - Google Analytics