zoukankan      html  css  js  c++  java
  • JDK1.8的新特性

    JAVA8新特性
    接口改善

    现在接口里已经完全可以定义静态方法了. 举一个比较普遍的例子就是在java类库中, 对于一些接口如Foo, 都会有一个有静态方法的工具类Foos 来生成或者配合Foo对象实例来使用. 既然静态方法可以存在于接口当中, 那么大多数情况下 Foos工具类完全可以使用接口中的公共方法来代理 (或者将Foos置成package-private).

    除此之外更重要的就是, Java 8中接口可以定义默认的方法了.举个例子,一个for-each循环的方法就可以加入到java.lang.Iterable中:

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

    在过去,java类库的接口中添加方法基本上是不可能的. 在接口中添加方法意味着破坏了实现了这个接口的代码. 但是现在, 只要能够提供一个正确明智的默认的方法的实现, java类库的维护者就可以在接口中添加方法.

    Java 8中, 大量的默认方法已经被添加到核心的JDK接口中

    为什么不能用默认方法来重载equals,hashCode和toString?

    接口不能提供对Object类的任何方法的默认实现。特别是,这意味着从接口里不能提供对equals,hashCode或toString的默认实现。

    这刚看起来挺奇怪的,但考虑到一些接口实际上是在文档里定义他们的equals行为的。List接口就是一个例子了。因此,为什么不允许这样呢?

    Brian Goetz在这个问题上的冗长的回复里给出了4个原因。我这里只说其中一个,因为那个已经足够说服我了:

    它会变得更困难来推导什么时候该调用默认的方法。现在它变得很简单了:如果一个类实现了一个方法,那总是优先于默认的实现的。一旦所有接口的实例都是Object的子类,所有接口实例都已经有对equals/hashCode/toString的非默认实现。因此,一个在接口上这些的默认版本都是没用的,它也不会被编译。

     

    函数式接口

    Java 8 引入的一个核心概念是函数式接口。如果一个接口定义个唯一一个抽象方法,那么这个接口就成为函数式接口。比如,java.lang.Runnable就是一个函数式接口,因为它只顶一个一个抽象方法:

    public abstract void run();
    留意到“abstract”修饰词在这里是隐含的,因为这个方法缺少方法体。为了表示一个函数式接口,并非想这段代码一样一定需要“abstract”关键字。

    默认方法不是abstract的,所以一个函数式接口里可以定义任意多的默认方法,这取决于你。

    同时,引入了一个新的Annotation:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。加上它的接口不会被编译,除非你设法把它变成一个函数式接口。它有点像@Override,都是声明了一种使用意图,避免你把它用错。

    Lambdas

    一个函数式接口非常有价值的属性就是他们能够用lambdas来实例化。这里有一些lambdas的例子:

    左边是指定类型的逗号分割的输入列表,右边是带有return的代码块:

    (int x, int y) -> { return x + y; }

    左边是推导类型的逗号分割的输入列表,右边是返回值:

    (x, y) -> x + y

    左边是推导类型的单一参数,右边是一个返回值:

    x -> x * x

    左边没有输入 (官方名称: "burger arrow"),在右边返回一个值:

    () -> x

    左边是推导类型的单一参数,右边是没返回值的代码块(返回void):

    x -> { System.out.println(x); }

    静态方法引用:

    String::valueOf

    非静态方法引用:

    Object::toString

    继承的函数引用:

    x::toString

    构造函数引用:

    ArrayList::new 

    你可以想出一些函数引用格式作为其他lambda格式的简写。

     
    方法引用 等价的lambda表达式 
    String::valueOf   x -> String.valueOf(x)
    Object::toString   x -> x.toString()
    x::toString   () -> x.toString()
    ArrayList::new   () -> new ArrayList<>()

    当然,在Java里方法能被重载。类可以有多个同名但不同参数的方法。这同样对构造方法有效。ArrayList::new能够指向它的3个构造方法中任何一个。决定使用哪个方法是根据在使用的函数式接口。

    一个lambda和给定的函数式接口在“外型”匹配的时候兼容。通过“外型”,我指向输入、输出的类型和声明检查异常。

    给出两个具体有效的例子:

    Comparator<String> c = (a, b) -> Integer.compare(a.length(),
                                                     b.length());

    一个Comparator<String>的compare方法需要输入两个阐述,然后返回一个int。这和lambda右侧的一致,因此这个任务是有效的。

    Runnable r = () -> { System.out.println("Running!"); }

    一个Runnable的run方法不需要参数也不会返回值。这和lambda右侧一致,所以任务有效。

    在抽象方法的签名里的受检查异常(如果存在)也很重要。如果函数式接口在它的签名里声明了异常,lambda只能抛出受检查异常。

     

    捕获和非捕获的Lambda表达式

    当Lambda表达式访问一个定义在Lambda表达式体外的非静态变量或者对象时,这个Lambda表达式称为“捕获的”。比如,下面这个lambda表达式捕捉了变量x:

    int x = 5; return y -> x + y;
    为了保证这个lambda表达式声明是正确的,被它捕获的变量必须是“有效final”的。所以要么它们需要用final修饰符号标记,要么保证它们在赋值后不能被改变。

    Lambda表达式是否是捕获的和性能悄然相关。一个非不捕获的lambda通常比捕获的更高效,虽然这一点没有书面的规范说明(据我所知),而且也不能为了程序的正确性指望它做什么,非捕获的lambda只需要计算一次. 然后每次使用到它都会返回一个唯一的实例。而捕获的lambda表达式每次使用时都需要重新计算一次,而且从目前实现来看,它很像实例化一个匿名内部类的实例。

     

    lambdas不做的事

    你应该记住,有一些lambdas不提供的特性。为了Java 8它们被考虑到了,但是没有被包括进去,由于简化以及时间限制的原因。

    Non-final* 变量捕获 - 如果一个变量被赋予新的数值,它将不能被用于lambda之中。"final"关键字不是必需的,但变量必须是“有效final”的(前面讨论过)。这个代码不会被编译:

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

    有绕过这个的办法,你能定义自己的功能性接口,扩展Consumer的同时通过像RuntimeException之类抛出 IOException。我试图用代码写出来,但发现它令人困惑是否值得。

    控制流程 (break, early return) -在上面的 forEach例子中,传统的继续方式有可能通过在lambda之内放置 "return;"来实现。但是,没有办法中断循环或者从lambda中通过包含方法的结果返回一个数值。例如:

    final String secret = "foo"; boolean containsSecret(Iterable<String> values) {
        values.forEach(s -> { if (secret.equals(s)) {
                ??? // want to end the loop and return true, but can't }
        });
    }

    进一步阅读关于这些问题的资料,看看这篇Brian Goetz写的说明:在 Block<T>中响应“已验证例外”

     

    为什么抽象类不能通过利用lambda实例化

    抽象类,哪怕只声明了一个抽象方法,也不能使用lambda来实例化。

    下面有两个类 Ordering 和 CacheLoader的例子,都带有一个抽象方法,摘自于Guava 库。那岂不是很高兴能够声明它们的实例,像这样使用lambda表达式?

    Ordering<String> order = (a, b) -> ...;

    CacheLoader<String, String> loader = (key) -> ...;

    这样做引发的最常见的争论就是会增加阅读lambda的难度。以这种方式实例化一段抽象类将导致隐藏代码的执行:抽象类的构造方法。

    另一个原因是,它抛出了lambda表达式可能的优化。在未来,它可能是这种情况,lambda表达式都不会计算到对象实例。放任用户用lambda来声明抽象类将妨碍像这样的优化。

    此外,有一个简单地解决方法。事实上,上述两个摘自Guava 库的实例类已经证明了这种方法。增加工厂方法将lambda转换成实例。

    Ordering<String> order = Ordering.from((a, b) -> ...);
    CacheLoader<String, String> loader = CacheLoader.from((key) -> ...);

    java.util.function

    包概要:java.util.function

    作为Comparator 和Runnable早期的证明,在JDK中已经定义的接口恰巧作为函数接口而与lambdas表达式兼容。同样方式可以在你自己的代码中定义任何函数接口或第三方库。

    但有特定形式的函数接口,且广泛的,通用的,在之前的JD卡中并不存在。大量的接口被添加到新的java.util.function 包中。下面是其中的一些:

    • Function<T, R> -T作为输入,返回的R作为输出
    • Predicate<T> -T作为输入,返回的boolean值作为输出
    • Consumer<T> - T作为输入,执行某种动作但没有返回值
    • Supplier<T> - 没有任何输入,返回T
    • BinaryOperator<T> -两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用

    这些最原始的特征同样存在。他们以int,long和double的方式提供。例如:

    • IntConsumer -以int作为输入,执行某种动作,没有返回值

    这里存在性能上的一些原因,主要释在输入或输出的时候避免装箱和拆箱操作。

  • 相关阅读:
    怎么对Navicat for Oracle 调试
    老版本的java代码与新代码如何找出差异
    Oracle 外部表是做什么用的
    如何在Navicat 中编辑和记录
    如何使用文件对比工具文件夹比较会话菜单
    哪些工具可以用来进行Bug管理
    5类开发者须知的工具
    怎么找出代码之间的差异
    Beyond Compare不仅可以修改网页代码
    文件对比工具有哪些用途
  • 原文地址:https://www.cnblogs.com/Fairy-02-11/p/8194773.html
Copyright © 2011-2022 走看看