前述
Lambda表达式是 Java 8 的新特性。许多语言都有 Lambda 的特性。
因此使用的 Java 环境一定要 8 以上的环境。
Lambda
到底什么是 Lambda 表达式呢?
Lambda 表达式,也可称为闭包。Lambda 允许把函数作为一个方法的参数直接传递到方法中去。可以让我们不用费神去给函数起名。但是 Lambda 也只适合于简单的函数,对于复杂的函数,写成 Lambda 的形式反而会让人更加看不懂。
实例
接下来用一个实例慢慢导入 Lambda,要完成的是一个判断Person的id是否大于90的功能
首先创建一个Person类,包含 id 属性
package person; /**
* Person类
* @author jyroy
*
*/
public class Person {
@SuppressWarnings("unused")
public int id; public Person() { } public Person(int id) {
this.id = id;
} @Override
public String toString() {
return "Person [id=" + id + "]";
} }
普通方法
用普通方法,在 for 循环中通过 if 进行条件判断 if(person.id>90)
package normal; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import lambda.Check;
import person.Person; public class TestLambda {
@SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random(); //随机生成100个数
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
}
//使用 Lambda 筛选出大于90的数据
judge(lists); }
@SuppressWarnings("rawtypes")
private static void judge(List<Person> lists) {
for(Person person:lists) {
if(person.id>90) { //判断是否大于90
System.out.println(person);
}
}
}
}
结果
匿名类方法
创建一个匿名类,通过匿名类来实现这个判断 id 大于90的功能。
首先提供匿名类需要的接口,用于创建一个判断的类
package anonymity; import person.Person; public interface PersonCheck {
public boolean test(Person person);
}
通过匿名类实现接口
//使用 匿名类的方式 筛选出大于90的数据
PersonCheck personCheck = new PersonCheck() {
@Override
public boolean test(Person person) {
return person.id>90;
}
};
实现之后,就可以在judge函数中调用personCheck实例的test函数进行处理
package anonymity; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import lambda.Check;
import person.Person; /**
* 匿名类方式
* @author jyroy
*
*/
public class TestLambda {
@SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
} //使用 匿名类的方式 筛选出大于90的数据
PersonCheck personCheck = new PersonCheck() {
@Override
public boolean test(Person person) {
return person.id>90;
}
}; judge(lists, personCheck); }
@SuppressWarnings("rawtypes")
private static void judge(List<Person> lists, PersonCheck personCheck) {
for(Person person:lists) {
if(personCheck.test(person)) {
System.out.println(person);
}
}
}
}
结果
Lambda方式
接下来就是Lambda方式了
先上 Lambda表达式的写法,运行过程序之后再做总结
person->person.id>90
这便是一个Lambda表达式的形式,先记住这个形式,这个形式就是判断 person.id>90。
package lambda; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import anonymity.PersonCheck;
import person.Person; /**
* Lambda表达式
* @author jyroy
*
*/
public class TestLambda { @SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
}
//使用 Lambda 筛选出大于90的数据
filter(lists, person -> person.id > 90); }
@SuppressWarnings("rawtypes")
private static void filter(List<Person> lists, PersonCheck personCheck) {
for(Person person:lists) {
if(personCheck.test(person)) {
System.out.println(person);
}
}
}
}
结果
实现了同样的效果
总结
上面的程序中使用了 Lambda表达式,完成了判断的功能,而且相比匿名类的写法要简单非常多,使用 Lambda 表达式可以使代码变的更加简洁紧凑。
Lambda 表达式的语法格式:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
简单的例子
// 1. 不需要参数,返回值为 5
() -> 5 // 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x // 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y // 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
现在就可以解释上面的实例中的 Lambda表达式 的含义
person->person.id>90
即接收一个person参数,并返回大于person.id>90的数据
Lambda方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
可以看作是lambda的一种快捷写法,显式的指定方法的名称更具可读性。格式:目标引用+分隔符::+方法,例如,Dog::getAge就是引用了Dog类中定义的方法getAge。
注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
Lambda方法引用包含一下四种:
- 静态方法引用
- 对象方法引用
- 类成员方法引用
- 构造方法引用
静态方法引用
首先需要有一个静态方法
public static boolean test(Person person) {
return person.id>90 && person.id<95;
}
在Lambda表达式中调用这个静态方法,因为是静态方法可以不用创建对象,所以直接用类名进行调用
judge(lists, person -> TestLambda.test(person));
利用方法引用调用静态方法的形式
judge(lists, TestLambda::test);
主程序为
package references; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import anonymity.PersonCheck;
import person.Person; /**
* Lambda表达式
* @author jyroy
*
*/
public class TestLambda { @SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
}
//使用 Lambda 筛选出大于90的数据
judge(lists, person -> person.id < 10); //在Lambda表达式中使用静态方法
judge(lists, person -> TestLambda.test(person)); //直接使用静态方法
judge(lists, TestLambda::test); } public static boolean test(Person person) {
return person.id>90 && person.id<95;
} @SuppressWarnings("rawtypes")
private static void judge(List<Person> lists, PersonCheck personCheck) {
for(Person person:lists) {
if(personCheck.test(person)) {
System.out.println(person);
}
}
}
}
对象方法引用
和静态方法相似,,但是在传递方法的时候,因为不是静态方法,所以必须要利用对象进行传送
package references; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import anonymity.PersonCheck;
import person.Person; /**
* Lambda表达式
* @author jyroy
*
*/
public class TestLambda { @SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
} //使用引用对象方法
TestLambda testLambda = new TestLambda();
judge(lists, testLambda::test2); } public boolean test2(Person person) {
return person.id>90 && person.id<95;
} @SuppressWarnings("rawtypes")
private static void judge(List<Person> lists, PersonCheck personCheck) {
for(Person person:lists) {
if(personCheck.test(person)) {
System.out.println(person);
}
}
}
}
类成员方法引用
Person类要添加一个成员方法,才能够进行成员方法的引用
public boolean test3() {
return this.id>90 && this.id<95;
}
在Lambda表达式中使用 test3方法
judge(lists, person -> person.test3());
利用方法引用的写法为
judge(lists, Person::test3);
主程序为
package references; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import anonymity.PersonCheck;
import person.Person; /**
* Lambda表达式
* @author jyroy
*
*/
public class TestLambda { @SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
} //使用类成员方法
judge(lists, person -> person.test3()); //可改写为
judge(lists, Person::test3); } public static boolean test(Person person) {
return person.id>90 && person.id<95;
} public boolean test2(Person person) {
return person.id>90 && person.id<95;
} @SuppressWarnings("rawtypes")
private static void judge(List<Person> lists, PersonCheck personCheck) {
for(Person person:lists) {
if(personCheck.test(person)) {
System.out.println(person);
}
}
}
}
构造方法引用
需要有返回一个对象的方法
构造方法引用形式为
ArrayList::new
Person::new
package lambda; import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier; public class TestLambda {
public static void main(String[] args) {
Supplier<List> s = new Supplier<List>() {
public List get() {
return new ArrayList();
}
};
//引用构造器
List list3 = getList(ArrayList::new); } public static List getList(Supplier<List> s){
return s.get();
}
聚合操作
引入实例
在上面的程序中,遍历输出数据利用的是for循环的方式
for(Person person:lists) {
if(personCheck.test(person)) {
System.out.println(person);
}
}
我们可以利用聚合操作来进行数据的输出
lists
.stream()
.filter(person -> person.id>95)
.forEach(person -> System.out.println(person.id));
主程序为
package normal; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import lambda.Check;
import person.Person; public class TestLambda {
@SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person(r.nextInt(100)));
}
System.out.println("使用传统方式----");
for(Person person:lists) {
if(person.id>90) {
System.out.println(person);
}
}
System.out.println("聚合操作方式");
lists
.stream()
.filter(person -> person.id>95)
.forEach(person -> System.out.println(person.id));
}
}
结果
Stream和管道
我们对应上面的代码,可以看出,聚合操作分为三步
- 生成
- 操作、变换(可以多次)
- 消耗(只有一次)
当然我们还要知道stream和管道的概念
Stream:Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到,前面处理的结果。注意:这个Stream和I/O中的InputStream,OutputStream是不一样的概念。
管道:指的是一系列的聚合操作。
管道又分3个部分:管道源、中间操作、结束操作
管道源:在这个例子里,源是一个List,可以用 .stream() 方法切换成管道源。但是数组没有stream() 方法,需要用 Arrays.stream(hs) 或者 Stream.of(hs)
package lambda; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Random; import charactor.Hero; public class TestAggregate { public static void main(String[] args) {
Random r = new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
}
//管道源是集合
heros
.stream()
.forEach(h->System.out.println(h.name)); //管道源是数组
Hero hs[] = heros.toArray(new Hero[heros.size()]);
Arrays.stream(hs)
.forEach(h->System.out.println(h.name)); }
}
中间操作: 每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。中间操作比较多,主要分两类对元素进行筛选 和 转换为其他形式的流
对元素进行筛选:
filter 匹配
distinct 去除重复(根据equals判断)
sorted 自然排序
sorted(Comparator<T>) 指定排序
limit 保留
skip 忽略
转换为其他形式的流
mapToDouble 转换为double的流
map 转换为任意类型的流
package lambda; import java.util.ArrayList;
import java.util.List;
import java.util.Random; import charactor.Hero; public class TestAggregate { public static void main(String[] args) {
Random r = new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
}
//制造一个重复数据
heros.add(heros.get(0));
System.out.println("初始化集合后的数据 (最后一个数据重复):");
System.out.println(heros);
System.out.println("满足条件hp>100&&damage<50的数据"); heros
.stream()
.filter(h->h.hp>100&&h.damage<50)
.forEach(h->System.out.print(h)); System.out.println("去除重复的数据,去除标准是看equals");
heros
.stream()
.distinct()
.forEach(h->System.out.print(h));
System.out.println("按照血量排序");
heros
.stream()
.sorted((h1,h2)->h1.hp>=h2.hp?1:-1)
.forEach(h->System.out.print(h)); System.out.println("保留3个");
heros
.stream()
.limit(3)
.forEach(h->System.out.print(h)); System.out.println("忽略前3个");
heros
.stream()
.skip(3)
.forEach(h->System.out.print(h)); System.out.println("转换为double的Stream");
heros
.stream()
.mapToDouble(Hero::getHp)
.forEach(h->System.out.println(h)); System.out.println("转换任意类型的Stream");
heros
.stream()
.map((h)-> h.name + " - " + h.hp + " - " + h.damage)
.forEach(h->System.out.println(h)); }
}
结束操作:当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回, 结束操作才进行真正的遍历行为,在遍历的时候,才会去进行中间操作的相关判断.
常见结束操作如下:
forEach() 遍历每个元素
toArray() 转换为数组
min(Comparator<T>) 取最小的元素
max(Comparator<T>) 取最大的元素
count() 总数
findFirst() 第一个元素
package lambda; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random; import org.omg.Messaging.SYNC_WITH_TRANSPORT; import charactor.Hero; public class TestAggregate { public static void main(String[] args) {
Random r = new Random();
List<Hero> heros = new ArrayList<Hero>();
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i, r.nextInt(1000), r.nextInt(100)));
}
System.out.println("遍历集合中的每个数据");
heros
.stream()
.forEach(h->System.out.print(h));
System.out.println("返回一个数组");
Object[] hs= heros
.stream()
.toArray();
System.out.println(Arrays.toString(hs));
System.out.println("返回伤害最低的那个英雄");
Hero minDamageHero =
heros
.stream()
.min((h1,h2)->h1.damage-h2.damage)
.get();
System.out.print(minDamageHero);
System.out.println("返回伤害最高的那个英雄"); Hero mxnDamageHero =
heros
.stream()
.max((h1,h2)->h1.damage-h2.damage)
.get();
System.out.print(mxnDamageHero); System.out.println("流中数据的总数");
long count = heros
.stream()
.count();
System.out.println(count); System.out.println("第一个英雄");
Hero firstHero =
heros
.stream()
.findFirst()
.get(); System.out.println(firstHero); }
}
编程实例
首选准备100个Person对象,id都是随机数。
分别用传统方式和聚合操作的方式,把id第三高的名称和id打印出来
Person.java
package aggregation; public class Person {
public String name;
public int id; public Person(String name, int id){
this.name = name;
this.id = id;
} @Override
public String toString() {
return "Person [name=" + name + ", id=" + id + "]";
}
}
TestLambda.java
package aggregation; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random; import lambda.Check; public class TestLambda {
@SuppressWarnings({ "rawtypes", "unused", "unchecked" })
public static void main(String[] args) {
Random r = new Random();
List<Person> lists = new ArrayList<Person>();
for(int i=0;i<100;i++) {
lists.add(new Person("Person"+i, r.nextInt(100)));
}
System.out.println("使用传统方式----");
Comparator<Person> c = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.id >= p2.id ? 1 : -1;
}
};
Collections.sort(lists, c);
System.out.println(lists.get(2)); System.out.println("聚合操作方式----");
Person p = lists
.stream()
.sorted((person1, person2) -> person1.id>=person2.id?1:-1)
.skip(2)
.findFirst()
.get();
System.out.println(p);
}
}
结果