翻译文章Java 8 Features – The ULTIMATE Guide的第二部分。第一部分在这里(链接可能会更改)。
5. Java 8库的新特性
Java 8 添加了许多新类并扩展了现有类,以更好地支持现代并发、函数式编程、日期/时间等。5.1. 类别 可选
著名的NullPointerException是迄今为止 Java 应用程序失败的最常见原因。很早以前,Google 的优秀项目Guava就提出了Optional
一种解决方案NullPointerException
,从而防止代码被 null 检查污染,从而鼓励编写更干净的代码。受 Google 启发的 Guava 类Optional
现在是 Java 8 的一部分。 Optional
它只是一个容器:它可以包含一个值或某种类型Т
,或者只是 null。它提供了许多有用的方法,因此显式空检查不再合理。请参阅官方文档以获取更详细的信息。让我们看一下两个使用的小例子Optional
:带 null 和不带 null。
Optional<String> fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
如果实例包含非空值,则 该方法isPresent()
返回true ,否则返回false。如果结果包含 null,则该方法包含一个回退机制,接受函数来生成默认值。map ()方法转换当前值并返回一个新实例。该方法与 类似,但它采用默认值而不是函数。这是该程序的输出: Optional
orElseGet()
Optional
Optional
Optional
orElse()
orElseGet()
Full Name is set? false
Full Name: [none]
Hey Stranger!
让我们快速看一下另一个例子:
Optional<String> firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) );
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
System.out.println();
结果会是这样的:
First Name is set? true
First Name: Tom
Hey Tom!
更详细的信息请参考官方文档。
5.2. 流
新添加的 Stream API (java.util.stream
) 引入了 Java 中真正的函数式编程。它是迄今为止 Java 库中最全面的补充,使 Java 开发人员的效率显着提高,并且使他们能够创建高效、干净和简洁的代码。Stream API 使处理集合变得更加容易(但不限于它们,我们稍后会看到)。我们以一个简单的类为例Task
。
public class Streams {
private enum Status {
OPEN, CLOSED
};
private static final class Task {
private final Status status;
private final Integer points;
Task( final Status status, final Integer points ) {
this.status = status;
this.points = points;
}
public Integer getPoints() {
return points;
}
public Status getStatus() {
return status;
}
@Override
public String toString() {
return String.format( "[%s, %d]", status, points );
}
}
}
任务有一定的意义(或伪难度),可以是OPEN或CLOSE。让我们介绍一小部分可以解决的问题。
final Collection<Task> tasks = Arrays.asList(
new Task( Status.OPEN, 5 ),
new Task( Status.OPEN, 13 ),
new Task( Status.CLOSED, 8 )
);
我们要了解的第一个问题是OPEN任务当前包含 多少分?在 Java 8 之前,通常的解决方案是使用迭代器foreach
。但在 Java 8 中,答案是流:支持顺序和并行聚合操作的元素序列。
// Подсчет общего количества очков всех активных задач с использованием sum()
final long totalPointsOfOpenTasks = tasks
.stream()
.filter( task -> task.getStatus() == Status.OPEN )
.mapToInt( Task::getPoints )
.sum();
System.out.println( "Total points: " + totalPointsOfOpenTasks );
控制台输出将如下所示:
Total points: 18
让我们看看这里发生了什么。首先,任务集合被转换为流表示。然后,该操作filter
会过滤掉所有处于CLOSED状态的任务。在下一步中,该操作使用每个实例的方法mapToInt
将流转换Task
为流。最后,使用 方法 对所有点进行求和,从而提供最终结果。在继续下一个示例之前,需要记住一些有关线程的注意事项(更多详细信息请参见此处)。操作分为中间操作和最终操作。 中间操作返回一个新流。它们总是很懒惰;当执行诸如 之类的中间操作时,它们实际上并不执行过滤,而是创建一个新流,该新流在完成后包含与给定谓词匹配的原始流的元素。 有限运算(例如和)可以通过流传递以产生结果或副作用。一旦最终操作完成,该流就被视为已使用,并且不能再次使用。几乎在所有情况下,最终操作都倾向于通过底层数据源来完成其遍历。线程的另一个有价值的特性是对并行进程的开箱即用的支持。让我们看一下这个例子,它找到所有问题的分数总和。 Integer
Task::getPoints
Task
sum
stream
filter
forEach
sum
// Calculate total points of all tasks
final double totalPoints = tasks
.stream()
.parallel()
.map( task -> task.getPoints() ) // or map( Task::getPoints )
.reduce( 0, Integer::sum );
System.out.println( "Total points (all tasks): " + totalPoints );
这与第一个示例非常相似,只不过我们尝试并行处理所有任务并使用方法计算最终结果reduce
。这是控制台输出:
Total points (all tasks): 26.0
通常需要根据特定标准对元素进行分组。该示例演示了线程如何帮助解决此问题。
// Группировка задач по их статусу
final Map<Status, List<Task>> map = tasks
.stream()
.collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
控制台输出如下:
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
为了完成问题示例,让我们根据总分来计算集合中每个问题的总体百分比(或权重):
// Подсчет веса каждой задачи (How процент от общего количества очков)
final Collection<String> result = tasks
.stream() // Stream<String>
.mapToInt( Task::getPoints ) // IntStream
.asLongStream() // LongStream
.mapToDouble( points -> points / totalPoints ) // DoubleStream
.boxed() // Stream<Double>
.mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
.mapToObj( percentage -> percentage + "%" ) // Stream<String>
.collect( Collectors.toList() ); // List<String>
System.out.println( result );
控制台输出将如下所示:
[19%, 50%, 30%]
最后,正如我们之前提到的,Stream API 不仅仅适用于 Java 集合。典型的 I/O 操作(例如逐行读取文本文件)非常适合使用流处理。这是一个小例子来证明这一点。
final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
onConsole
在线程上调用的 方法返回一个带有附加私有处理程序的等效线程。close()
当在线程上调用方法时,将调用私有处理程序。Stream API 以及lambda和引用方法以及 Java 8 中的默认方法和静态方法是现代软件开发范例的答案。更详细的信息请参考官方文档。
5.3. 日期/时间 API (JSR 310)
Java 8 通过提供新的日期和时间 API (JSR 310),为日期和时间管理带来了新的面貌。日期和时间操作是 Java 开发人员最大的痛点之一。java.util.Date
遵循标准并java.util.Calendar
没有总体上改善这种情况(甚至可能使其更加混乱)。这就是Joda-Time的诞生:Java 的一个出色的日期/时间 API 替代品。Java 8 (JSR 310) 中的新日期/时间 API 深受Joda-Time的影响,并从中汲取了精华。新包java.time
包含日期、时间、日期/时间、时区、持续时间和时间操作的所有类。API 设计非常重视不变性:不允许更改(从 中吸取的惨痛教训java.util.Calendar
)。如果需要修改,将返回相应类的新实例。让我们看看主要的类和它们的使用示例。第一类Clock
,它使用时区提供对当前时刻、日期和时间的访问。Clock
可以用来代替System.currentTimeMillis()
和TimeZone.getDefault()
。
// Получить системное время How смещение UTC
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
控制台输出示例:
2014-04-12T15:19:29.282Z
1397315969360
我们将关注的其他新类是LocaleDate
和LocalTime
。LocaleDate
仅包含 ISO-8601 日历系统中的日期部分,不包含时区。因此,LocalTime
它仅包含时间码的一部分>。
// получить местную date и время время
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );
System.out.println( date );
System.out.println( dateFromClock );
// получить местную date и время время
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
System.out.println( time );
System.out.println( timeFromClock );
控制台输出示例:
2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
LocalDateTime
连接LocaleDate
andLocalTime
包含 ISO-8601 日历系统中的日期和时间,但不包含时区。下面给出一个简单的例子。
// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
System.out.println( datetime );
System.out.println( datetimeFromClock );
控制台输出示例:
2014-04-12T11:37:52.309
2014-04-12T15:37:52.309
如果您需要特定时区的日期/时间,ZonedDateTime
. 它包含 ISO-8601 日历系统中的日期和时间。以下是不同时区的一些示例。
// Получение даты/времени для временной зоны
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );
控制台输出示例:
2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
最后,让我们看一下该类Duration
:以秒和纳秒为单位的时间跨度。这使得两个日期之间的计算变得非常简单。让我们看看如何做到这一点:
// Получаем разницу между двумя датами
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
上面的示例计算两个日期April 16, 2014和April 16, 2015之间的持续时间(以天和小时为单位) 。以下是控制台输出的示例:
Duration in days: 365
Duration in hours: 8783
Java 8 中新日期/时间的总体印象非常非常积极。部分是因为这些变化是基于久经考验的基础(Joda-Time),部分是因为这次问题得到了认真的重新考虑,并且开发人员的声音得到了倾听。详细信息请参考官方文档。
5.4. Nashorn JavaScript 引擎
Java 8 附带了新的 Nashorn JavaScript 引擎,它允许您在 JVM 上开发和运行某些类型的 JavaScript 应用程序。Nashorn JavaScript 引擎只是 javax.script.ScriptEngine 的另一个实现,它遵循相同的规则集以允许 Java 和 JavaScript 交互。这是一个小例子。ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
控制台输出示例:
jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2
5.5. Base64
最后,随着 Java 8 的发布,对 Base64 编码的支持进入了 Java 标准库。它非常易于使用,示例演示了这一点。package com.javacodegeeks.java8.base64;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class Base64s {
public static void main(String[] args) {
final String text = "Base64 finally in Java 8!";
final String encoded = Base64
.getEncoder()
.encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
System.out.println( encoded );
final String decoded = new String(
Base64.getDecoder().decode( encoded ),
StandardCharsets.UTF_8 );
System.out.println( decoded );
}
}
程序的控制台输出显示编码和解码的文本:
QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!
还有 URL 友好的编码器/解码器以及 MIME 友好的编码器/解码器(Base64.getUrlEncoder()
/ Base64.getUrlDecoder()
、Base64.getMimeEncoder()
/ Base64.getMimeDecoder()
)的类。
5.6. 并行数组
Java 8 版本添加了许多用于并行数组处理的新方法。也许其中最重要的是parallelSort()
,它可以大大加快多核机器上的排序速度。下面的小示例演示了新方法系列 ( parallelXxx
) 的实际应用。
package com.javacodegeeks.java8.parallel.arrays;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelArrays {
public static void main( String[] args ) {
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong,
index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
}
}
这段小代码使用一种方法parallelSetAll()
用 20,000 个随机值填充数组。此后即可应用parallelSort()
。程序打印排序前后的前 10 个元素,以表明数组确实已排序。示例程序输出可能如下所示(请注意,数组的元素是随机的)。
Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378
Sorted: 39 220 263 268 325 607 655 678 723 793
5.7. 并行性
该类中添加了新方法,java.util.concurrent.ConcurrentHashMap
以支持基于新添加的流对象和 lambda 表达式的聚合操作。此外,该类中还添加了新方法java.util.concurrent.ForkJoinPool
来支持共享池(另请参阅我们关于 Java 并发性的免费课程)。添加了一个新类,java.util.concurrent.locks.StampedLock
以提供基于功能的锁定,具有三种访问模式用于读/写控制(可以将其视为不太好的锁定的更好替代方案java.util.concurrent.locks.ReadWriteLock
)。已添加到包中的新类java.util.concurrent.atomic
:
- 双累加器
- 双加法器
- 长累加器
- 长加法器
6. Java运行环境(JVM)的新特性
该区域PermGen
已退役并由Metaspace (JEP 122) 取代。JVM 选项-XX:PermSize
和-XX:MaxPermSize
已分别替换为-XX:MetaSpaceSize
和-XX:MaxMetaspaceSize
。
GO TO FULL VERSION