美文网首页
Java基础之Lamdba表达式02之深入表达式

Java基础之Lamdba表达式02之深入表达式

作者: 孔浩 | 来源:发表于2017-12-16 15:51 被阅读0次

上一讲给大家介绍了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中已经提供支持了。

相关文章

网友评论

      本文标题:Java基础之Lamdba表达式02之深入表达式

      本文链接:https://www.haomeiwen.com/subject/gewswxtx.html