java8-1

公司代码使用了很多java8新特性… 在阅读代码的时候开始有点难受. 习惯了还是挺顺畅的, 为了更好的使用和理解, 找了本书看看记录一下。 本篇先对几个特性有个基本认识。

流处理

流这个概念比较抽象,以前听过输入输出流,字节字符流, java8的流跟这些不是同类。java8提供的流式api主要主要对集合对象(List,Set,Map…)使用,遍历集合,使集合对象的操作(排序、筛选、转换…)更快速、简洁。 所以Stream是类似Iterator迭代器的东西,将集合对象转换成流,然后进行一系列处理。为什么说流会高效呢? 看书上关于linux流处理的例子:

 cat file1 file2 |  sort  |  tail -3

cat 将两个文件输入连接成一个流,同时输出的流交给sort进行排序,排序处理过的流再给tail 取出最后3项。 这是流水线处理,前一程序的输出可作为下一程序的输入。不用等待结果全部产生。 来自流水线的高效. 另外还提供了并行api:parallelStream,可以很轻松的利用多cpu实现并行处理集合数据.

List<User> users = getUsers();
List<long> userIds = users.sorted(Comparator
	// 根据年龄排序
	.comparing(User::getAge())
    // 翻转
	.reversed())
    // 取前10
	.limit(10)
    // 转换成userid
	.map(Photo::getUserId())
    // 返回list
	.collect(Collectors.toList());

这是一个取排名前10的用户id的java流处理例子,可以看出来流的代码看起来很简洁明了, 同时也提高了处理效率。java8提供了很多高效的流式编程api。

lambda表达式和方法引用

行为参数化

为了提高代码的复用性。我们一般将方法部分实现抽象出来成一个接口,作为方法的参数, 方法内部再回调接口的方法来做具体的操作. 例如有一个getCountInfo方法用来对用户的不同维度(年龄,身高,体重)进行计数然后做其他的 一些统计操作, 这里的计数操作便是一个具体行为。这个行为可能有不同的实现方式, 我们把行为抽象为接口,作为方法的 参数。 就叫做行为参数化。

    // getCountInfo获取计数信息的方法, CounterInterface 接口只有一个count方法,给用户自己实现
    public int getCountInfo(List<User> users, CounterInterface counter) {
        dosometing...
        // 回调接口方法
        return counter.count(users);
    }

一般我们写出CounterInterface这个接口的多种实现,然后根据不同业务场景创建不同的对 象传入。但如果某个实现仅有一次使用, 单独创建一个类显得不划算,这时候就适合使用匿名类。

// 使用匿名类参数调用getCount方法
int resCount = getCountInfo(List<User> users, new CounterInterface(){
    // 实现count方法
    @Override
    public int count(List<User> users){
        // 求年龄总和
        int ageCount = 0;
        for(User u : users) {
            ageCount += u.age;
            // 做一些只与age相关的其他count操作
            ...
        }
        return ageCount.
    }
});

另外,我们可能根据求体重,身高之类的计数,这时候只需要实现接接口方法作为其它行为 参数传入。 如果没有行为参数化, 我们可能需要些getAgeCountInfo, getWeightCountInfo 等一系列方法来达到目的, 这些方法内部可能还有很多重复的业务代 码。

lambda表达式

当接口方法很只有很简单的几行代码时,像上面一样的匿名类写法显得有些多余, 每一个 接口实现都需要写 new xxx(){ methodname(){} } 之类的,新建对象、实现方法等代码. lambda表达式就像一个简化的匿名类:


// 使用lambda参数调用getCount方法
int resCount = getCountInfo(List<User> users, (users) -> {
        // 求年龄总和
        int ageCount = 0;
        for(User u : users) {
            ageCount += u.age;
            // 做一些只与age相关的其他count操作
            ...
        }
        return ageCount.
    }
});

lambda与匿名类的作用一样,用来传递参数化的行为,但是比匿名类更加简洁。 lambda表达式可以有参数列表,方法体,返回值,异常列表。 主要不同是匿名类会在内部独立一个作 用域,可以声明与外部同样名称的参数。 lambda则沿用外部方法的作用域。

先来看一下它的简单语法:

参数列表    箭头  lambda主体
(parameters) -> expression
(parameters) -> { statements; }

()里是参数列表,lambda主体可以是表达式或者语句: () -> return 0; , (a,b) -> return a+b; , (a) -> {int c = a; System.out.print(c);} 都是正确的表达式语句.

函数式接口

上面的getCountInfo 方法定义了一个CounterInterface接口作为形参, lambda表达式作为 参数传递,要求形参必须为函数式接口。 :) —-函数式接口就是只定义了一个抽象方法的 接口.

方法签名

抽象方法的参数和返回值是方法的签名,编译器就是根据签名来为简化的lambda做类型推断 和类型检查。

方法引用

如果lambda表达式的主体太长,会影响代码的阅读。 或者某段 lambda表达式希望重复使用。这时候可以使用方法引用。


// 使用方法引用
int resCount = getCount(users, this::myCount);

public int myCount(List<User> users){
    // 求年龄总和
    int ageCount = 0;
    for(User u : users) {
    ageCount += u.age;
    // 做一些只与age相关的其他count操作
    ...
    }
    return ageCount.
}

方法引用使复杂的lambda再次清晰,一共有三种类型的方法引用

接口默认方法

java8为了实现流等新特性,为集合接口添加了很多新方法,如在Collection接口中添加了stream()方法, 以实现java8的流。 但是很多第三方机构或者用户实现了Collection接口的工具类并没有实现这个方法,会导致编译错误。 让所有用户都来实现这些新添的方法显然不行。 于是java8让接口可以有默认方法, 由接口提供默认实现。增加了一个default关键字:

// 默认方法
default int count(List<User> users){
    return 0;
}

由于接口提供了默认方法实现,即接口的方法也有了行为。 此时一个类实现多个接口,就 会产生多继承的效果。 一个类继承多种行为。 但当两个接口有相同的方法签名,并都提供了默认实现时. 会产生c++中多继承的菱形问题, 此时会使用哪个方法呢? 定义了三点规则:

  1. 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级
  2. 如果无法依据第一条进行判断,那么子接口的优先级更高:即如果B继承了A,那么悠闲 选择B接口的默认方法。
  3. 如果还是无法判断,就必须显式地选择使用哪一个默认方法的实现, 使用 接口 名.super().方法名() 语法来显示调用.

End

本篇只是对java8的一些特性有个简单的认识, 对于每个特性,java8都提供了丰富的类库和方法, 这些需要实际使用中学习掌握. 更多知识阅读书籍了解


0

Powered by Jekyll and Theme by solid