import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class LambdaTest {
public static void main(String[] args) {
Random r = new Random();
List<Student> students = new ArrayList<Student>();
for (int i = 0; i < 5; i++) {
int age = r.nextInt(6) + 6;
students.add(new Student("student" + i, age, age % 6 + 1));
}
System.out.println("初始化后的集合:");
System.out.println(students);
System.out.println("满足条件的学生有:");
// 这里传递的第二个参数就是Lambda表达式
// 但实际上,它相当于一个简写的匿名类(实现了StudentChecker接口)
// s是参数,【->】后是方法体,相当于以下的匿名类
filter(students, s -> s.age >= 10 && s.grade <= 6);
/*StudentChecker checker = new StudentChecker() {
@Override
public boolean check(Student student) {
return student.age >= 10 && student.grade <= 6;
}
}; // 这和上面的Lambda表达式是一样的效果
filter(students, checker);*/
}
public static void filter(List<Student> list, StudentChecker checker) {
for (Student student : list) {
if (checker.check(student)) {
System.out.println(student);
}
}
}
}
class Student {
public String name;
public int age;
public int grade;
public Student(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
@Override
public String toString() {
return "\n{Name : " + name + ",Age : " + age + ",Grade : " + grade + "}\n";
}
}
interface StudentChecker {
boolean check(Student student);
}
(1)、从匿名类演变成Lambda表达式
Lambda表达式可以看成是匿名类一点点演变过来:↓ ↓ ↓
[1]、匿名类的正常写法
StudentChecker checker = new StudentChecker() {
@Override
public boolean check(Student student) {
return student.age >= 10 && student.grade <= 6;
}
};
[2]、把外面的壳子去掉,只保留方法参数和方法体,参数和方法体之间加上符号【 ->】
StudentChecker checker = (Student student)->{
return student.age >= 10 && student.grade <= 6;
};
[3]、把return和{}去掉
StudentChecker checker = (Student student)->student.age >= 10 && student.grade <= 6;
[4]、把 参数类型和圆括号去掉(只有一个参数的时候,才可以去掉圆括号)
StudentChecker checker = student->student.age >= 10 && student.grade <= 6;
[5]、把checker作为参数传递进去
filter(students, checker);
[6]、直接把表达式传递进去
filter(students, s -> s.age >= 10 && s.grade <= 6);
(2)、匿名方法
与匿名类概念相比较,
Lambda 其实就是匿名方法,这是一种把方法作为参数进行传递的编程思想。
虽然代码是这么写
filter(students, s -> s.age >= 10 && s.grade <= 6);
但是,Java会在背后,悄悄的,把这些都还原成匿名类方式。
引入Lambda表达式,会使得代码更加紧凑,而不是各种接口和匿名类到处飞。
(3)、Lambda的弊端
Lambda表达式虽然带来了代码的简洁,但是也有其局限性。
[1]、可读性差,与啰嗦的但是清晰的匿名类代码结构比较起来,Lambda表达式一旦变得比较长,就难以理解
[2]、不便于调试,很难在Lambda表达式中增加调试信息,比如日志
[3]、版本支持,Lambda表达式在JDK8版本中才开始支持,如果系统使用的是以前的版本,考虑系统的稳定性等原因,而不愿意升级,那么就无法使用。
Lambda比较适合用在简短的业务代码中,并不适合用在复杂的系统中,会加大维护成本。
(4)、方法引用
[1]、引用静态方法
[2]、引用对象方法
[3]、引用容器中的对象的方法
[4]、引用构造器
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class LambdaTest {
public static void main(String[] args) {
Random r = new Random();
List<Student> students = new ArrayList<Student>();
for (int i = 0; i < 5; i++) {
int age = r.nextInt(6) + 6;
students.add(new Student("student" + i, age, age % 6 + 1));
}
System.out.println("初始化后的集合:");
System.out.println(students);
// 引用静态方法
System.out.println("引用静态方法的结果:");
// filter(students, s->LambdaTest.checkStudent(s));
filter(students, LambdaTest::checkStudent);
// 引用对象方法
LambdaTest lambda = new LambdaTest();
System.out.println("引用对象方法的结果:");
// filter(students, s->lambda.checkObjectStudent(s));
filter(students, lambda::checkObjectStudent);
// 引用容器中的对象的方法
System.out.println("引用容器中的对象的方法的结果:");
// filter(students, s->s.matched());
filter(students, Student::matched);
System.out.println("无参的Lambda表达式使用:");
List list1 = getList(()->new ArrayList<>());
System.out.println(list1);
System.out.println("引用构造器的结果:");
List list2 = getList(ArrayList::new);
System.out.println(list2);
}
public static void filter(List<Student> list, StudentChecker checker) {
for (Student student : list) {
if (checker.check(student)) {
System.out.println(student);
}
}
}
public boolean checkObjectStudent(Student student) {
return student.age >= 10 && student.grade <= 6;
}
public static boolean checkStudent(Student student) {
return student.age >= 10 && student.grade <= 6;
}
public static List getList(Worker<List> w) {
return w.get();
}
}
class Student {
public String name;
public int age;
public int grade;
public Student(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
@Override
public String toString() {
return "\n{Name : " + name + ",Age : " + age + ",Grade : " + grade + "}\n";
}
public boolean matched() {
return age >= 10 && grade <= 6;
}
}
interface StudentChecker {
boolean check(Student student);
}
interface Worker<T>{
T get();
}
运行结果:
(5)、聚合操作
[1]、传统方式与聚合操作方式遍历数据
遍历数据的传统方式就是使用for循环,然后条件判断,最后打印出满足条件的数据
for (Hero h : heros) {
if (h.hp > 100 && h.damage < 50)
System.out.println(h.name);
}
使用聚合操作方式,画风就发生了变化:
heros
.stream()
.filter(h -> h.hp > 100 && h.damage < 50)
.forEach(h -> System.out.println(h.name));
[2]、Stream和管道的概念
要了解聚合操作,首先要建立Stream和管道的概念
Stream 和Collection结构化的数据不一样,Stream是一系列的元素,就像是生产线上的罐头一样,一串串的出来。
管道指的是一系列的聚合操作。
【管道】又分3个部分:
管道源: 在这个例子里,源是一个List
中间操作: 每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
结束操作: 当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回, 结束操作才进行真正的遍历行为,在遍历的时候,才会去进行中间操作的相关判断
【注: 这个Stream和I/O章节的InputStream,OutputStream是不一样的概念。】
[3]、管道源
把Collection切换成管道源很简单,调用stream()就行了。
heros.stream()
但是数组却没有stream()方法,需要使用
Arrays.stream(hs)
或
Stream.of(hs)
[4]、中间操作每个中间操作,又会返回一个Stream,比如.filter()又返回一个Stream, 中间操作是“懒”操作,并不会真正进行遍历。
中间操作比较多,主要分两类:
1、对元素进行筛选
2、转换为其他形式的流
常见中间操作:↓ ↓ ↓
对元素进行筛选 | 转换为其他形式的流 | ||
---|---|---|---|
操作名 | 功能 | 操作名 | 功能 |
filter | 匹配 | mapToDouble | 转换为double的流 |
distinct | 去除重复(根据equals判断) | map | 转换为任意类型的流 |
sorted | 自然排序 | ||
sorted(Comparator<T>) | 指定排序 | ||
limit | 保留 | ||
skip | 忽略 |
[5]、结束操作
当进行结束操作后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。 结束操作不会返回Stream,但是会返回int、float、String、 Collection或者像forEach,什么都不返回,。
结束操作才真正进行遍历行为,前面的中间操作也在这个时候,才真正的执行。
常见结束操作:↓ ↓ ↓
操作名 | 功能 |
---|---|
forEach() | 遍历每个元素 |
min(Comparator<T>) | 取最小的元素 |
max(Comparator<T>) | 取最大的元素 |
toArray() | 转换为数组 |
count() | 总数 |
findFirst() | 第一个元素 |