Lambda
Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像数据一样传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到提升
体验
/**
* lambda 初体验
*/
public class lambdaTest {
@Test
public void test() {
// 匿名内部类
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
TreeSet<Integer> treeSet = new TreeSet<>(comparator);
// lambda
Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);
TreeSet<Integer> treeSet1 = new TreeSet<>(comparator1);
}
}
演变
原始做法
有这么一个需求,求一个班级中年龄大于16岁的同学;
public class Student {
private String name;
private Integer age;
private Integer scope;
public Student() {
}
public Student(String name, Integer age, Integer scope) {
this.name = name;
this.age = age;
this.scope = scope;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getScope() {
return scope;
}
public void setScope(Integer scope) {
this.scope = scope;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", scope=" + scope +
'}';
}
}
List<Student> students = Arrays.asList(
new Student("张三", 18, 90),
new Student("李四", 14, 95),
new Student("王五", 16, 98),
new Student("牛二", 18, 95),
new Student("赵六", 15, 96)
);
// 年龄大于16
@Test
public void test01() {
List<Student> stus = new ArrayList<>();
for (Student student : students) {
if (student.getAge() > 16) {
stus.add(student);
}
}
for (Student student : stus) {
System.out.println(student);
}
}
需求变更:求班级中分数大于95的同学
// 分数大于95
@Test
public void test02() {
List<Student> stus = new ArrayList<>();
for (Student student : students) {
if (student.getScope() > 95) {
stus.add(student);
}
}
for (Student student : stus) {
System.out.println(student);
}
}
可以看出,有效代码只有if
判断中的条件,这样情况下,随着需求的变更,我们会书写很多重复性代码
优化方式一:策略设计模式
需求:年龄大于16
// 定义过滤接口
public interface MyPredicate<T> {
public boolean test(T t);
}
// 年龄过滤
public class AgePredicate implements MyPredicate<Student> {
@Override
public boolean test(Student student) {
return student.getAge() > 16;
}
}
public List<Student> filterStu(List<Student> students, MyPredicate myPredicate) {
List<Student> list = new ArrayList<>();
for (Student student : students) {
if (myPredicate.test(student)) {
list.add(student);
}
}
return list;
}
// 年龄大于16
@Test
public void test03() {
List<Student> students = filterStu(this.students, new AgePredicate());
for (Student student : students) {
System.out.println(student);
}
}
需求变更:分数大于95
// 分数过滤
public class ScopePredicate implements MyPredicate<Student> {
@Override
public boolean test(Student student) {
return student.getScope() > 95;
}
}
public List<Student> filterStu(List<Student> students, MyPredicate myPredicate) {
List<Student> list = new ArrayList<>();
for (Student student : students) {
if (myPredicate.test(student)) {
list.add(student);
}
}
return list;
}
// 分数大于95
@Test
public void test04() {
List<Student> students = filterStu(this.students, new ScopePredicate());
for (Student student : students) {
System.out.println(student);
}
}
此时,根据需求的不同,我们只需要定义MyPredicate
的实现类来编写过滤条件即可,不要修改原有的逻辑代码
优化方式二:匿名内部类
需求:年龄大于16
// 年龄大于16
@Test
public void test05() {
List<Student> students = filterStu(this.students, new MyPredicate<Student>() {
@Override
public boolean test(Student stu) {
return stu.getAge() > 16;
}
});
for (Student student : students) {
System.out.println(student);
}
}
需求变更:分数大于95
// 分数大于95
@Test
public void test06() {
List<Student> students = filterStu(this.students, new MyPredicate<Student>() {
@Override
public boolean test(Student stu) {
return stu.getScope() > 95;
}
});
for (Student student : students) {
System.out.println(student);
}
}
通过匿名内部类的方式实现过滤代码,可以避免为每一个需求定一个实现类来编写过滤代码;但是此时的代码还是略显臃肿
优化方式三:lambda表达式
需求:年龄大于16
@Test
public void test07() {
List<Student> students = filterStu(this.students, x -> x.getAge() > 16);
students.forEach(System.out::println);
}
需求变更:分数大于95
@Test
public void test08() {
List<Student> students = filterStu(this.students, x -> x.getScope() > 95);
students.forEach(System.out::println);
}
此时,我们可以看出,有效代码只有x -> x.getAge() > 16
,x -> x.getScope() > 95
,极大提高代码的简洁性和可读性
语法
Java8中引入了一个新的操作符->
,该操作符称为箭头操作符或Lambda操作符,->
将Lambda表达式拆分成左右两部分
左侧:Lambda表达式的参数列表(对应匿名函数,重写方法的参数列表)
右侧:Lambda表达式中所执行的功能,即Lambda体(对应匿名函数,重写方法的方法体)
语法格式一:无参数,无返回值
() -> System.out.println("Hello lambda");
@Test
public void test01() {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello world");
}
};
r.run();
System.out.println("---------------------");
Runnable r1 = () -> System.out.println("Hello lambda");
r1.run();
}
语法格式二:有一个参数,无返回值
(x) -> System.out.println(x);
// 若只有一个参数,小括号可以不写
x -> System.out.println(x);
语法格式三:多个参数,有返回值,多条执行语句
@Test
public void test02() {
// 匿名函数方式
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
System.out.println("-------------------------------");
// 多条执行语句
Comparator<Integer> comparator1 = (o1, o2) -> {
System.out.println(o1 + "--------" + o2);
return Integer.compare(o1, o2);
};
System.out.println("-------------------------------");
// 若只有一条执行语句,return和大括号可以省略不写
Comparator<Integer> comparator2 = (o1, o2) -> Integer.compare(o1, o2);
}
函数式接口
接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解@FunctionalInterface
修饰;Lambda表达式需要“函数式接口”的支持
内置四大核心函数式接口
接口 | 接口说明 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|---|
Consumer<T> | 消费型接口 | T | void | 对类型为T的对象操作,方法:void accept(T t); |
Supplier<T> | 供给型接口 | 无 | T | 返回T类型对象,方法:T get(); |
Function<T,R> | 函数型接口 | T | R | 对T类型对象操作,并返回R类型结果;方法:R apply(T t); |
Predicate<T> | 断言型接口 | T | boolean | 确定T类型对象是否满足某中约束,并返回boolean值;方法:test(T t); |
扩展接口
接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | 对类型为T, U参数应用操作,返回R类型的结果;方法 R apply(T t, U u); |
UnaryOpetator<T> (Function子接口) | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果;方法:T apply(T t); |
BinaryOperator<T> (BiFunction子接口) | T, T | T | 对类型为T的对象进行二元运算,并返回T类型的结果;方法:T apply(T t1, T t2); |
BiConsumer(T, U) | T, U | void | 对类型为T, U参数应用操作;方法:void accept(T t, U u) |
ToIntFunction<T><br />ToLongFunction<T><br />ToDoubleFunction<T> | T | int<br />long<br />double | 分别计算int, long, double值的函数 |
IntFunction<R><br />LongFunction<R><br />DoubleFunction<R> | int<br />long<br />double | R | 参数分别为int, long, double 类型的函数 |
方法引用
若Lambda体中的内容有方法已经实现,我们可以使用方法引用
;方法引用
可以理解为是Lambda表达式的另外一种表现形式
对象::实例方法名
注意:Lambda 体中调用方法的参数列表和返回值类型,要与函数式接口中抽象方法的参数列表和返回值类型保持一致
public void test01() {
// 方式一
Consumer<String> c1 = (x) -> System.out.println(x);
// 方式二
PrintStream out1 = System.out;
Consumer<String> c2 = (x) -> out1.println(x);
// 方式三
PrintStream out2 = System.out;
Consumer<String> c3 = out2::println;
// 方式四
Consumer<String> c4 = System.out::println;
}
类名::静态方法名
注意:Lambda 体中调用方法的参数列表和返回值类型,要与函数式接口中抽象方法的参数列表和返回值类型保持一致
public void test02() {
// 方式一
Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);
// 方式二
Comparator<Integer> comparator2 = Integer::compare;
}
类名::实例方法名
注意:Lambda表达式参数列表中第一参数是实例方法的调用者,第二个参数是实例方法的参数时,可以使用ClassName::Method
@Test
public void test03() {
// 方式一
BiPredicate<String, String> predicate01 = (x, y) -> x.equals(y);
// 方式二
BiPredicate<String,String> predicate02 = String::equals;
}
构造器引用
格式: ClassName::new
注意:Lambda体中需要调用构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致
@Test
public void test04() {
// 无参
Supplier<Date> s1 = () -> new Date();
Supplier<Date> s2 = Date::new;
// 一个参数
Function<Long, Date> s3 = (x) -> new Date(x);
Function<Long, Date> s4 = Date::new;
}
数组引用
格式:Type[] ::new
@Test
public void test05() {
// 方式一
Function<Integer, String[]> f1 = (x) -> new String[x];
// 方式二
Function<Integer, String[]> f2 = String[]::new;
}