匿名内部类
在介绍如何使用 Lambda 表达式之前,我们先来看看匿名内部类,例如,我们使用匿名内部类比较两个 Integer 类型数据的大小
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
在上述代码中,我们使用匿名内部类实现了比较两个 Integer 类型数据的大小。
接下来,我们就可以将上述匿名内部类的实例作为参数,传递到其他方法中了,如下所示。
TreeSet treeSet = new TreeSet<>(com);
完整的代码如下所示:
public static void main(String[] args) {
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// return Integer.compare(o1,o2);
return o2-o1;
}
};
TreeSet<Integer> integers = new TreeSet<>(comparator);
integers.addAll(Arrays.asList(1,6,5,3,7,8,2));
System.out.println(integers);
}
我们分析下上述代码,在整个匿名内部类中,实际上真正有用的就是下面一行代码。
return Integer.compare(o1, o2); 或 return o2-o1;
Lambda表达式
如果使用 Lambda 表达式完成两个 Integer 类型数据的比较,我们该如何实现呢?
Comparator com = (x, y) -> Integer.compare(x, y);
我们也可以将 Lambda 表达式传递到 TreeSet 的构造方法中,如下所示。
TreeSet treeSet = new TreeSet<>((x, y) -> Integer.compare(x, y));
直观的感受就是使用 Lambda 表达式一行代码就能搞定匿名内部类多行代码的功能,非常简洁。
对比常规方法和Lambda表达式
常规模式
如有以下数据模型:
@Data
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
private String username;
private Integer age;
private Double salary;
}
当进行不同属性比较时,会有如下操作:
List<Emp> emps = Arrays.asList(new Emp("zhangsan", 15, 5000.0),
new Emp("lisi", 16, 4000.0),
new Emp("wangwu", 17, 6000.0));
emps.forEach(emp -> {
if(emp.getAge()>16){
System.out.println(emp);
}
});
emps.forEach(emp -> {
if(emp.getSalary()>5000){
System.out.println(emp);
}
});
如果此时我们再来一个需求,查找当前公司中年龄小于或者等于 20 的员工信息,那我们又要单独写一个过滤了。使用常规方法是真的不方便啊!
使用设计模式优化代码
先定义一个过滤器接口类:
public interface MyPredicate<T>{
public boolean filter(T t);
}
实现过滤器接口类:
public class FilterEmployeeByAge implements MyPredicate<Emp>{
@Override
public boolean filter(Emp emp) {
return emp.getAge()>16;
}
}
测试:
List<Emp> emps = Arrays.asList(new Emp("zhangsan", 15, 5000.0),
new Emp("lisi", 16, 4000.0),
new Emp("wangwu", 17, 6000.0));
MyPredicate myPredicate = new FilterEmployeeByAge();
emps.forEach(emp -> {
if(myPredicate.filter(emp)){
System.out.println(emp);
}
});
此时,你会发现,当你将遍历打印封装成方法时,如果你需要以其他方式过滤,只需要实现MyPredicate接口,并引用对应的实现即可,这就是设计模式的魅力。
使用设计模式优化代码也有不好的地方:每次定义一个过滤策略的时候,我们都要单独创建一 个过滤类!
匿名内部类
List<Emp> emps = Arrays.asList(new Emp("zhangsan", 15, 5000.0),
new Emp("lisi", 16, 4000.0),
new Emp("wangwu", 17, 6000.0));
MyPredicate<Emp> myPredicate = new MyPredicate<Emp>() {
@Override
public boolean filter(Emp emp) {
if(emp.getSalary()>5000.0)
return true;
return false;
}
};
emps.forEach(emp -> {
if(myPredicate.filter(emp)){
System.out.println(emp);
}
});
匿名内部类看起来比常规遍历集合的方法要简单些,并且将使用设计模式优化代码时,每次创
建一个类来实现过滤规则写到了匿名内部类中,使得代码进一步简化了。
Lambda表达式
List<Emp> emps = Arrays.asList(new Emp("zhangsan", 15, 5000.0),
new Emp("lisi", 16, 4000.0),
new Emp("wangwu", 17, 6000.0));
emps.stream().filter(emp -> emp.getAge()>16).forEach(emp-> System.out.println(emp));
emps.stream().filter(emp -> emp.getSalary()>6000.0).forEach(emp -> System.out.println(emp));
使用 Lambda 表达式结合 Stream API,只要给出相应的集合,我们就可以完成对集合的各种过滤并输出结果信息。
只给出一个集合,使用 Lambda 表达式和 Stream API,一行代码就能够过滤出想要的元素并进行输出。
匿名类到Lambda表达式
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("test");
}
};
runnable = ()-> System.out.println("hello");
从直观上看,Lambda 表达式要比常规的语法简洁的多。
Lambda表达式到语法
Lambda 表达式在 Java 语言中引入了 “->” 操作符, “->” 操作符被称为 Lambda 表达式
的操作符或者箭头操作符,它将 Lambda 表达式分为两部分:
- 左侧部分指定了 Lambda 表达式需要的所有参数。(Lambda 表达式本质上是对接口的实现,Lambda 表达式的参数列表本质上对应着接口中方法的参数列表)
- 右侧部分指定了 Lambda 体,即 Lambda 表达式要执行的功能。(Lambda 体本质上就是接口方法具体实现的功能。)
语法格式一:无参,无返回值,Lambda 体只有一条语句
Runnable r = () -> System.out.println(“Hello Lambda”);
语法格式二:Lambda 表达式需要一个参数,并且无返回值
Consumer func = (s) -> System.out.println(s);
语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略
Consumer func = s -> System.out.println(s);
语法格式四:Lambda 需要两个参数,并且有返回值
BinaryOperator<Integer> bo = (a, b) -> {
System.out.println("函数式接口");
return a + b;
};
Comparator<Integer> comparator = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
语法格式五:当 Lambda 体只有一条语句时,return 和大括号可以省略
BinaryOperator bo = (a, b) -> a + b;
Comparator comparator = (x, y) -> Integer.compare(x, y);
语法格式六:Lambda 表达式的参数列表的数据类型可以省略不写,因为 JVM 编译器能够通过上下文推断出数据类型,这就是“类型推断”
BinaryOperator<Integer> bo = (Integer a, Integer b) -> {
return a + b;
};
等价于
BinaryOperator<Integer> bo = (a, b) -> {
return a + b;
};
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,
程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
函数式接口
1)、Lambda 表达式需要函数式接口的支持,只包含一个抽象方法的接口,称为函数式接口。
2)、可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。
3)、可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
4)、下面自定义函数式接口,并使用 Lambda 表达式来实现相应的功能。
例如,使用函数式接口和 Lambda 表达式实现对字符串的处理功能。
首先,我们定义一个函数式接口 MyFunc,如下所示。
@FunctionalInterface
public interface MyFunc <T>{
public T getValue(T t);
}
接下来,我们定义一个操作字符串的方法,其中参数为 MyFunc 接口实例和需要转换的字符串。
public static String handlerString(MyFunc<String> myFunc,String str){
return myFunc.getValue(str);
}
接下来,我们对自定义的函数式接口进行测试,此时我们传递的函数式接口的参数为 Lambda表达式,并且将字符串转化为大写。
String hello = handlerString(s -> s.toUpperCase(), “hello”);
System.out.printf(hello);
运行结果如下:
我们也可以截取字符串的某一部分,如下所示。
String hello = handlerString(s -> s.substring(2),“hello”);
可以看到, 我们可以通过handlerString (MyFunc myFunc,String str)方法结合Lambda 表达式对字符串进行任意操作。
注意:作为参数传递Lambda表达式:为了将Lambda表达式作为参数传递,接收 Lambda表达式的参数类型必须是与该Lambda表达式兼容的函数式接口的类型。
Lambda表达式案例
案例一
List<Emp> emps = Arrays.asList(new Emp("zhangsan", 18, 5000.0),
new Emp("lisi", 16, 4000.0),
new Emp("wangwu", 17, 6000.0));
List<Emp> list = emps.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).collect(Collectors.toList());
list.stream().forEach(emp -> System.out.println(emp));
案例二
String hello = handlerString(s -> s.toUpperCase(), "hello");
String substr = handlerString(s -> s.substring(2),"hello");
案例三
@FunctionalInterface
public interface Func <T,R>{
R getValue(T t1,T t2);
}
private static Integer handTwoArgs(int i, int i1, Func<Integer,Integer> func) {
return func.getValue(i,i1);
}
handTwoArgs(1,2,(x,y)->x+y);