JAVA8实战 -(1)

JAVA致力于让并发编程更容易,出错更少,JAVA5添加了线城池和并发集合,JAVA7添加了fork、join框架

1. JAVA8中有哪些新的设计

  • Stream API,支持处理数据的并行操作,避免用synchronized编写代码
  • 向方法传递代码,行为参数化,函数式编程
  • 接口中的默认方法

流处理:流处理实际上是一个序列,但是不同加工过程一般是并行的,基于这一思想,JAVA8中在java.util.stream中添加了Stream API;Stream<T>可以看做比较花哨的迭代器。JAVA8可以透明的把输入的不相关的部分拿到几个CPU内核上去执行操作流水线。

行为参数化:即把你的代码传递给函数。

并行与共享的可变数据:如果使用流进行并行操作,那么就意味着写代码时不能访问共享的可变数据(因为并行操作会有不确定性),这些函数成为“纯函数”,“无副作用函数”或“无状态函数”。

没有共享的可变数据,即将代码传递给其他方法的能力是函数式编程的基石。

2. Java中的函数

2.1 将函数作为值

函数一般指方法,尤其是静态方法。在JAVA中值是一等公民,类和方法是二等公民,JAVA8致力于将二等公民方法变为一等公民值。

方法引用

与对象引用类似,下面例子是找出目录下的隐藏文件:

File hiddenFiles = new File(".").listFiles(new FileFilter() {
    public boolean accept(File file) {
        return file.isHidden();
    }
});//JAVA8以前还要new 一个对象,实现方法。

JAVA8中

File hiddenFiles = new File(".").listFiles(File::isHidden);

因为isHidden()方法已经实现过了,所以我们可以直接通过引用的方法使用。只要方法中有代码(方法中的可执行部分),用方法引用就可以传递代码。

Lambda--匿名函数

顾名思义就是没有名字的函数,简单的例如(int x) -> x+1,表示“调用时给定参数x,就返回x+1值的函数”。

传递代码的例子

从一个例子,我们来学习JAVA8如何帮我们更好的写程序:

如果我们有一个苹果Apple列表用inventory表示,我们希望得到一个返回苹果颜色的红色的列表,那么应该这样写,

public static List<Apple> filterRedApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if("red".equals(apple.getColor())){
            result.add(apple);
        }
    }
    return result;
}

那如果我们还想筛选出重量大于150g的苹果该怎么办呢,按照JAVA8以前的写法,我们会粘贴复制filterRedApples()的代码,然后修改条件。显然这样不符合代码的高可用性。所以我们理想的是编写一个筛选的模板,只需要把不同的条件传递进去就可以。也就是前面我么提到过的将代码作为参数传递给方法。

public static boolean isRedApple(Apple apple){  //颜色的条件代码
    return "red".equals(apple.getColor);
}
public static boolean isHeavyApple(Apple apple){  //重量的条件代码
    return apple.getWeight() > 150;
}
public interface Predicate<T>{
    boolean test(T t);
}
public static List<Apple> filterApples(List<Apple> inventory, Predicate<Apple> p) {
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(p.test(apple)){
            result.add(apple);
        }
    }
    return result;
}
// 使用时只需要传递函数名,注意不是函数。
filterApples(inventory, Apple::isRedApple);
filterApples(inventory, Apple::isHeavyApple);

即使这样提高了代码的复用性,但是我们还是会感觉到,因为一行条件代码还需要去创建一个方法去传递它,有没有更见的方法去传递代码呢?JAVA8还引入了一套新记法去解决这个问题,那就是匿名函数或Lambda。你可以简单地把需要传递的代码直接写进去。

filterApples(inventory, (Apple apple) -> "red".equals(apple.getColor()));
filterApples(inventory, (Apple apple) -> apple.getWeight() > 150);

2.2 流

Stream API

流主要是针对集合的处理而设计,因为几乎每一个JAVA应用都会制造和处理集合,但集合用起来总不是那么理想,比如如果你想在一个列表中筛选出金额比较高的交易,然后按货币分组,则需要写一大堆代码,如下所示:

Map<Currency, List<Transaction>> tracsactionsByCurrencies = new HashMap<>();
for(Transaction transaction: transactions) {
    if(transaction.getPrice() > 1000) {  //筛选较高金额的交易
        Currency currency = transaction.getCurrency();
        List<Transaction> transactionsByCurrency = tracsactionsByCurrencies.get(currency);  //获取当前货币的所有高金额列表
        if(transactionsByCurrency == null){ //如果该货币没有高金额交易,新建列表,并添加至集合。
            transactionsByCurrency = new ArrayList();
            transactionsByCurrencies.put(currency, transactionsByCurrency);
        }
        transactionsByCurrency.add(transaction); //将该交易添加到列表中去
    }
}

显然这是十分复杂的,那么通过JAVA8的Stream API,我们可以这样解决问题了。

import java.util.stream.Collectors.toList;
Map<Currency, List<Transaction>> transactionsByCurrencies = transactions.stream()
    .filter((Tracsantion tracsaction) -> transaction.getPrice() > 1000)
    .collect(groupBy(Transaction::getCurrency));

看起来十分简单,先通过filter筛选出金额较高的交易,然后按货币分组,Stream API提供了许多对开发者友好的API。它与Collection API十分不同,Collection API是得自己去迭代使用for-each,我们称之为外部迭代,而Stream API是内部迭代,使用者不用操心循环的事情,完全是在内库进行的。到这里,我们直说了新的Stream API与现有的JAVA集合API行为差不多,它们都能访问数据的序列,但是Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的计算。

JAVA中的多线程

通过多线程来实现并行并非易事,JAVA8基于Stream的并行提倡尽量少使用synchronized的函数式编程风格,它关注数据块而不是协调访问。JAVA8主要用Stream API解决了两个问题:

1. 集合处理时的套路与晦涩,以及难以利用多核;

2. 让一些操作可以并行化,例如filter;

再回到刚才我们筛选苹果的例子中,我么将体验一下利用Stream和Lamba表达式顺序或并行从一个列表中筛选比较重的苹果。

顺序处理:

List<Apple> result = inventory.stream()
    .filter((Apple apple) -> apple.getWeight() > 150)
    .collect(toList());

并行处理:

List<Apple> result = inventory.parallelStream()
    .filter((Apple apple) -> apple.getWeight() > 150)
    .collect(toList());

2.3 默认方法

默认方法其实是为了支持库数据师,让他们能够写出更容易改进的接口。我们举一个简单地例子来说明:

我们之前使用了List.stream()和List.parallelStream(),但是JAVA8以前的List<T>以及Collection<T>接口里面没有这个方法,那么就需要把这些方法加到Collection<T>接口,但这对使用Collection接口的开发者无疑是噩梦,因为所有实现Collection<T>接口的类都必须提供一个实现,所以如何保证改变已发布的接口而不破坏已有的实现呢?JAVA8的解决办法是添加默认方法,即接口可以包含实现类没有提供实现的方法签名了。

这给接口设计者提供了一个扩充接口的方式,而且不会破坏现有的代码,JAVA8使用新的default关键字来表示。例如,你可以直接使用List.sort(),而不必使用之前的Collection.sort(list, comparator)。

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

不过有一个问题需要思考,一个类可以实现多个接口,如果在好几个接口理实现多个默认实现,那是不是就会有某种程度上的多重继承?JAVA8中会有限制来避免类似于C++中臭名昭著的菱形继承问题。有兴趣的可以自行了解。

2.4 函数编程的其他好思想

Optional<T>类

它是一个容器类,包含的内容可以是空的也可以是一个值。如果你能一致的使用它,它会帮你避免NullPointerException。

(结构)模式匹配

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值