Java从零开始 第17讲 lambda表达式,Stream类
14年发布的Java 8 API目前仍是各大企业最为青睐的JDK版本,无论更新了新的长期稳定版本又或是发布了最新的版本,JDK8永远是不变的选择(当然也有很多公司会使用JDK7甚至6)。
使用JDK8的原因有很多,但是在本文中,我将会介绍并简单教学JDK8中两个十分重要的特性,lambda表达式和Stream类。
lambda表达式
首先让我们假设一个应用场景,有一个接口中的抽象方法需要被实现,那我们通常的方法是
// 原始的接口
interface MyIf{
void myPrint();
}
// 实现接口,重写其中抽象方法
class MyClass implements MyIf{
@Override
public void myPrint(){
System.out.println(2);
}
}
// 调用该方法
// MyClass test = new MyClass();
// test.myPrint(); // 标准流程
new MyClass().myPrint(); // 简要版
如果我们只需要使用一次这个方法(或者次数比较少),继承接口和重写方法这些流程看下来实在是有些复杂,现在让我们用lambda的方式来实现这个功能
// 原始的接口
interface MyIf{
void myPrint();
}
// 实现接口,重写其中抽象方法
MyIf test = () -> System.out.println(2);
// 调用该方法
test.myPrint();
使用了lambda之后整个代码量就下降了许多,而且实现和调用的代码可以放在一起,不需要翻看具体的实现语句了
现在让我们正式介绍一下lambda表达式,lambda表达式的目的是为了让代码更优美,其完整表达式如下
- (parameters) ->{ statements; }
lambda使用也有一些规则:
- 如果statement只有一句,那么后面的大括号可以省略
- 必须实现接口(即含有抽象方法的抽象类不行),接口中有且只能有一个抽象的方法
- 参数列表可以不用写明参数类型
让我们再写两个简单的例子来加深一下印象
class Scratch {
public static void main(String[] args) {
InnerIf1 test1 = (a) -> System.out.println(a*a);
InnerIf2 test2 = (a, b) -> {
a=a*2;
return a+b;
};
test1.lambdaMulti(2);
System.out.println( test2.lambdaAdd(2,2) );
}
// 内部接口
interface InnerIf1{
void lambdaMulti(int i);
}
interface InnerIf2{
int lambdaAdd(int a, int b);
}
}
// 运行结果
4
6
Process finished with exit code 0
lambda表达式十分常用的一个场景就是实现多线程的Runnable接口,在这里我就不举例子了。
双冒号调用方法
在lambda表达式被提出的同时,也推出了一种新的方法调用方式,即双冒号 :: ,使用双冒号能够直接调用类中的方法,可以帮助lambda进一步简化代码
同样让我们用几个简单的例子实用一下
// 使用形式
类名::方法名 // 对于静态方法
对象名::方法名 // 对于非静态方法
new 类名::方法名 // 匿名调用
super::父类方法名 // super调用
类名::new // 构造方法调用
System.out::println // 调用println方法,注意此方法为静态的
new Object()::equals // 调用Object类中的equals方法
但是注意,这些方法并不能如此直接使用,而要和lambda表达式一同使用,或者配合其他方法使用,如下对于一个简单加的运算可以使用Integer中的sum方法替代
class Scratch {
public static void main(String[] args) {
// InnerIf ifImpl = (i, j) -> i+j; // 原语句
InnerIf ifImpl = Integer::sum;
System.out.println(ifImpl.add(2,2));
}
interface InnerIf{
int add(int i, int j);
}
}
另一个常用的场景是对Collection接口下的每一个元素进行操作,如下是输出每一个元素
// 下两行不会输出结果,因为myList是空的
List<Integer> myList = new ArrayList<>();
myList.forEach(System.out::println);
// 这里可以正常输出
List<Integer> myList = Arrays.asList(3, 2, 1);
myList.forEach(System.out::println);
Stream类
在学完了lambda表达式和双冒号调用后,让我们来学习JDK8中另一个重要的新特性,那就是Stream/流。
之所以要在流之前学习lambda,是因为在流中将会大量使用lambda表达式来简化代码,如果看到这里但是还没有完全弄清楚lambda的使用,建议再多温习几遍上面的内容再继续看下去。
首先让我们定义一下流,流是一个来自数据源的元素队列,通常用于在类集中执行较为复杂的查找和过滤等操作。其中数据源通常是Collection接口下的类集(list和set),元素队列即是该类集中的元素。
// 一个普通的链表
List<Integer> myList = Arrays.asList(3, 2, 1);
// 得到串行流
myList.stream();
// 得到并行流
myList.parallelStream();
流不会存储数据,所以在获取到流之后,需要对最终的结果进行存储,其间可以对流中的元素进行任意的操作。同样使用例子来简要说明一下
List<Integer> myList = Arrays.asList(3, 2, 1, 2, 3);
// 不进行任何操作
List<Integer> myList2 = myList.stream().collect(Collectors.toList());
// 将每一个元素平方
List<Integer> myList3 = myList.stream().map(x -> x*x).collect(Collectors.toList());
// 仅保留小于2的元素
List<Integer> myList4 = myList.stream().filter(x -> x<2).collect(Collectors.toList());
System.out.println(myList2);
System.out.println(myList3);
System.out.println(myList4);
// 运行结果
[3, 2, 1, 2, 3]
[9, 4, 1, 4, 9]
[1]
Process finished with exit code 0
通过方法的叠加也可以定义复杂一些的方法,在stream中可以夹杂许多方法,它们的结构并不复杂,我们就不一一展示了,此处仅写一些基础的常用方法
List<Integer> myList = Arrays.asList(7, 2, 1, 4, 3, 5, 2);
List<Integer> myList2 = myList.stream()
.map(x -> x*3) // 每一个元素*3
.filter(x -> x<15) // 保留小于15的元素
.sorted() // 从小到大排列
.distinct() // 去除重复结果
.limit(5) // 最多5个元素
.collect(Collectors.toList());
System.out.println(myList2);
// 运行结果
[3, 6, 9, 12]
Process finished with exit code 0
除此之外也可以使用forEach和count等方法来结尾,以应对多样的需求
List<Integer> myList = Arrays.asList(7, 2, 1, 4, 3, 5, 2);
long count = myList.stream().count(); // 计数
String str = myList.stream()
.map(x -> ""+x) // 需要将其先转为string类型
.collect(Collectors.joining(", "));
IntSummaryStatistics statistics = myList.stream()
.mapToInt(x -> x) // 为了通过编译器审核
.summaryStatistics(); // 得到统计数据
// 统计数据主要用于int、double、long等基本类型上
// 输出每一个元素
myList.stream().forEach(System.out::print);
System.out.println("\n" + count);
System.out.println(str);
System.out.println("max="+ statistics.getMax()+
"\nmin="+ statistics.getMin()+
"\navg="+ statistics.getAverage());
// 运行结果
7214352
7
7, 2, 1, 4, 3, 5, 2
max=7
min=1
avg=3.4285714285714284
Process finished with exit code 0
转载请注明出处
上一篇: 三个数排序