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再次清晰,一共有三种类型的方法引用
- 指向静态方法:
Integer::parseInt
- 指向任意实例类型:
String::length
需要一个String实例对象作为参数 - 现有实例对象的的方法:
String s; s::isEmpty
接口默认方法
java8为了实现流等新特性,为集合接口添加了很多新方法,如在Collection接口中添加了stream()方法, 以实现java8的流。 但是很多第三方机构或者用户实现了Collection接口的工具类并没有实现这个方法,会导致编译错误。 让所有用户都来实现这些新添的方法显然不行。 于是java8让接口可以有默认方法, 由接口提供默认实现。增加了一个default
关键字:
// 默认方法
default int count(List<User> users){
return 0;
}
由于接口提供了默认方法实现,即接口的方法也有了行为。 此时一个类实现多个接口,就 会产生多继承的效果。 一个类继承多种行为。 但当两个接口有相同的方法签名,并都提供了默认实现时. 会产生c++中多继承的菱形问题, 此时会使用哪个方法呢? 定义了三点规则:
- 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级
- 如果无法依据第一条进行判断,那么子接口的优先级更高:即如果B继承了A,那么悠闲 选择B接口的默认方法。
- 如果还是无法判断,就必须显式地选择使用哪一个默认方法的实现, 使用
接口 名.super().方法名()
语法来显示调用.
End
本篇只是对java8的一些特性有个简单的认识, 对于每个特性,java8都提供了丰富的类库和方法, 这些需要实际使用中学习掌握. 更多知识阅读书籍了解
- 参考: 《java8 实战》 ISBN: 9787115419347
- 2018.08 看书开始写…
- 这两天才想到总结完…
- 上一篇 zookeeper-1