上一讲给大家介绍了Lamdba表达式入门,让我们先回顾上一讲的实例代码
public class TestStudentByLamdba {
public static void main(String[] args) {
List<Student> stuList = Arrays.asList(
new Student("张三","001",19),
new Student("李四","005",22),
new Student("王五","010",14),
new Student("赵六","004",18),
new Student("何琦","006",12)
);
sortByNo(stuList);
System.out.println("age > 15");
//查询所有年龄大于15的人
filterCondition(stuList,(t)->t.getAge()>15);
System.out.println("name like 张");
//查询姓张的所有学生
filterCondition(stuList,(t)->t.getName().startsWith("张"));
System.out.println("all student");
//查询所有学生对象,条件为true
filterCondition(stuList, (t)->true);
}
//根据学号排序
public static void sortByNo(List<Student> list) {
Collections.sort(list,(s1,s2)->s1.getNo().compareTo(s2.getNo()));
}
public static void filterCondition(List<Student> stuList,Condition<Student> c) {
for(Student stu:stuList) {
if(c.test(stu)) {
System.out.println(stu);
}
}
}
interface Condition<T> {
public boolean test(T t);
}
}
在该实例中我们通过Condition接口来处理条件的测试请求,如果要使用Lamdba表达式,我们将会使用到大量的接口,所以Java的开发团队也考虑到了这个问题,他们为我们提供了大量的Function的接口,供开发人员使用,所有的接口都在java.util.function包中,大家可以通过jdk的docs进行查看。其中Predicate<T>
接口中就提供了test(T t)
的 方法来完成对一个对象的比较操作。我们就可以进一步的简化以上的代码,可以不再需要Condition
//使用Predicate来替代Condition
public static void filterCondition(List<Student> stuList,Predicate<Student> c) {
for(Student stu:stuList) {
if(c.test(stu)) {
System.out.println(stu);
}
}
}
更多的Java Function接口
出了Predicate外,Jdk还提供了几个非常好用的接口,这里简单进行了一些归纳
接口 | 函数 | 介绍 |
---|---|---|
Function<T,R> | R apply(T t) | 传入两个对象,根据T返回R |
Predicate<T> | boolean test(T t) | 传入一个对象,返回一个boolean |
Supplier<T> | T get() | 返回一个对象 |
Consumer<T> | void accept(T t) | 传入一个对象,没有返回值 |
BiConsumer<T,U>等 | 传入两个对象 | 以上的方法都提供了一个BiXXX的方法来增加参数 |
JDK其实已经基本把最基础的函数接口都提供,大家在使用的时候一定思考清楚函数接口是什么,需不需要自己再添加,接下来我会展示另外一个接口的应用。依然是上面的例子,我们在filterCondition中有一个非常不好的地方
if(c.test(stu)) {
System.out.println(stu);
}
System.out.println(stu)
是典型的硬编码,我们不一定要打印整个对象,我们可能只会涉及到打印对象的某个值,或者我们的输出不一定是控制台,而是文件,由于上面的硬编码使得上述需求无法实现,接下来利用Consumer来解决该问题,Consumer接口提供的方法可以接受一个对象来进行处理,刚好满足这个要求,先看看如何改造filterCondition方法
public static void filterCondition(List<Student> stuList,Predicate<Student> pre,Consumer<Student> con) {
for(Student stu:stuList) {
if(pre.test(stu)) {
con.accept(stu);
}
}
}
增加第三个参数Consumer来接受一个对象,此时即可在调用的地方通过Lamdba来完成操作
//查询所有年龄大于15的人,打印所有学生信息
filterCondition(stuList,t->t.getAge()>15,t->System.out.println(t));
System.out.println("name like 张");
//查询姓张的所有学生,仅仅打印姓名
filterCondition(stuList,t->t.getName().startsWith("张"),t->System.out.println(t.getName()));
System.out.println("all student");
//查询所有学生对象,条件为true,仅仅打印学号
filterCondition(stuList, t->true,t->System.out.println(t.getNo()));
通过第三个参数确定输出的结果,值得一提的是由于参数只有一个,我们可以将参数的括号去掉。完整代码如下
public class TestStudentByLamdba3 {
public static void main(String[] args) {
List<Student> stuList = Arrays.asList(
new Student("张三","001",19),
new Student("李四","005",22),
new Student("王五","010",14),
new Student("赵六","004",18),
new Student("何琦","006",12)
);
sortByNo(stuList);
System.out.println("age > 15");
//查询所有年龄大于15的人,打印所有学生信息
filterCondition(stuList,(t)->t.getAge()>15,t->System.out.println(t));
System.out.println("name like 张");
//查询姓张的所有学生,仅仅打印姓名
filterCondition(stuList,(t)->t.getName().startsWith("张"),t->System.out.println(t.getName()));
System.out.println("all student");
//查询所有学生对象,条件为true,仅仅打印学号
filterCondition(stuList, (t)->true,t->System.out.println(t.getNo()));
}
//根据学号排序
public static void sortByNo(List<Student> list) {
Collections.sort(list,(s1,s2)->s1.getNo().compareTo(s2.getNo()));
}
public static void filterCondition(List<Student> stuList,Predicate<Student> pre,Consumer<Student> con) {
for(Student stu:stuList) {
if(pre.test(stu)) {
con.accept(stu);
}
}
}
}
通过这个例子,相信大家对Lamdba表达式有了更进一步的了解,而且应该也看到Lamdba表达式给我们带来的好处。
Lamdba表达式处理异常和表达式封装模式
异常处理是Java中一个非常重要的错误处理手段,我们接下来通过实例来看一下Lamdba的异常处理,我们将会通过异常处理提出一中比较理想的封装模式,其实非常像设计模式中的代理模式,首先看如下实例
public class TestLamdba {
public static void main(String[] args) {
int[] nums = {1,2,3,4};
int key = 2;
cal(nums,key,(v,k)->System.out.println(v+k));
}
private static void cal(int[]nums,int key,BiConsumer<Integer, Integer> bc) {
for(int n:nums) {
bc.accept(n, key);
}
}
}
以上程序主要完成对数组的处理方法,可以对数组里面的数据进行统一的运算,代码中统一加了key值,只要稍加改动就可以对数组进行另一种处理
cal(nums,key,(v,k)->System.out.println(v/k));
此时就会完成数组的除法运算,当使用除法时,如果key的值为0,那就会出现ArithmeticException。那我们该如何来处理这个异常呢?第一种方案在cal方法中添加异常处理模块
private static void cal(int[]nums,int key,BiConsumer<Integer, Integer> bc) {
for(int n:nums) {
try {
bc.accept(n, key);
} catch (ArithmeticException e) {
e.printStackTrace();
}
}
}
这种方案明显是不妥的,因为Biconsumer不一定会发生ArithmeticException,因为调用是在运行时刻才知道的,所以在这里捕获异常不合理,第二种方案是在cal的调用处处理异常
cal(nums,key,(v,k)->{
try {
System.out.println(v/k);
} catch (ArithmeticException e) {
e.printStackTrace();
}
});
这种方式虽然可以在调用时捕获异常,但这种方式和Lamdba的简洁方式有所违背,这更像是匿名内部类的处理方式,所以依然不建议使用这种方式,下面我们将会介绍第三种解决方案,基于封装模式的解决方案。
我们首先再添加一个方法wrapperLamdba
该方法用来封装Consumer这个表达式,并且传入参数BiConsumer
/**
* 封装一个Lamdba,传入一个BiConsumer并且返回BiConsumer,没有对请求做任何处理
*/
private static BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return bc;
}
该方法没有做任何处理,但参数是一个BiConsumer类型,此时在调用时就可以用这个函数进行封装
cal(nums,key,wrapperLamdba((v,k)->System.out.println(v/k)));
由于wrapperLamda什么都没有做,所以此处不会进行任何处理,如果修改wrapperLamdba的代码如下
private static BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return (v,k)-> System.out.println(v+k);
}
如果这样处理,我们会发现cal中调用的Lamdba不起作用了,因为不管cal中的lamdba是什么类型,最后都会返回+的操作,这说明我们可以通过封装的方法来替换原有的表达式,这样我们就可以在这个封装方法中进行异常处理,这带来的好处就是可以在一个位置集中处理所有异常信息,在设计和使用上更加合理一些
private static BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return (v,k)-> {
try {
bc.accept(v, k);
} catch (ArithmeticException e) {
System.out.println("发生异常:"+e.getMessage());
}
};
}
这种模式带来的好处是不仅仅可以处理异常,还可以加入一些限定条件,当不满足条件时,可以用新的表达式完成替换。完整代码如下:
public class TestLamdba {
public static void main(String[] args) {
int[] nums = {1,2,3,4};
int key = 0;
cal(nums,key,wrapperLamdba((v,k)->System.out.println(v/k)));
}
private static void cal(int[]nums,int key,BiConsumer<Integer, Integer> bc) {
for(int n:nums) {
bc.accept(n, key);
}
}
/**
* 封装一个Lamdba,传入一个BiConsumer并且返回BiConsumer,没有对请求做任何处理
*/
private static BiConsumer<Integer, Integer>
wrapperLamdba(BiConsumer<Integer, Integer> bc) {
return (v,k)-> {
try {
bc.accept(v, k);
} catch (ArithmeticException e) {
System.out.println("发生异常:"+e.getMessage());
}
};
}
}
方法引用
Lamdba表达式对方法的引用有一种特殊的写法,先看看下面的实例
public class SimpleObject {
public static void main(String[] args) {
Thread t = new Thread(()->printSth());
t.start();
}
private static void printSth() {
System.out.println("hello world!");
}
}
例子很简单,创建了一个Runnable的Lambda表达式,并且启动了该线程,()->printSth()
有另外一种表示方法,此时printSth是静态方法可以通过SimpleObject::printSth
来替换
public static void main(String[] args) {
//Thread t = new Thread(()->printSth());
Thread t = new Thread(SimpleObject::printSth);
t.start();
}
对于方法引用还有另外一种表示方法
public class FooObject {
public static void main(String[] args) {
// printSth(new User(1,"foo"), u->System.out.println(u));
//此时println会自动输出Consumer方法的参数,也就是u这个对象
printSth(new User(1,"foo"),System.out::println);
}
private static void printSth(User u,Consumer<User> c) {
c.accept(u);
}
}
class User {
private int id;
private String name;
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
public User() {
}
/*省略了getter和setter*/
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
java8中的集合遍历
在java8中增加了一些新的遍历方法,在jdk5中提供了一种增强的for循环来遍历数组和集合类
public static void main(String[] args) {
List<Student> stuList = Arrays.asList(
new Student("张三","001",19),
new Student("李四","005",22),
new Student("王五","010",14),
new Student("赵六","004",18),
new Student("何琦","006",12)
);
/**
* 增强的for循环
*/
for(Student stu:stuList) {
System.out.println(stu);
}
}
Java中提供了基于Lamdba表达式的遍历方式,非常类似javascript函数的闭包,通过forEach来进行遍历,先看看forEach的源码
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
参数是Consumer,所以只要以Lamdba的方法来编写就快速完成遍历
/**
* 等于执行了
for (T t : this) {
action.accept(t);
}
t就等于参数p
*/
stuList.forEach(p->System.out.println(p));
可以利用上一部分讲的函数引用方式来执行
//使用方法引用的方式来编写也可以
stuList.forEach(System.out::println);
完整代码:
public class TestCollection {
public static void main(String[] args) {
List<Student> stuList = Arrays.asList(
new Student("张三","001",19),
new Student("李四","005",22),
new Student("王五","010",14),
new Student("赵六","004",18),
new Student("何琦","006",12)
);
/**
* 增强的for循环
*/
for(Student stu:stuList) {
System.out.println(stu);
}
System.out.println("java8中新增的方法");
/**
* 等于执行了
for (T t : this) {
action.accept(t);
}
t就等于参数p
*/
stuList.forEach(p->System.out.println(p));
//使用方法引用的方式来编写也可以
stuList.forEach(System.out::println);
}
}
这就是Lamdba表达式的基本用法,只要掌握熟练之后,会提供很多简化的方式来提高开发效率,特别是在Spring5中经常使用基于Function的表达式,下一部分将会介绍Stream,Stream在Hibernate5中已经提供支持了。
网友评论