目录
为什么default方法不能覆盖equals,hashCode和toString?
Capturing versus non-capturing lambdas
Why abstract classes can't be instantiated using a lambda
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 reference | Equivalent lambda expression |
---|---|
String::valueOf | x -> String.valueOf(x) |
Object::toString | x -> 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/