java8概述:有哪些核心新特性?为什么会有这些特性?

java8可以说是自java诞生以来,发生最大变化的一个版本,关键是这一变化对于我们程序员来说是一个很大的福音:java8提供的新功能能够帮助我们写出更清晰、更简洁的代码(这貌似是程序员一生都在追求的东西吧,而java8让java开发者的这一追求瞬间向前跨了一大步)。

那么java8究竟提供了哪些核心新特性来让程序员轻易就可以写出更清晰、更简洁的代码呢?

先来看一个小需求"对库存中的苹果按重量升序排列"在java8中及java8之前的实现,感受一下java8在编码上给我们带来的简化。

java8以前的做法:

  1. Collections.sort(inventory, new Comparator<Apple>() {
  2. public int compare(Apple a1, Apple a2){
  3. return a1.getWeight().compareTo(a2.getWeight());
  4. }
  5. });

在java8里面,上面这5行代码可以简化为一行:

  1. inventory.sort(comparing(Apple::getWeight));

不对比就不知道一种技术的先进性,这样对比一下,java8的魅力尽显。

下面将逐一介绍java8里的一些核心新特性或概念,但尽限于概述,本系列文章的后续篇章中会逐一展开对java8的解析。

  • 函数式编程
  • Lambda表达式
  • 流(stream)
  • 接口(interface)默认方法

函数式编程

函数式编程是一种编程风格,它的核心是把函数作为值。而java8把函数式编程的精华融入到了java语法中,你可以将代码传递给方法,也能够返回代码并将其包含在数据结构中,同时这种被函数式编程界称为函数的代码,在java8中可以被来回传递并加以组合,以产生强大的编程语汇,使我们可以用更少的时间,编写更清楚、更简洁的代码。

下面来看一个传递代码的例子:

下面代码的两个方法分别过滤出库存中绿色的苹果与重量大于150g的苹果:

  1. /**
  2. *过滤出绿色苹果
  3. */
  4. public static List<Apple> filterGreenApples(List<Apple> inventory){
  5. List<Apple> result = new ArrayList<>();
  6. for (Apple apple: inventory){
  7. //颜色等于green则加入筛选结果中
  8. if ("green".equals(apple.getColor())) {
  9. result.add(apple);
  10. }
  11. }
  12. return result;
  13. }
  14.  
  15. /**
  16. *过滤出重量大于150g的苹果
  17. */
  18. public static List<Apple> filterHeavyApples(List<Apple> inventory){
  19. List<Apple> result = new ArrayList<>();
  20. for (Apple apple: inventory){
  21. //重量是否大于150g则加入筛选结果中
  22. if (apple.getWeight() > 150) {
  23. result.add(apple);
  24. }
  25. }
  26. return result;
  27. }

可以看到这两个方法唯一的不同是if条件不一样,复制粘贴虽然也方便,但当类似的筛选需求增多,如过滤出红色的苹果、过滤出甜的苹果等,重复的代码也会非常多,且非常容易出错。

下面看怎么通过传递代码,一个过滤方法实现上面两个过滤需求:

  1. public static boolean isGreenApple(Apple apple) {
  2. return "green".equals(apple.getColor());
  3. }
  4. public static boolean isHeavyApple(Apple apple) {
  5. return apple.getWeight() > 150;
  6. }
  7. /**
  8. *java8里的谓词,在此列出是为了看得更清晰,可能从java.util.function包中直接导入
  9. */
  10. public interface Predicate<T>{
  11. boolean test(T t);
  12. }
  13. /**
  14. *通用的过滤方法
  15. *@param inventory
  16. *@param p 方法作为Predicate参数p传递进去
  17. */
  18. static List<Apple> filterApples(List<Apple> inventory,
  19. Predicate<Apple> p) {
  20. List<Apple> result = new ArrayList<>();
  21. for (Apple apple: inventory){
  22. //苹果符合p的条件即加入筛选结果中
  23. if (p.test(apple)) {
  24. result.add(apple);
  25. }
  26. }
  27. return result;
  28. }

上面代码中的过滤方法filterApples有两个参数:第一个参数List<Apple> inventory是需要过的数据集合;第二个参数Predicate<Apple> p 接收过滤方法或代码的参数,它实际上是java8里的谓词(Predicate)。

于是使用filterApples筛选苹果的代码变成了下面这样:

  1. //筛选绿色苹果
  2. filterApples(inventory, Apple::isGreenApple);
  3. //筛选较重的苹果
  4. filterApples(inventory, Apple::isHeavyApple);

这就是java8方法的传递,在该系列后续的文章中介绍它是怎样工作的。

如果你要增加筛选红色的苹果,只需在Apple model中增加一个返回boolean值的方法isRedApple方法就可以了。

Lambda表达式

前文中把方法作为值来传递显然很有用,但要是为类似于isHeavyApple和isGreenApple这种可能只用一两次的短方法写一堆定义有点儿烦人。于是java8引入了一套新
写法(匿名函数或Lambda),因此,过滤绿苹果与较重苹果的代码可以写成:

  1. //筛选绿苹果
  2. filterApples(inventory, (Apple a) -> "green".equals(a.getColor()) );
  3. //筛选较重的苹果
  4. filterApples(inventory, (Apple a) -> a.getWeight() > 150 );
  5. //更复杂的,同时过滤重量与颜色
  6. filterApples(inventory, (Apple a) -> a.getWeight() < 80 && "brown".equals(a.getColor()) );

上述3个filterApples方法调用,传递给其的第二个参数,都使用了匿名的Lambda表达式

那么Lambda表达式是什么?

我们暂时可以把Lambda表达式理解为没有声明名称的方法或匿名类,它可以作为参数传递给一个方法。在本系列后续的文章中会详细介绍Lambda的语法及用途。

流(stream)

先来感受一下java8中流API的魅力。

需求:
从交易列表中筛选出金额较高的交易,然后按货币种类分组。

java8以前Collection API实现:

  1. //新建一个hashmap,用于存放筛选结果
  2. Map<Currency, List<Transaction>> transactionsByCurrencies=new HashMap<>();
  3. //遍历交易列表
  4. for (Transaction transaction : transactions) {
  5. //筛选出金额较高的交易
  6. if(transaction.getPrice() > 1000){
  7. //获取交易的货币
  8. Currency currency = transaction.getCurrency();
  9. //获取该货币的交易分组
  10. List<Transaction> transactionsForCurrency=transactionsByCurrencies.get(currency);
  11. //若该货币的交易分组为空,则新建一个
  12. if (transactionsForCurrency == null) {
  13. transactionsForCurrency = new ArrayList<>();
  14. transactionsByCurrencies.put(currency,transactionsForCurrency);
  15. }
  16. //将交易加入对应分组List
  17. transactionsForCurrency.add(transaction);
  18. }
  19. }

java8中Stream Api的实现:

  1. import static java.util.stream.Collectors.toList;
  2. Map<Currency, List<Transaction>> transactionsByCurrencies =
  3. transactions.stream()//从交易列表中生成流
  4. .filter((Transaction t) -> t.getPrice() > 1000)//过滤出金额较高的交易
  5. .collect(groupingBy(Transaction::getCurrency));//将交易按货币分组

从上面Collection API与Stream Api实现同一个需求的例子,可以知道它们两者有如下差异:

  • ">Stream Api使用起来比Collection API更直观,更简洁
    对于上面的Collection API实现方式,我们很难一眼看出来这些代码是做什么的,但流api却可以,它看起来就像数据库查询式编程,只是使用java语法。
  • ">它们两者处理数据的方式不同
    使用集合api需要我们自己去实现迭代的过程,在这个过程中,我们需要使用for-each去一个个遍历列表中的所有元素并根据条件if判断作出不同的处理,这种迭代方式称为外部迭代,因为整个迭代的过程是暴露在我们程序员眼前的;与之相对应是stream api的内部迭代,我们不用理会迭代的过程,迭代在stream api的内部进行。
    注:以上两点是通过代码对比就能很容易看出的差异,下面列出的差异是更深层次的
  • ">Collection API在并行编程中需要自己处理多线程带来的所有问题,而Stream Api却在库内部实现了并行化的处理,我们只需简单调用一个方法即可
    关于多线程编程,java1.0里线程和锁,java5里添加线程池与并发集合等,java7里添加了分支/合并(fork/join)框架,使得并行变得很实用,但使用起来仍然很困难且容易出错,而java8中的流Api支持许多处理数据的并行操作,且使用相当简单,如:
    1. import static java.util.stream.Collectors.toList;
    2. List<Apple> heavyApples = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150).collect(toList());
    ">我们只需将stream()方法改成parallelStream()即可。
  • ">Stream Api在处理大数据时更有优势
    一方面是因为在处理大数据时,流Api能够轻易实现并行处理,速度更快;另一方面,流api无需像集合api一样,一次性将数据全部加载进内存后才能处理。
  • ">虽然流api与现在集合api的行为差不多:都能访问数据序列,但它们使用的侧重却有本质的不同Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的计算
    使用集合时要对集合中数据进行一些统计计算需要我们自己去实现,而流却帮我们实现了通用的计算方法,我们只需调用相应的方法即可。

接口(interface)默认方法

默认方法是接口为定义在接口中的方法提供的默认实现,使用default关键词声明,有了默认方法,接口就可以包含实现类没有提供实现的方法签名。

在前文中,多次在集合中上使用stream()方法,如inventory.stream(),但在Java 8之前, List并没有stream或parallelStream方法,它实现的Collection接口也没有,但编译器却不报错,这就是新规则“默认方法”在起作用了。

一个默认方法的示例

在Java 8里,可以直接对List调用sort方法。它是用Java 8 List接口中如下所示的默认方法实现的。

  1. default void sort(Comparator<? super E> c) {
  2. Collections.sort(this, c);
  3. }

有了这个默认方法,List的任何实体类都不需要显式实现sort,而在以前的Java版本中,除非提供了sort的实现,否则这些实体类在重新编译时都会失败。

以上就介绍完了java8中的核心新特性,那为什么java8的设计者会加入这些新特性呢?

一句话概括就是:新的语言会出现,旧语言则会被取代,除非它们不断演变,以顺应时代的发展。

展开来看,有如下两点:

  • 日新月异的计算应用场景:多核和处理大型数据集(大数据)
  • 改进的压力:函数式比命令式更适应新的体系架构

所以有了stream api,它使java开发者更简便地进行并行编程,与这个硬件多核心的时代相匹配,同时满足了如今大数据时代的需要,流比集合处理大数据时更占优势。

猜你喜欢

转载自blog.csdn.net/sdmxdzb/article/details/82742182