Java8手记
一、Lambda表达式
1.1 Lambda表达式是什么?
Lambda表达式是Java8的最大特色。简单的Lambda表达式可以清晰的表达很多复杂的编程目的。Lambda表达式类似于如下表示格式:x->x+1。
其中x是一个没有参数类型的参数名,x+1为表达式,这里也可以用大括号括起来的代码块代替。他们之间使用->右箭头连接。
1.2 Lambda表达式可以做什么?
了解Lambda表达式可以做什么,也就是告诉了我们为什么要去学习Lambda表达式的原因。
其一,使用Lambda表达式,可以更清晰的表示程序意图。通常此类例子我们都以swing开发中的ActionListener接口的actionPerform方法作为说明。此处我们换用Runnable方法的run进行说明。如:
Runnable noArgs = () -> System.out.println(“无参调用”);
也许此处,您会觉得使用匿名函数同样可以达到效果,的确。对于简单的表达式来说,同种功能的匿名函数结果一致,阅读起来也不太费劲。但是细心的您能发现,这里表达式直接可以指出功能核心的实现,而匿名函数需要一堆“格式”,并且需要找到其中的功能核心部分。这点,就突出了Lambda表达式的优势。如:
Runnable noArgs = new Runnable(){
public void run()
{
System.out.println(“无参调用”);
}
};
其二,使用Lambda表达式,可以使用Java8新引入的流操作,而告别以往需要多个for循环迭代的低效模式。如:针对于迭代寻找一个学生集合中的男生数量。我们先定义一个简单的学生类Student。这个类具有一个整形的sexual成员变量,0表示男性,1表示女性。不使用流操作的时候,我们通常这么写:
List<Student> students = getData(); //假设这里getData()方法用于获取学生集合
int count = 0;
for(Student stu:students)
{
if(stu.getSexual() == 0)
{
count ++;
}
}
System.out.println("班里共有"+count+"个男生。");
而使用流操作时,通常只用几步就可以筛选出结果。如下:
List<Student> students = getData(); //假设这里getData()方法用于获取学生集合
int count = students.stream()->filter(stu -> stu.getSexual() == 0).count();
System.out.println("班里共有"+count+"个男生。");
这里可以看到,使用流操作不仅简明易懂,而且减少了代码量。不仅如此,对于流的内部实现来说,在调用count()方法前,实际上还没有开始内部迭代。当调用count()方法后,流才开始迭代,并且仅迭代一次。单独一层的循环可能无法突显出这种优势,当多层循环嵌套时,就可以明显看出差异了。
二、书写Lambda表达式
Lambda表达式可以直接书写,形如:
Runnable noArguments = ()->System.out.println();
也可以带有一个参数:
ActionListener oneArguments = event -> System.out.println("Button clicked");
也可以带有一个代码块:
Runnable multiStatement = () -> {
System.out.println(1);
System.out.println(2);
};
也可以返回一个参数:
BinaryOperator<Long> add = (x,y) -> x + y;
也可以显示指明参数类型:
BinaryOperator<Long> addExplicit = (Long x,Long y) -> x + y;
三、流操作
3.1 基本操作
流操作相当于将数据集合放入一个流中,根据流的一些条件过滤或者映射为不同的内容,最后迭代出结果。其中,流操作的方法有两类,一类为惰性求值方法,一类为及早求值方法。
其中惰性求值可以理解为,在流中加入的一些限制条件,这些限制条件并不引发流的迭代求值操作。及早求值可以理解为,将流启动,引发流的迭代操作并获得流的最终结果。
下表中归纳了流操作的常用方法。
方法签名 | 类型 | 用法示例 | 特点 |
Stream<T> filter(Predicate<? super T> predicate) 筛选出符合条件的流。 | 惰性 | 示例3.1 | 筛选 |
Stream<T> distinct() 过滤掉重复元素后的流。 | 惰性 | 示例3.2 | 筛选 |
Stream<T> limit(long maxSize) 将流按照最大长度截断。 | 惰性 | 示例3.3 | 筛选 |
Stream<T> skip(long n) 返回扔掉前n个元素后的流。 | 惰性 | 示例3.4 | 筛选 |
<R> Stream<R> map(Function<? super T,? extends R> mapper) 将原流中的元素按照某种方法(mapper)映射为新的流。流中的原内容会根据mapper映射为新的内容。 | 惰性 | 示例3.5 | 映射 |
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper) 如果源流中某一个变量含有多个元素(数组、列表等),则这个方法会将源流映射为含有这种多个元素项的合并集合。 | 惰性 | 示例3.6 | 映射 |
Stream<T> sorted() 返回按照自然顺序排序后的流。 | 惰性 | 示例3.7 | 排序 |
boolean anyMatch(Predicate<? super T> predicate) 查找流中是否至少包含一个符合条件的元素。 | 及早 | 示例3.8 | 查找 |
boolean allMatch(Predicate<? super T> predicate) 查找流中所有元素是否都匹配某条件。 | 及早 | 示例3.9 | 查找 |
boolean noneMatch(Predicate<? super T> predicate) 查找流中无元素与条件相匹配。 | 及早 | 示例3.10 | 查找 |
Optional<T> findAny() 返回流中的元素。 | 及早 | 示例3.11 | 查找 |
Optional<T> findFirst() 返回流中第一个元素。 | 及早 | 示例3.12 | 查找 |
T reduce(T identity, BinaryOperator<T> accumulator) Optional<T> reduce(BinaryOperator<T> accumulator) <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner) 此方法用于进行迭代求值,并返回迭代结果。共有3中重载方法。 | 及早 | 示例3.13 | 归约 |
<R,A> R collect(Collector<? super T,A,R> collector) 把流归约成一个集合。 | 及早 | 示例3.14 | 归约 |
Optional<T> max(Comparator<? super T> comparator) 查找出流中的最大的元素。 | 及早 | 示例3.15 | 归约 |
Optional<T> min(Comparator<? super T> comparator) 查找出流中最小的元素。 | 及早 | 示例3.16 | 归约 |
long count() 返回流中元素的数量和。 | 及早 | 示例3.17 | 归约 |
void forEach(Consumer<? super T> action) 遍历流中的元素,并做出针对于每个元素的具体操作。 | 及早 | 示例3.18 | 遍历 |
3.2 Optional类
在操作列表中,涉及到了一个Optional类。因为null关键字带来的诸多问题,在Java8中使用了一个Optional类。这个类用于优雅的代替null。
使用工厂方法of,可以从某个值创建出一个Optional对象。
使用工厂方法empty,可以创建一个空的Optional对象。
使用工厂方法ofNullable,可以创建一个允许为空的Optional对象。
可以通过isPresent()方法判断值是否为空。
使用get()方法获取Optional对象里的值。
为了更加简洁,通常在使用时,使用orElse或者orElseGet方法。当Optional对象没有值时,会提供一个备选值。
3.3 收集器
收集器用作一种高级归约,将流中的数据转换为需要的的数据集合类型。下面列出了所有收集器:
工厂方法 | 返回类型 | 用于 |
toList | List<T> | 把流中所有的项目收集到一个List中。 |
toSet | Set<T> | 把流中所有的项目收集到一个Set中,删除重复项。 |
toCollection | Collection<T> | 把流中所有的项目收集到指定的供应源创建的集合。 |
counting | Long | 计算流中元素的个数。 |
summingInt | Integer | 对流中项目的一个整数属性求和。 |
averagingInt | Double | 计算流中项目具有Integer属性的平均值。 |
summarizingInt | IntSummaryStatistics | 收集关于流中项目Integer属性的统计值。如最大、最小、总和与平均值。 |
joining | String | 连接对流中每个项目调用toString()方法生成的字符串。 |
maxBy | Optional<T> | 返回流中按照给定比较器选出的最大元素Optional。 |
minBy | Optional<T> | 返回流中按照给定比较器选出的最小元素Optional。 |
reducing | 归约类型 | 从一个做为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而将流归约为单个值。 |
collectingAndThen | 转换函数返回类型 | 包裹另一个收集器,对其结果应用转换函数。 |
groupingBy | Map<K,List<T>> | 根据项目一个属性的值对流中的项目分组,并将属性值作为结果Map的键。 |
partitioningBy | Map<Boolean, List<T>> | 根据对流中每个项目应用谓词的结果来对项目进行分区。 |
mapping | 映射函数返回类型 | 将收集器中的元素进行映射 |
四、默认方法
默认方法是在接口中,用default关键字标识的一种方法。如:
public interface Readable
{
public default void sing()
{
System.out.println("Start!");
}
}
接口的默认方法会带来多重继承等问题,因此这里记录下使用原则:
- 类胜于接口。
- 子类胜于父类。
- 其他。(解决方法:要么子类实现该方法;要么将该方法声明为抽象方法)
五、并行化
Java8中的并行化仅仅是对一个Stream对象,调用其parallel方法或集合创建流时,调用parallelStream代替stream方法即可。
虽然用法很简单,但是决定何时用并行化何时用串行化并不简单。并行化并不一定会提升效率。在处理大量(几万以上)数据时,用并行化也许会提升计算效率。否则一般来说串行化的计算效率是高于并行化的。并且并行化的效率提升有一个限值,根据阿姆达尔定律,并行化最大能提升到2倍速度。
同学们可以下载本手记附属的代码,依次运行,获得更深刻的体会。点我下载示例代码。