JavaRush /Java 博客 /Random-ZH /Java 8 功能 – 终极指南(第 2 部分)
0xFF
第 9 级
Донецк

Java 8 功能 – 终极指南(第 2 部分)

已在 Random-ZH 群组中发布
翻译文章Java 8 Features – The ULTIMATE Guide的第二部分。第一部分在这里(链接可能会更改)。 Java 8 功能 – 终极指南(第 2 部分)- 1

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 ()方法转换当前值并返回一个新实例。该方法与 类似,但它采用默认值而不是函数。这是该程序的输出: OptionalorElseGet()OptionalOptionalOptionalorElse()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 );
        }
    }
}
任务有一定的意义(或伪难度),可以是OPENCLOSE。让我们介绍一小部分可以解决的问题。
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为流。最后,使用 方法 对所有点进行求和,从而提供最终结果。在继续下一个示例之前,需要记住一些有关线程的注意事项(更多详细信息请参见此处)。操作分为中间操作和最终操作。 中间操作返回一个新流。它们总是很懒惰;当执行诸如 之类的中间操作时,它们实际上并不执行过滤,而是创建一个新流,该新流在完成后包含与给定谓词匹配的原始流的元素。 有限运算(例如和)可以通过流传递以产生结果或副作用。一旦最终操作完成,该流就被视为已使用,并且不能再次使用。几乎在所有情况下,最终操作都倾向于通过底层数据源来完成其遍历。线程的另一个有价值的特性是对并行进程的开箱即用的支持。让我们看一下这个例子,它找到所有问题的分数总和。 IntegerTask::getPointsTasksumstreamfilterforEachsum
// 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
我们将关注的其他新类是LocaleDateLocalTimeLocaleDate仅包含 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连接LocaleDateandLocalTime包含 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, 2014April 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

七、结论

未来已来:Java 8 通过提供让开发人员提高工作效率的功能,推动了其平台的发展。现在将生产系统迁移到 Java 8 还为时过早,但在接下来的几个月里,采用率应该会慢慢开始增长。然而,现在是时候开始准备代码库以实现 Java 8 兼容性,并准备在 Java 8 足够安全和​​稳定时合并 Java 8 更改。作为社区对 Java 8 接受度的证明,Pivotal 最近发布了 Spring 框架,为 Java 8 提供了生产支持您可以在评论中提供有关 Java 8 中令人兴奋的新功能的意见。

8. 来源

一些深入讨论 Java 8 功能各个方面的其他资源:
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION