文章《Java 8 Features – The ULTIMATE Guide》 翻译的第一部分。第二部分在这里(链接可能会更改)。 编者注:本文是在 Java 8 向公众开放时发表的,所有迹象都表明这确实是一个主要版本。在这里,我们提供了丰富的 Java Code Geeks 指南,例如《Playing with Java 8 – Lambdas and Concurrency》、《Java 8 Date and Time API Guide: LocalDateTime》和《Java 8 Era 中的 Abstract Class vs. Interface》。我们还链接到其他来源的 15 篇必读 Java 8 教程。当然,我们也会看到一些缺点,例如Java 8 的黑暗面。因此,是时候将 Java 8 的所有主要功能集中在一处,以方便您使用。享受!
一、简介
毫无疑问,Java 8 的发布是自 Java 5(很久以前发布,2004 年发布)以来最伟大的事件。它在语言、编译器、库、工具和 JVM(Java 虚拟机)方面为 Java 带来了许多新功能。在本教程中,我们将了解这些更改,并通过现实生活中的示例演示不同的用例。该指南由几个部分组成,每个部分都涉及平台的一个特定方面:- 语言
- 编译器
- 图书馆
- 工具
- 运行时环境(JVM)
2.Java 8的新特性
无论如何,Java 8 是一个主要版本。可以说,之所以花了这么长时间,是因为实现了每个 Java 开发人员都在寻找的功能。在本节中,我们将介绍其中的大部分内容。2.1. Lambda 和函数式接口
Lambda(也称为私有或匿名方法)是整个 Java 8 版本中最大且最受期待的语言变化。它们允许我们将功能指定为方法参数(通过在其周围声明一个函数),或将代码指定为数据:每个功能开发人员都熟悉的概念。编程_ JVM 平台上的许多语言(Groovy、Scala等)从第一天起就拥有 lambda,但 Java 开发人员别无选择,只能通过匿名类来表示 lambda。lambda 设计争论花费了公众大量的时间和精力。但最终找到了妥协方案,从而导致了新的简洁设计的出现。在最简单的形式中,lambda 可以表示为以逗号分隔的参数列表、–>符号和主体。例如:Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
请注意,参数e 的类型由编译器确定。此外,您可以通过将参数括在括号中来显式指定参数的类型。例如:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
如果 lambda 主体更复杂,可以将其括在大括号中,类似于 Java 中的常规函数定义。例如:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
System.out.print( e );
System.out.print( e );
} );
lambda 可以引用类成员和局部变量(无论final
字段是否被访问,隐式地使调用有效)。例如,这两个片段是等效的:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
和:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );
Lambda 可以返回一个值。返回类型将由编译器确定。return
如果 lambda 主体由一行组成,则不需要声明。下面的两个代码片段是等效的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
和:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
语言开发人员花了很长时间思考如何使现有函数对 lambda 友好。于是,函数式接口的概念就出现了。函数式接口是只有一种方法的接口。因此,它可以隐式转换为 lambda 表达式。java.lang.Runnable
以及java.util.concurrent.Callable
函数式接口的两个很好的例子。在实践中,函数式接口非常脆弱:如果有人在接口定义中添加甚至一种其他方法,它将不再具有函数式,并且编译过程将无法完成。为了避免这种脆弱性并将接口的意图显式定义为函数式,Java 8 中添加了一个特殊的注释@FunctionalInterface
(Java 库中的所有现有接口都收到了 @FunctionalInterface 注释)。让我们看一下函数式接口的简单定义:
@FunctionalInterface
public interface Functional {
void method();
}
有一点需要记住:默认方法和静态方法不违反函数式接口的原则,可以声明:
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
Lambda 是 Java 8 中最受欢迎的功能。它们有潜力吸引更多开发人员使用这个出色的平台,并为纯 Java 中的功能提供智能支持。更详细的信息请参考官方文档。
2.2. 默认接口和静态方法
Java 8 通过两个新概念扩展了接口的定义:默认方法和静态方法。默认方法使接口与特征有些相似,但用途略有不同。它们允许您向现有接口添加新方法,而不会破坏这些接口以前编写的版本的向后兼容性。默认方法和抽象方法之间的区别在于,抽象方法必须实现,而默认方法则不需要。相反,每个接口必须提供一个所谓的默认实现,并且所有后代都将默认接收它(如果需要,可以覆盖此默认实现)。让我们看下面的例子。private interface Defaulable {
// Интерфейсы теперь разрешают методы по умолчанию,
// клиент может реализовывать (переопределять)
// or не реализовывать его
default String notRequired() {
return "Default implementation";
}
}
private static class DefaultableImpl implements Defaulable {
}
private static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
该接口使用关键字作为方法定义的一部分来Defaulable
声明默认方法。其中一个类实现了此接口,保留默认方法不变。另一个类覆盖默认实现并提供自己的实现。Java 8 中引入的另一个有趣的功能是接口可以声明静态方法(并提供静态方法的实现)。这是一个例子: notRequired()
default
DefaultableImpl
OverridableImpl
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier<Defaulable> supplier ) {
return supplier.get();
}
}
一小段代码结合了上例中的默认方法和静态方法:
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}
该程序的控制台输出如下所示:
Default implementation
Overridden implementation
JVM 中默认的方法实现非常高效,并且方法调用由字节码指令支持。默认方法允许现有的 Java 接口在不破坏编译过程的情况下发展。很好的例子是添加到接口中的许多方法java.util.Collection
:stream()
, parallelStream()
, forEach()
, removeIf()
, ... 尽管默认方法功能强大,但应谨慎使用:在声明默认方法之前,您应该三思而行是否真的有必要,因为这可能会导致复杂层次结构中的编译歧义和错误。有关更详细的信息,请参阅文档。
2.3. 参考方法
引用方法实现有用的语法来引用 Java 类或对象(实例)的现有方法或构造函数。与 lambda 表达式一起,引用方法使语言构造紧凑、简洁,使其基于模板。下面是一个类Car
作为不同方法定义的示例,让我们重点介绍一下支持的四种引用方法类型:
public static class Car {
public static Car create( final Supplier<Car> supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
Class::new
第一个引用方法是对具有语法的构造函数或泛型的替代方法 的引用Class< T >::new
。请注意,构造函数没有参数。
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
第二个选项是使用语法引用静态方法Class::static_method
。请注意,该方法仅采用一个类型为 的参数Car
。
cars.forEach( Car::collide );
第三种类型是对某种类型的任意对象实例的方法的引用,其语法为Class::method
。请注意,该方法不接受任何参数。
cars.forEach( Car::repair );
最后,第四种类型是对特定类实例方法的引用,语法为instance::method
。请注意,该方法仅接受一个类型为 的参数Car
。
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
作为 Java 程序运行所有这些示例会产生以下控制台输出(类引用Car
可能会有所不同):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
更详细的信息和参考方法详情请参考官方文档。
2.4. 重复注释
自从Java 5引入了对注解的支持以来,这个特性已经变得非常流行并且使用非常广泛。然而,使用注释的限制之一是同一注释不能在同一位置声明多次。Java 8 打破了这个规则并引入了重复的注释。这允许相同的注释在声明它们的位置重复多次。重复的注释应该使用 annotation 来注释自己@Repeatable
。事实上,这与其说是语言的变化,不如说是编译器的技巧,但技术保持不变。让我们看一个简单的例子:
package com.javacodegeeks.java8.repeatable.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface Filters {
Filter[] value();
}
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
public static void main(String[] args) {
for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
System.out.println( filter.value() );
}
}
}
正如我们所看到的,该类Filter
使用@Repeatable(Filters. class
)进行注释。Filters
只是注释的所有者Filter
,但 Java 编译器试图向开发人员隐藏它们的存在。因此,该接口Filterable
包含声明Filter
两次的注释(不提及Filters
)。Reflection API 还提供了一种getAnnotationsByType()
用于返回某种类型的重复注释的新方法(请记住 Filterable..getAnnotation class
( Filters. class
) 将返回编译器注入的实例Filters
)。程序的输出将如下所示:
filter1
filter2
更详细的信息请参考官方文档。
2.5. 改进的类型推断
Java 8 编译器已获得许多类型推断改进。在许多情况下,编译器可以定义显式类型参数,从而使代码更简洁。让我们看一个例子:package com.javacodegeeks.java8.type.inference;
public class Value<T> {
public static<T> T defaultValue() {
return null;
}
public T getOrDefault( T value, T defaultValue ) {
return ( value != null ) ? value : defaultValue;
}
}
这是 type 的用法Value<String>
:
package com.javacodegeeks.java8.type.inference;
public class TypeInference {
public static void main(String[] args) {
final Value<String> value = new Value<>();
value.getOrDefault( "22", Value.defaultValue() );
}
}
类型参数Value.defaultValue()
是自动确定的,不需要显式提供。在 Java 7 中,同一示例将无法编译,必须重写为 <NOBR>Value.<String>defaultValue()</NOBR>。
2.6。扩展注释支持
Java 8 扩展了可以使用注释的上下文。如今,几乎任何东西都可以有注释:局部变量、泛型类型、超类和实现的接口,甚至方法异常。下面提供了一些示例:package com.javacodegeeks.java8.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
public class Annotations {
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
public @interface NonEmpty {
}
public static class Holder<@NonEmpty T> extends @NonEmpty Object {
public void method() throws @NonEmpty Exception {
}
}
@SuppressWarnings( "unused" )
public static void main(String[] args) {
final Holder<String> holder = new @NonEmpty Holder<String>();
@NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
}
}
ElementType.TYPE_USE
以及ElementType.TYPE_PARAMETER
两个新的元素类型来描述相关的注释上下文。Annotation Processing API
还进行了一些细微的更改以识别 Java 中的新注释类型。
3. Java编译器的新特性
3.1. 参数名称
长期以来,Java 开发人员发明了多种在 Java 字节码中存储方法参数名称的方法,以使它们在运行时可用(例如Paranamer 库)。最后,Java 8 在语言(使用反射 API 和方法Parameter.getName()
)和字节码(使用新的编译器参数javac
:)中创建了这个困难的函数–parameters
。
package com.javacodegeeks.java8.parameter.names;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
public class ParameterNames {
public static void main(String[] args) throws Exception {
Method method = ParameterNames.class.getMethod( "main", String[].class );
for( final Parameter parameter: method.getParameters() ) {
System.out.println( "Parameter: " + parameter.getName() );
}
}
}
如果您在不使用参数的情况下编译此类–parameters
,然后运行该程序,您将看到如下内容:
Parameter: arg0
随着参数–parameters
传递给编译器,程序输出将有所不同(将显示参数的实际名称):
Parameter: args
对于高级 Maven 用户,可以使用以下部分将 –parameters 参数添加到编译中maven-compiler-plugin
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
要检查参数名称的可用性,该类isNamePresent()
提供了一种方便的方法Parameter
。
4.新的Java工具
Java 8 附带了一组新的命令行工具。在本节中,我们将看看其中最有趣的部分。4.1. Nashorn 引擎:jjs
jjs是一个基于命令行的独立 Nashorn 引擎。它获取 JavaScript 源代码文件列表并运行它们。例如,让我们创建一个包含以下内容的 func.js文件:function f() {
return 1;
};
print( f() + 1 );
要运行此文件,我们将其作为参数传递给jjs:
jjs func.js
控制台输出将如下所示:
2
有关更多详细信息,请参阅文档。
4.2. 类依赖分析器:jdeps
jdeps是一个非常棒的命令行工具。它显示了 Java 类的包或类级别的依赖关系。它接受.class文件、文件夹或JAR 文件作为输入。默认情况下,jdeps将依赖项输出到标准输出(控制台)。作为示例,让我们看一下流行的Spring 框架库的依赖关系报告。为了使示例简短,我们只看一下 JAR 文件的依赖关系org.springframework.core-3.0.5.RELEASE.jar
。
jdeps org.springframework.core-3.0.5.RELEASE.jar
该命令输出相当多,因此我们只分析部分输出。依赖项按包分组。如果没有依赖,会显示not found。
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
-> java.io
-> java.lang
-> java.lang.annotation
-> java.lang.ref
-> java.lang.reflect
-> java.util
-> java.util.concurrent
-> org.apache.commons.logging not found
-> org.springframework.asm not found
-> org.springframework.asm.commons not found
org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
-> java.lang
-> java.lang.annotation
-> java.lang.reflect
-> java.util
更详细的信息请参考官方文档。
GO TO FULL VERSION