java8新特性

目录

Interface improvements

为什么default方法不能覆盖equals,hashCode和toString?

Functional interfaces

Lambdas

Capturing versus non-capturing lambdas

What lambdas don't do

Why abstract classes can't be instantiated using a lambda

java.util.function

java.util.stream

Collections API新增功能

并发API新增功能

IO/NIO API 新增功能

Reflection and annotation 的变化

Other


Interface improvements

接口现在可以定义静态方法。例如,在java.util.Comparator中添加了一个naturalOrder方法:

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

Java库中的一个常见场景是,对于某些接口Foo,会有一个伴随工具程序类Foos,其中包含用于生成或使用Foo实例的静态方法。现在静态方法可以存在于接口上,在许多情况下,Foos工具程序类可以消失(或者使其成为包私有),而Foos的公共方法则出现在接口上。 

此外,更重要的是,接口现在可以定义default 方法。例如,在java.lang.Iterable中添加了一个forEach方法:

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

 在过去,Java库基本上不可能向接口添加方法。向接口添加方法意味着破坏实现接口的所有现有代码。现在,只要可以提供方法的合理默认实现,库维护者就可以向这些接口添加方法。

为什么default方法不能覆盖equals,hashCode和toString?

接口无法为Object类的任何方法提供默认实现。特别是,这意味着无法在接口中为equals,hashCode或toString提供默认实现。这看起来很奇怪,因为有些接口实际上在文档中定义了它们的等于行为。 List接口就是一个例子。那么,为什么不允许这个呢?Brian Goetz在Project Lambda邮件列表的长篇回复中给出了四个理由。我只会在这里描述一个,因为那个足以说服我:

在调用默认方法时,更难以推断。现在很简单:如果一个类实现了一个方法,那么它总是胜过默认实现。由于接口的所有实例都是对象,因此接口的所有实例都已具有equals / hashCode / toString的非默认实现。因此,接口上的这些默认版本总是无用的,也可能无法编译。

Functional interfaces

Java 8中引入的核心概念是“功能接口(functional interface)”。如果接口只定义了一个抽象方法,那么接口就是一个功能接口。例如,java.lang.Runnable是一个功能接口,因为它只定义了一个抽象方法:

public abstract void run();

请注意,“abstract”修饰符是隐含的,因为该方法缺少方法体。没有必要像这个代码那样指定“abstract”修饰符,以便作为功能接口。

默认方法(Default method)不是抽象的,因此功能接口可以定义任意数量的默认方法(Default methods)。

引入了一个新的注释@FunctionalInterface。它可以放在一个接口上,以声明它是一个功能接口的意图。如果它不是一个功能接口,它会导致接口拒绝编译。这有点像@Override;它声明了意图并且不允许您错误地使用它。

Lambdas

功能接口的一个非常有价值的特性是它们可以使用lambdas进行实例化。以下是lambda的几个例子:

左侧是指定类型的逗号分隔输入列表,右侧是一个带有返回值的块:

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

以逗号分隔的输入列表,左侧是推断类型,右侧是返回值:

(x, y) -> x + y

左侧是带有推断类型的单个参数,右侧是返回值:

x -> x * x

左侧没有输入(官方名称:“汉堡箭头”),右侧返回值:

() -> x

左侧为推断类型的单个参数,右侧为无返回(无效返回)的块:

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

静态方法引用:

String::valueOf

非静态方法引用:

Object::toString

捕获方法引用:

x::toString

构造函数引用:

ArrayList::new

您可以将方法引用表单视为其他lambda表单的简写:

Method referenceEquivalent lambda expression
String::valueOfx -> String.valueOf(x)
Object::toStringx -> x.toString()
x::toString() -> x.toString()
ArrayList::new() -> new ArrayList<>()

当然,Java中的方法可能会overloaded。类可以有多个具有相同名称但参数不同的方法。它的构造函数也是如此。 ArrayList :: new可以引用它的三个构造函数中的任何一个,取决于它所使用的功能接口。

当lambda的“shapes”匹配时,lambda就与给定的功能接口兼容。“shapes”指的是输入,输出和声明的已检查异常的类型

给出一些具体,有效的例子:

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只能抛出已检查的异常。

Capturing versus non-capturing lambdas

如果Lambdas访问在lambda体外定义的非静态变量或对象,则称其为“捕获”。例如,这个lambda捕获变量x:

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

为了使这个lambda声明有效,它捕获的变量必须是“effectively final”。因此,它们必须用final修饰符标记,或者在分配后不得修改它们。

提到对性能的影响,非捕获lambda通常比捕获lambda更高效。虽然这没有在任何规范中定义(据我所知),并且你不应该依赖它来获得程序的正确性,但是非捕获lambda只需要评估一次,从那时起,它将返回一个相同的实例。捕获lambdas需要在每次遇到时进行评估,并且它的实现方式与实例化匿名类的新实例非常相似。

What lambdas don't do

Non-final variable capture.如果为变量分配了新值,则不能在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
});

Exception transparency.如果可能从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并将IOException作为RuntimeException隐藏。我在代码中尝试了这一点,发现它太令人困惑,不值得。

Control flow (break, early return) .在上面的forEach示例中,通过放置“return”语句在 lambda中,可以实现传统的continue ,但是,没有办法打破循环或从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撰写的解释:response to "Checked exceptions within Block<T>

Why abstract classes can't be instantiated using a lambda

抽象类即使只声明一个抽象方法,也无法使用lambda进行实例化。使用一个抽象方法的两个类的示例是来自Guava库的Ordering和CacheLoader。能够使用像这样的lambdas声明它们的实例不是很好吗?

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

最常见的反对意见是它会增加阅读lambda的难度。以这种方式实例化抽象类可能导致隐藏代码的执行:在抽象类的构造函数中。另一个原因是它抛出了lambdas的可能优化。将来,lambda可能不会被评估为对象实例。让用户使用lambdas声明抽象类会阻止像这样的优化。此外,还有一个简单的解决方法。实际上,Guava的两个示例类已经证明了这种解决方法。添加工厂方法以从lambda转换为实例:

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

有关进一步阅读,请参阅Brian Goetz撰写的此解释:response to "Allow lambdas to implement abstract classes

java.util.function

正如前面使用Comparator和Runnable所演示的那样,JDK中定义的接口恰好是功能接口,它们与lambdas兼容。对于您自己的代码或第三方库中定义的任何功能接口也是如此。

但是某些形式的功能接口广泛,通用,以前在JDK中不存在。大量的这些接口已添加到新的java.util.function包中。以下是一些:

  • Function<T, R> - take a T as input, return an R as ouput
  • Predicate<T> - take a T as input, return a boolean as output
  • Consumer<T> - take a T as input, perform some action and don't return anything
  • Supplier<T> - with nothing as input, return a T
  • BinaryOperator<T> - take two T's as input, return one T as output, useful for "reduce" operations

大多数这些的原始类型的指定也存在。它们以int,long和double形式提供。例如:IntConsumer - 将int作为输入,执行一些操作而不返回任何内容。

这些功能接口的存在是出于性能原因,以避免在输入或输出是基元时装箱和拆箱。

java.util.stream

新的java.util.stream包提供了工具“以支持值流上的功能样式的操作(to support functional-style operations on streams of values)”(引用javadoc)。获取流的最常见方式可能来自集合:

Stream<T> stream = collection.stream();

流就像迭代器。值(The values)“流过”(类似于水流),然后它们就消失了。流只能遍历一次,然后用完。流也可能是无限的。

流可以是sequential 的或parallel的。它们从一开始,可以使用stream.sequential()或stream.parallel()切换到另一个。sequential stream的动作在一个线程上以串行方式发生。parallel stream的动作在多个线程上发生。那么,你如何处理流?以下是javadocs包中给出的示例:

int sumOfWeights = blocks.stream().filter(b -> b.getColor() == RED)
                                  .mapToInt(b -> b.getWeight())
                                  .sum();

注意:上面的代码使用了primitive stream,而sum()方法仅适用于primitive stream。

流提供了一个流畅的API,用于转换值并对结果执行某些操作。流操作是“intermediate”或“terminal”。

Intermediate - 中间操作使流保持打开并允许进一步操作。上例中的过滤器和映射方法是中间操作。这些方法的返回类型是Stream;它们返回当前流以允许链接更多操作。

Terminal - 终端操作必须是在流上调用的最终操作。一旦调用了终端操作,该流就“被消耗”并且不再可用。以上示例中的求和方法是终端操作。

通常,处理流将涉及以下步骤: 1.从某个来源获取流。 2.执行一个或多个中间操作。 3.执行一个终端操作。

您可能希望在一种方法中执行所有这些步骤。这样,您就知道了源和流的属性,并且可以确保它的使用正确。您可能不希望接受任意Stream <T>实例作为方法的输入,因为它们可能具有您无法处理的属性,例如parallel 或infinite。

要考虑的流操作还有一些更常见的属性:

Stateful -有状态操作会在流上强加一些新属性,例如元素的唯一性,或最大数量的元素,或确保元素以排序方式使用。这些通常比无状态的中间操作更昂贵。

Short-circuiting - 短路操作可能允许处理流以提前停止而不检查所有元素。在处理infinite streams时,这是一个特别理想的属性;如果在流上调用的操作都没有短路,则代码可能永远不会终止。

以下是每种Stream方法的简短描述。有关更详细的说明,请参阅javadocs。下面提供了每个重载形式的操作的链接。

Intermediate operations

  • filter 1 - Exclude all elements that don't match a Predicate.排除与Predicate不匹配的所有元素。
  • map 1 2 3 4 - Perform a one-to-one transformation of elements using a Function.使用Function执行元素的一对一转换。
  • flatMap 1 2 3 4 - Transform each element into zero or more elements by way of another Stream.通过另一个Stream将每个元素转换为零个或多个元素。
  • peek 1 - Perform some action on each element as it is encountered. Primarily useful for debugging.在遇到每个元素时对其执行一些操作。 主要用于调试。
  • distinct 1 - Exclude all duplicate elements according to their .equals behavior. This is a stateful operation.根据其.equals行为排除所有重复元素。 这是一项有状态的操作。
  • sorted 1 2 - Ensure that stream elements in subsequent operations are encountered according to the order imposed by a Comparator. This is a stateful operation.确保根据比较器强加的顺序遇到后续操作中的流元素。 这是一项有状态的操作。
  • limit 1 - Ensure that subsequent operations only see up to a maximum number of elements. This is a stateful, short-circuiting operation.确保后续操作仅查看最多元素数。 这是一种有状态的短路操作。
  • skip 1 - Ensure that subsequent operations do not see the first n elements. This is a stateful operation.确保后续操作看不到前n个元素。 这是一项有状态的操作。

Terminal operations:

  • forEach 1 - Perform some action for each element in the stream.对流中的每个元素执行一些操作。
  • toArray 1 2 - Dump the elements in the stream to an array.将流中的元素转储到数组中。
  • reduce 1 2 3 - Combine the stream elements into one using a BinaryOperator.使用BinaryOperator将流元素合并为一个。
  • collect 1 2 - Dump the elements in the stream into some container, such as a Collection or Map.将流中的元素转储到某个容器中,例如Collection或Map。
  • min 1 - Find the minimum element of the stream according to a Comparator.根据比较器查找流的最小元素。
  • max 1 - Find the maximum element of the stream according to a Comparator.根据比较器查找流的最大元素。
  • count 1 - Find the number of elements in the stream.查找流中的元素数量。
  • anyMatch 1 - Find out whether at least one of the elements in the stream matches a Predicate. This is a short-circuiting operation.找出流中至少有一个元素是否与Predicate匹配。 这是一种短路操作。
  • allMatch 1 - Find out whether every element in the stream matches a Predicate. This is a short-circuiting operation.找出流中的每个元素是否与谓词匹配。 这是一种短路操作。
  • noneMatch 1 - Find out whether zero elements in the stream match a Predicate. This is a short-circuiting operation.找出流中的零元素是否与谓词匹配。 这是一种短路操作。
  • findFirst 1 - Find the first element in the stream. This is a short-circuiting operation.找到流中的第一个元素。 这是一种短路操作。
  • findAny 1 - Find any element in the stream, which may be cheaper than findFirst for some streams. This is a short-circuiting operation.查找流中的任何元素,对于某些流可能比findFirst更便宜。 这是一种短路操作。

正如javadocs中所指出的,中间操作是懒惰的。只有终端操作才会开始处理流元素。此时,无论包含多少中间操作,元素都会被消耗(通常但不总是)单次传递。 (有状态操作,例如sorted()和distinct()可能需要对元素进行第二次传递。)Streams尽力做尽可能少的工作。存在微优化,例如当它可以确定元素已经按顺序时,删除sorted()操作。在包含limit(x)或substream(x,y)的操作中,流有时可以避免对它知道不需要确定结果的元素执行中间映射操作。

对于int,long和double,有一些原始专用版本的Stream:

可以使用primitive-specialized map和flatMap函数等在object stream和primitive stream之间来回转换。举几个人为的例子:

List<String> strings = Arrays.asList("a", "b", "c");
strings.stream()                    // Stream<String>
       .mapToInt(String::length)    // IntStream
       .longs()                     // LongStream
       .mapToDouble(x -> x / 10.0)  // DoubleStream
       .boxed()                     // Stream<Double>
       .mapToLong(x -> 1L)          // LongStream
       .mapToObj(x -> "")           // Stream<String>
       ...

collect引入了一个名为Collector的新接口。这个接口有点难以理解,但幸运的是有一个Collectors工具类用于生成各种有用的Collectors。例如:

List<String> strings = values.stream()
                             .filter(...)
                             .map(...)
                             .collect(Collectors.toList());

如果要将流元素放入Collection,Map或String中,则Collectors可能具有您所需的功能。

Collections API新增功能

所有核心接口都提供了这些的默认实现,并且在适用的情况下,将更高效或性能更好的重写实现添加到所有具体的实现类中。

略。

并发API新增功能

StampedLock是一种新的锁实现,在大多数情况下可能会取代ReentrantReadWriteLock。

ForkJoinPool.commonPool()是处理所有并行流操作的结构。当您需要ForkJoinPool / ExecutorService / Executor时,它是一种简单,好的方法。

等等。

IO/NIO API 新增功能

略。

Reflection and annotation 的变化

允许在更多地方添加注释。等。

Other

略。

原文出处:https://www.techempower.com/blog/2013/03/26/everything-about-java-8/

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值