Lambda与Stream简介
一、 Lambda表达式
1. 入门实例
先来看一个简单的例子,通过实现Runnable接口的方式定义线程
new Thread(new Runnable(){
public void run(){
System.out.println(“Lambda”);
}
}).start();
在java8中可以简化上面的程序,而简化的关键就是使用lambda表达式:
new Thread(()->System.out.println(“Lambda”)).start();
2. 哪里使用Lambda表达式–函数式接口
a) 定义
函数式接口的定义:只定义一个抽象方法的接口
例如上面我们的示例Runnable接口就是一个函数式接口:
public interface Runnable{
void run();
}
b) @FunctionalInterface 简介
我们通常会在函数式接口的接口定义上加入@FunctionalInterface注解,表示这个接口是一个函数式的接口,如果我们接口定义不满足函数式接口的定义时,编译器将返回一个提示原因的错误
c) 函数式接口与Lambda结合使用
进一步了解函数式接口与Lambda表达式:
@FunctionalInterface
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
public static String processFile(BufferedReaderProcessor p) throws IOException {
…
}
上面我们定义了一个函数式接口,接口接收一个BufferedReader对象,整个process应该是根据输入流读取内容做一些处理。
我们还定义了一个processFile的静态方法,它接收一个与函数式接口定义相同的对象。
下面我们来补全上面的静态方法:
public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(“data.txt”))) {
return p.process(br);
}
}
静态方法中我们定义了一个输入流对象,读取的文件为data.txt,而返回的内容则是交给函数式接口的方法调用返回来决定。
在java8 以前,让我们来调用这个静态方法,我们首先可能会想到的是使用匿名内部类的方式来完成调用,如下:
processFile(new BufferedReaderProcess(){
public String process(BufferedReader b) throws IOException{
return br.readLine() + br.readLine()
}
});
Java8 使用Lambda表达式来实现的方式则简洁很多,如下:
processFile((BufferedReader br) -> br.readLine() + br.readLine())
由于Lambda表达式提供参数的类型检查和类型推断,所以我们还可以继续精简:
processFile(br -> br.readLine() + br.readLine())
上面由于整个操作一行代码完成,所以我们无需指定返回,默认就返回这行代码生成的值,等价于:processFile(br ->{return br.readLine() + br.readLine()}) 注意大括号不要漏了。
同样的如果存在多行逻辑处理代码,则需要用{}写成代码块的形式处理并显示指定return语句。
d) Java8提供的函数式接口简介
到这里应该简单了解了函数式接口的定义与Lambda表达式的结合使用了吧,实际java8已经为我们定义了许多可用的函数式接口供给我们应对不同的场景使用,例如最简单的三个常用的函数式接口:Predicate,Consumer,Function
定义分别如下:
@FunctionalInterface
public interface Predicate<T>{
boolean test(T t);
}
返回boolean
@FunctionalInterface
public interface Consumer<T>{
void accept(T t);
}
无返回值
@FunctionalInterface
public interface Function<T, R>{
R apply(T t);
}
传入T返回R
函数式接口 |
函数描述符 |
原始类型特化 |
Predicate<T> |
T->boolean |
IntPredicate,LongPredicate, DoublePredicate |
Consumer<T> |
T->void |
IntConsumer,LongConsumer, DoubleConsumer |
Function<T,R> |
T->R |
IntFumction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction<R>, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<T>, ToDoubleFunction<T>, ToLongFunction<T> |
Supplier<T> |
()->T |
BooleanSupplier,IntSupplier, LongSupplier,DoubleSupplier |
UnaryOperator<T> |
T->T |
IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> |
(T,T)->T |
IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L,R> |
(L,R)->boolean |
|
BiConsumer<T,U> |
(T,U)->void |
ObjIntConsumer<T>, ObjLongCosumer<T>, ObjDoubleConsumer<T> |
BiFunction<T,U,R> |
(T,U)->R |
ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U> |
3. 方法引用
上面我们初步了解了Lambda表达式,下面我们来介绍一下java8中经常看到的另一个功能:方法引用,它经常用来进一步简化Lambda表达式
a) 方法引用简单实例
先来看一个结合排序的例子:
List<Person> persons = new ArrayList<Person>();
Persons.add(…)
现在我们需要按照人员姓名来排序,我们可以调用集合的sort的方法,传入一个Compartor接口,利用Lambda表达式我们可以写成如下实现:
persons.sort((Person first,Person second) -> a1.getName().compareTo(a2.getName())));
那有没有更加简单的写法呢,这里就要用到方法引用:
persons.sort(comparing(Person::getName));
对于comparing方法不了解的同学,请先去了解一下其实我们这边用的是comparing中的
comparing(Function<? super T, ? extends U> keyExtractor)
Function方法是一个T->R的模型实际上面Person::getName相当于 p->p.getName() 再让大家看的明白一些就相当于 (Person p) -> p.getName()
b) 创建方法引用
那么我们需要如何来创建方法引用呢,下面列出了方法引用创建的几种方式:
(1) 指向静态方法引用
(2) 指向任意类型实例方法的方法引用
(3) 指向现有对象的实例方法的方法引用
Lambda |
方法引用 |
(args)->ClassName.staticMethod(args) |
ClassName::staticMethod |
(arg0,rest)->arg0.instanceMethod(rest) |
ClassName::instanceMethod (arg0是className类型的) |
(args)->expr.instanceMethod(args) |
expr::instanceMethod |
除了上面的还有一些针对构造函数等一些特殊形式的方法引用
例如:
Supplier<Person> c1 = Person::new;
Person a1 = c1.get();
这个就等价于:
Supplier<Person> c1 = () -> new Person();
Person a1 = c1.get();
二、Stream流简介
上面介绍了函数式接口,Lambda表达式,下面介绍流
流的定义:流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。
1. 入门实例
先来看一个简单的示例:
List<Person> persons = new ArrayList<>();
Java7以前:
遍历所有的人员对象
for(Person person : persons){
String name = person.getName();
int age = person.getAge();
}
找出年龄小于30岁的人
List<Person> rs = new ArrayList<>();
for(Person person : persons){
int age = person.getAge();
if(age < 30){
rs.add(person);
}
}
获取所有人员的名称
List<String> names = new ArrayList<>();
for(Person person : persons){
names.add(person.getName());
}
按照年龄排序
Collections.sort(persons, new Comparator<Person>(){
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
以上是我们在List集合处理过程中常用的一些方法,java8中我们可以用流的方式来处理和遍历集合,如下:
遍历所有元素:
persons.stream().forEach(person -> {…});
找出年龄小于30岁的人
List<Person> rs = persons.stream().filter(person -> person.getAge() < 30).collect(toList());
获取所有人员的名称
List<String> names = persons.stream().map(person -> person.getName()).collect(toList());
按照年龄排序
List<Person> rs = persons.stream().sorted(comparing(Person::getAge)).collect(toList());
当然上面的方法是可以以链式调用的方式组合使用的,例如我要查找年龄小于30岁的所有的姓名:
List<String> names = persons.stream().filter(person -> person.getAge() < 30).map(person -> person.getName()).collect(toList());
2. Stream中间、终端操作简介
上面简单介绍了流的一些基本使用法法,现在我们来详细介绍一下流操作,整个流从输入数据到输出结果,我们可以分为三类:
(1) 一个数据源(如集合)来执行一个查询
(2) 中间操作,用来做一些数据过滤和塞选等,可以形成一条操作链,形成一条流的流水线
(3) 终端操作,用来输出或者返回最终的结果
操作 |
类型 |
返回类型 |
使用的类型/函数式接口 |
函数描述符 |
filter |
中间 |
Stream<T> |
Predicate<T> |
T->boolean |
distinct |
中间 (有状态–无界) |
Stream<T> |
|
|
skip |
中间 (有状态–有界) |
Stream<T> |
long |
|
limit |
中间 (有状态–有界) |
Stream<T> |
long |
|
map |
中间 |
Stream<R> |
Function<T,R> |
T->R |
flatMap |
中间 |
Stream<R> |
Function<T,Stream<R>> |
T->Stream<R> |
sorted |
中间 (有状态–无界) |
Stream<T> |
Compartor<T> |
(T,T)->int |
anyMatch |
终端 |
boolean |
Predicate<T> |
T->boolean |
noneMatch |
终端 |
boolean |
Predicate<T> |
T->boolean |
allMatch |
终端 |
boolean |
Predicate<T> |
T->boolean |
findAny |
终端 |
Optional<T> |
|
|
findFirst |
终端 |
Optional<T> |
|
|
forEach |
终端 |
void |
Consumer<T> |
T->void |
collect |
终端 |
R |
Collector<T,A,R> |
|
reduce |
终端 (有状态–有界) |
Optional<T> |
BinaryOperator<T> |
(T,T)->T |
count |
终端 |
long |
|
|
3. flatMap简介
流的扁平化,下面我们来看一个例子,现在我们有一个存储英文语句的List<String>对象 其中每个String对象存储的值为:this is my first program 类似的语句,现在我们需要输出一个List<String> 结果为上面英文语句所有的单词对象,我们按照上面所学来编写的时候写出如下语句:
sentences.stream().map(s -> s.split(“ ”)).distinct().collect(toList());
最后我们发现上面的程序并不能满足我们需求,他返回的结果是一个List<string[]>对象,为什么呢,因为我们第一个map返回的是一个split出来的String[]数组对象,而实际我们希望这个数组中所有元素都当成一个独立的String对象流返回到我们后一步去重的操作中,此时我们就需要引入flatMap 扁平化流,具体代码如下:
sentences.stream().flatMap(s -> s.split(“ ”)).distinct().collect(toList());
这样我们就得到了我们想要的结果,单独去看输出我们也会发现上面的最后输出的是List<String>不再是上面的List<String[]>
在实际的开发过程中使用到flatMap的地方还是很多的,好好花几分钟体会一下,动手写几个例子观察一下帮助理解。