作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Eduard Grinchenko
Verified Expert in Engineering
13 Years of Experience

edward (MCE)是一位才华横溢的Java工程师,在OOP分析方面拥有丰富的专业知识. 他参与了整个SDLC,从设计到维护.

Expertise

Share

Java平台的最新版本, Java 8一年多前被释放. Many companies and developers 还在使用以前的版本吗, 这可以理解, 因为从一个平台版本迁移到另一个平台版本有很多问题. 即便如此,许多开发商仍在起步 new 使用旧版本Java的应用程序. 很少有好的理由这样做, 因为Java 8对该语言进行了一些重要的改进.

There are many new features in Java 8. 我将向你展示一些最有用和最有趣的方法:

  • Lambda expressions
  • 用于处理集合的流API
  • 的异步任务链 CompletableFuture
  • Brand new Time API

Lambda Expressions

A lambda 一个代码块是否可以被引用并传递给另一段代码以供将来执行一次或多次. 例如,其他语言中的匿名函数是lambda. Like functions, lambda可以在执行时传递参数, 修改结果. Java 8 introduced lambda expressions,它提供了一个简单的语法来创建和使用lambdas.

让我们看一个示例,看看它如何改进我们的代码. 这里我们有一个简单的比较器来比较两个 Integer 值的模2:

class BinaryComparator implements Comparator{
   @Override
   public int比较(整数i1,整数i2) {
       return i1 % 2 - i2 % 2;
   }
}

可以调用该类的一个实例, in the future, 在需要这个比较器的代码中, like this:

...
List list = ...;
Comparator comparator = new BinaryComparator();
Collections.排序(列表,比较器);
...

新的lambda语法允许我们更简单地做到这一点. 下面是一个简单的lambda表达式,它的作用与 compare method from BinaryComparator:

(Integer i1, Integer i2) -> i1 % 2 - i2 % 2;

结构与函数有许多相似之处. 在括号中,我们设置了一个参数列表. The syntax -> 表明这是一个. 在这个表达式的右边部分,我们建立了的行为.

Java 8 lambda表达式

现在我们可以改进前面的例子:

...
List list = ...;
Collections.sort(list, (Integer i1, Integer i2) -> i1 % 2 - i2 % 2);
...

可以用这个对象定义一个变量. Let’s see how it looks:

Comparator comparator = (Integer i1, Integer i2) -> i1 % 2 - i2 % 2;

现在我们可以重用这个功能,像这样:

...
List list1 = ...;
List list2 = ...;
Collections.排序(list1,比较器);
Collections.类(用于比较器);
...

注意,在这些示例中,lambda被传递给 sort() 方法的实例所使用的方法相同 BinaryComparator 在前面的例子中传递. JVM如何知道如何正确地解释lambda?

为了允许函数接受lambdas作为参数,Java 8引入了一个新概念: functional interface. 功能接口是只有一个抽象方法的接口. 实际上,Java 8将lambda表达式视为函数式接口的特殊实现. This means that, 以接收lambda作为方法参数, 该参数声明的类型只需要是一个功能接口.

声明函数式接口时,可以添加 @FunctionalInterface 向开发人员显示它是什么的符号:

@FunctionalInterface
私有接口DTOSender {
   无效发送(String accountId, DTO);
}

sendDTO(bessmodel对象,DTOSender) {
   //发送的逻辑...
   ...
   dtoSender.send(id, dto);
   ...
}

现在,我们可以调用这个方法了 sendDTO,传入不同的lambdas来实现不同的行为,如下所示:

sendDTO(object, ((accountId, dto) -> sendToAndroid(accountId, dto)));
sendDTO(object, ((accountId, dto) -> sendToIos(accountId, dto)));

Method References

Lambda参数允许我们修改函数或方法的行为. 正如我们在最后一个例子中看到的,有时lambda只用于调用另一个方法(sendToAndroid or sendToIos). 对于这种特殊情况,Java 8引入了一种方便的简写: method references. 此缩写语法表示调用方法的lambda,其形式为 objectName::methodName. 这使我们可以使前面的例子更加简洁和可读:

sendDTO(对象::sendToAndroid);
sendDTO(对象::sendToIos);

在本例中,方法 sendToAndroid and sendToIos are implemented in this class. 我们也可以引用另一个对象或类的方法.

Stream API

Java 8带来了新的功能 Collections,以全新的流API的形式. 提供了这个新功能 java.util.stream 包,并旨在使更多 functional 使用集合编程的方法. 正如我们将看到的,这在很大程度上要归功于我们刚刚讨论的新的lambda语法.

Stream API提供了简单的过滤, counting, 集合的映射, 以及从它们中获取信息片段和子集的不同方法. 多亏了函数式语法, Stream API允许使用更短、更优雅的代码来处理集合.

让我们从一个简短的例子开始. 我们将在所有示例中使用此数据模型:

class Author {
   String name;
   int countOfBooks;
}

class Book {
   String name;
   int year;
   Author author;
}

假设我们需要在a中打印所有作者 books 在2005年之后写了一本书. 我们如何在Java 7中做到这一点呢?

for (Book Book: books) {
   if (book.author != null && book.year > 2005){
       System.out.println(book.author.name);
   }
}

如何在Java 8中实现呢?


books.stream()
       .filter(book -> book.year > 2005)  // filter out books published in or before 2005
       .map(Book::getAuthor) //获取剩余图书的作者列表
       .filter(Objects::nonNull) //从列表中删除空作者
       .map(Author::getName) //获取剩余作者的名字列表
       .forEach(System.out::println);     // print the value of each remaining element

它只是一个表达式! Calling the method stream() on any Collection returns a Stream 对象封装该集合的所有元素. 这可以使用来自Stream API的不同修饰符进行操作,例如 filter() and map(). 每个修饰符返回一个new Stream 对象的修改结果,可以进一步操作. The .forEach() 方法允许我们对结果流的每个实例执行一些操作.

这个示例还演示了函数式编程和lambda表达式之间的密切关系. 注意,传递给流中每个方法的参数要么是自定义lambda, or a method reference. Technically, 每个修饰符可以接收任何功能接口, 如前一节所述.

Stream API帮助开发人员从一个新的角度看待Java集合. 想象一下,现在我们需要 Map 每个国家的可用语言. 这在Java 7中如何实现呢?

Map> countryToSetOfLanguages = new HashMap<>();

for (Locale: Locale).getAvailableLocales()){
   String country = locale.getDisplayCountry();
   if (!countryToSetOfLanguages.containsKey(country)){
       countryToSetOfLanguages.put(country, new HashSet<>());
   }
   countryToSetOfLanguages.get(country).add(locale.getDisplayLanguage());
}

在Java 8中,事情变得更简洁了:

import java.util.stream.*;
import static java.util.stream.Collectors.*;

...
Map> countryToSetOfLanguages = Stream.of(Locale.getAvailableLocales())
      .收集(groupingBy (Locale:: getDisplayCountry,
                          映射(Locale:: getDisplayLanguage,释放())));

The method collect() 允许我们以不同的方式收集流的结果. 在这里,我们可以看到它首先按国家分组,然后按语言分组. (groupingBy() and toSet() 的静态方法是否都来自 Collectors class.)

JAVA 8 STREAM API

流API还有很多其他的功能. 可以找到完整的文档 here. 我建议进一步阅读,以更深入地了解这个包所提供的所有强大工具.

的异步任务链 CompletableFuture

In Java 7’s java.util.concurrent 包中,有一个接口 Future,它允许我们在将来获得某些异步任务的状态或结果. 要使用此功能,我们必须:

  1. Create an ExecutorService,它管理异步任务的执行,并且可以生成 Future 对象来跟踪它们的进度.
  2. 异步创建 Runnable task.
  3. Run the task in the ExecutorService, which will provide a Future 允许访问状态或结果.

以便利用异步任务的结果, 从外部监督其进展是必要的, 的方法 Future interface, and when it is ready, 显式检索结果并对其执行进一步的操作. 这在没有错误的情况下实现起来可能相当复杂, 特别是在具有大量并发任务的应用程序中.

然而,在Java 8中, Future 概念进一步发展,与 CompletableFuture 接口,它允许创建和执行异步任务链. 它是在Java 8中创建异步应用程序的强大机制, 因为它允许我们在完成每个任务后自动处理结果.

Let’s see an example:

import java.util.concurrent.CompletableFuture;
...
CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> blockingReadPage())
       .thenApply (:: getLinks)
       .thenAccept(System.out::println);

The method CompletableFuture.supplyAsync 在默认情况下创建新的异步任务 Executor (typically ForkJoinPool). 任务完成后,其结果将自动作为参数提供给函数 this::getLinks,它也在一个新的异步任务中运行. 最后,第二阶段的结果将自动打印到 System.out. thenApply() and thenAccept() are just two of several useful methods 可帮助您构建并发任务,而无需手动使用 Executors.

The CompletableFuture 使管理复杂异步操作的顺序变得容易. 假设我们需要创建一个包含三个任务的多步骤数学运算. Task 1 and task 2 使用不同的算法来找到第一步的结果, 我们知道只有一个会成功,而另一个会失败. 然而,哪一个工作取决于输入的数据,我们不知道提前. 这些任务的结果必须用……的结果加起来 task 3. 因此,我们需要找到任意一个的结果 task 1 or task 2, and the result of task 3. 为了实现这一点,我们可以这样写:

import static java.util.concurrent.CompletableFuture.*;

...

Supplier task1 = (...) -> {
   ...                                   //一些复杂的计算
   return 1;                             // example result
};

Supplier task2 = (...) -> {
   ...                                   //一些复杂的计算
   throw new RuntimeException();         // example exception
};

Supplier task3 = (...) -> {
   ...                                   //一些复杂的计算 
   return 3;                             // example result
};

supplyAsync(task1) //运行task1
       .applyToEither(//使用先准备好的结果,task1的结果或
               supplyAsync(task2), // task2的结果
               (Integer i) -> i)         // return result as-is
       .然后组合(//组合结果
               supplyAsync(task3), //与task3的结果
               Integer::sum) //使用求和
       .thenAccept(System.out::println); // print final result after execution

如果我们研究一下Java 8是如何处理这个问题的, 我们将看到这三个任务将同时运行, asynchronously. Despite task 2 如果失败并出现异常,则计算最终结果并成功打印.

JAVA 8异步编程与CompletableFuture

CompletableFuture 使构建具有多个阶段的异步任务变得更加容易, 并为我们提供了一个简单的界面,用于定义在每个阶段完成时应该采取的行动.

Java Date and Time API

As stated by Java’s own admission:

在Java SE 8发布之前,Java日期和时间机制是由 java.util.Date, java.util.Calendar, and java.util.TimeZone 类及其子类,例如 java.util.GregorianCalendar. 这些类有几个缺点,包括

  • Calendar类不是类型安全的.
  • 因为类是可变的,所以它们不能在多线程应用程序中使用.
  • 由于不同寻常的月份编号和缺乏类型安全,应用程序代码中的错误很常见.”

Java 8最终解决了这些长期存在的问题 java.time 包,其中包含处理日期和时间的类. 它们都是不可变的,并且具有类似于流行框架的api Joda-Time,几乎所有Java开发人员都在他们的应用程序中使用它而不是本机 Date, Calendar, and TimeZone.

下面是这个包中一些有用的类:

  • Clock -时钟告诉当前时间,包括当前时刻,日期,时间与时区.
  • Duration, and Period - An amount of time. Duration 使用基于时间的值,如“76”.8 seconds, and Period,以日期为基础,例如“4年6个月12天”.
  • Instant -一个瞬时的时间点,有几种格式.
  • LocalDate, LocalDateTime, LocalTime, Year, YearMonth - A date, time, year, month, 或者两者的结合, 在ISO-8601日历系统中没有时区.
  • OffsetDateTime, OffsetTime -在ISO-8601日历系统中,与UTC/Greenwich有偏移的日期时间, 如“2015-08-29T14:15:30+01:00”.”
  • ZonedDateTime - ISO-8601日历系统中与时区相关联的日期时间, 例如“1986-08-29T10:15:30+01:00欧洲/巴黎”.”

JAVA 8 TIME API

有时,我们需要找到一些相对的日期,比如“这个月的第一个星期二”.” For these cases java.time 提供一个特殊的类 TemporalAdjuster. The TemporalAdjuster 类包含一组标准的调整器,可作为静态方法使用. These allow us to:

  • 找出一个月的第一天或最后一天.
  • 找出下一个月或前一个月的第一天或最后一天.
  • 找出一年的第一天或最后一天.
  • 查找明年或前一年的第一天或最后一天.
  • 查找一个月内一周的第一天或最后一天,例如“六月的第一个星期三”.”
  • 找到一周的下一个或前一天,比如“下周四”.”

这里有一个简短的例子,如何得到每月的第一个星期二:

LocalDate getFirstTuesday(int year, int month) {
   return LocalDate.of(year, month, 1)
                   .with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY));
}
Still using Java 7? Get with the program! #Java8

Java 8 in Summary

正如我们所看到的,Java 8是Java平台的一个划时代的版本. 有很多语言的变化, 特别是引入了, 它代表了将更多函数式编程能力引入Java的举措. Stream API就是一个很好的例子,lambda可以改变我们使用标准Java工具的方式.

此外,Java 8还带来了一些用于处理的新特性 异步编程 以及对其日期和时间工具进行急需的彻底改革.

总之,这些变化代表了Java语言向前迈出的一大步 Java development 更有趣,更有效率.

聘请Toptal这方面的专家.
Hire Now
Eduard Grinchenko

Eduard Grinchenko

Verified Expert in Engineering
13 Years of Experience

Tbilisi, Georgia

2015年1月22日成为会员

About the author

edward (MCE)是一位才华横溢的Java工程师,在OOP分析方面拥有丰富的专业知识. 他参与了整个SDLC,从设计到维护.

作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

世界级的文章,每周发一次.

输入您的电子邮件,即表示您同意我们的 privacy policy.

Toptal Developers

Join the Toptal® community.

" class="hidden">时尚论坛