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

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

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

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

java8以前的做法:

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

 

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

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

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

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

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

函数式编程

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

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

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

/**
*过滤出绿色苹果
*/
public static List<Apple> filterGreenApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
//颜色等于green则加入筛选结果中
    if ("green".equals(apple.getColor())) {
        result.add(apple);
    }
   }
    return result;
}

/**
*过滤出重量大于150g的苹果
*/
public static List<Apple> filterHeavyApples(List<Apple> inventory){
List<Apple> result = new ArrayList<>();
for (Apple apple: inventory){
    //重量是否大于150g则加入筛选结果中
    if (apple.getWeight() > 150) {
        result.add(apple);
        }
    }
return result;
}

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

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

public static boolean isGreenApple(Apple apple) {
    return "green".equals(apple.getColor());
}
public static boolean isHeavyApple(Apple apple) {
    return apple.getWeight() > 150;
}
/**
*java8里的谓词,在此列出是为了看得更清晰,可能从java.util.function包中直接导入
*/
public interface Predicate<T>{
    boolean test(T t);
}
/**
*通用的过滤方法
*@param inventory
*@param p 方法作为Predicate参数p传递进去
*/
static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple: inventory){
    //苹果符合p的条件即加入筛选结果中
    if (p.test(apple)) {
        result.add(apple);
        }
    }
    return result;
}

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

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

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

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

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

Lambda表达式

前文中把方法作为值来传递显然很有用,但要是为类似于isHeavyApple和isGreenApple这种可能只用一两次的短方法写一堆定义有点儿烦人。

于是java8引入了一套新\写法(匿名函数或Lambda),因此,过滤绿苹果与较重苹果的代码可以写成:

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

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

在前面文章中那个过滤苹果的方法,我们用Lambda表达式实现的如下:

 List<Apple> greenApples = filter(inventory, (Apple a) -> "green".equals(a.getColor()));
  1. 那么为什么能这样使用呢?filter方法的第二个参数实际上是一个函数式接口Predicate<T>,它的定义如下:
 public interface Predicate<T>{
    boolean test (T t);
}

函数式接口就是只定义一个抽象方法的接口

实际上,在java8以前,我们可能已经接触过下面这些函数式接口:

 //java.util.Comparator
public interface Comparator<T> {
    int compare(T o1, T o2);
}
//java.lang.Runnable
public interface Runnable{
    void run();
}
//java.awt.event.ActionListener
public interface ActionListener extends EventListener{
    void actionPerformed(ActionEvent e);
}
//java.util.concurrent.Callable
public interface Callable<V>{
    V call();
}
//java.security.PrivilegedAction
public interface PrivilegedAction<V>{
    V run();
}

Predicate<T>是java8中定义的一个新的函数式接口.

Lambda表达式允许我们以内联的方式实现一个函数式接口(Lambda表达式本身就是函数式接口的一个实例).

如下,我们即可以以函数式接口的方式,也可以以匿名类的方式传递runnable的实体,它们的实现效果相同,只是匿名类更为烦锁而已:

 //使用Lambda实例化runnable
Runnable r1 = () -> System.out.println("Hello World 1");
//使用匿名类,实例化runnable
Runnable r2 = new Runnable(){
public void run(){
    System.out.println("Hello World 2");
}
};
public static void process(Runnable r){
    r.run();
}
process(r1);
process(r2);
//直接传递Lambda表达式
process(() -> System.out.println("Hello World 3"));

从上面的代码中可以看出,Lambda表达式可以赋值给一个变量,也可以作为参数直接传递给以函数式接口为参数的方法,前提是Lambda表达式的签名要和函数式接口的抽象方法一样,这个抽象方法也叫做函数描述符,java编译器就是根据上下文来匹配函数描述符来判断Lambda表达式是否合法.

Lambda表达式在环绕执行模式(execute around)中的应用

通俗来讲,环绕执行模式(execute around)是指代码执行的首尾都是模版代码,真正重要的代码在中间部分执行,比如文件处理:先是打开文件,然后对文件进行处理,最后释放资源,这里我们关注的重点是对文件的处理,而不是打开文件与资源的释放.

而文件的处理,因为需求的不同会有所区别,所以Lambda表达式特别适合环绕执行模式的代码开发,像打开文件与释放资源这种一成不变的模版代码写在方法内,不同需求对应的不同行为以Lambda的形式传递进去。

代码如下:

 //定义函数式接口来传递处理文件的行为
@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}
//定义处理文件的方法(环绕执行模式)
public static String processFile(BufferedReaderProcessor p) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    return p.process(br);
}
}

//传递Lambda:读取一行
String oneLine = processFile((BufferedReader br) -> br.readLine());

//传递Lambda:读取两行
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());

 

流(stream)

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

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

java8以前Collection API实现:

//新建一个hashmap,用于存放筛选结果
Map<Currency, List<Transaction>> transactionsByCurrencies=new HashMap<>();
//遍历交易列表
for (Transaction transaction : transactions) {
    //筛选出金额较高的交易
    if(transaction.getPrice() > 1000){
        //获取交易的货币
        Currency currency = transaction.getCurrency();
        //获取该货币的交易分组
        List<Transaction> transactionsForCurrency=transactionsByCurrencies.get(currency);
        //若该货币的交易分组为空,则新建一个
            if (transactionsForCurrency == null) {
                transactionsForCurrency = new ArrayList<>();
                transactionsByCurrencies.put(currency,transactionsForCurrency);
                }
            //将交易加入对应分组List
            transactionsForCurrency.add(transaction);
        }
}

java8中Stream Api的实现:

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

Map<Currency, List<Transaction>> transactionsByCurrencies =

transactions.stream()//从交易列表中生成流

.filter((Transaction t) -> t.getPrice() > 1000)//过滤出金额较高的交易

.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支持许多处理数据的并行操作,且使用相当简单,如:
    "我们只需将stream()方法改成parallelStream()即可。
import static java.util.stream.Collectors.toList;
List<Apple> heavyApples = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150).collect(toList());
  • ">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接口中如下所示的默认方法实现的。

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

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

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

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

展开来看,有如下两点:

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

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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值