工作中常用到的Java8特性

1,844 阅读4分钟

Lambda表达式

lambda表达式是是一个匿名函数,即没有函数名的函数

举个我们平时创建线程的栗子

 //Runnable实现类
public class RunnableImpl implements Runnable {
     @Override
     public void run() {
         logger.info("Runnable实现类创建并启动一个新的线程");
     }
 }
 ​
 //采用传统的Runnable对象作为参数新建并启动一个线程
 new Thread(new RunnableImpl()).start();
 ​
 //采用匿名内部类的方式新建并启动一个线程
 new Thread(new Runnable() {
     @Override
     public void run() {
         logger.info("匿名内部类方式创建并启动一个新的线程");
     }
 }).start();
 ​
 //对匿名内部类进行简化,写成lambda表达式
 new Thread(()-> logger.info("lambda表达式创建并启动一个新的线程")).start();
 ​

优点:lambda表达式相比传统的Runnable对象作为参数传递给Thread类方式创建线程的方式,代码更简洁

方法引用

方法引用可以将一个方法封装成一个变量。::双冒号是方法引用的符号

 //方法引用,将字符串100转换成Integer类型
 Function<String, Integer> function = Integer::parseInt;
 Integer IntegerResult= function.apply("100");

方法引用的返回值类型是函数式接口

被引用方法的参数个数,类型,返回值类型需要和函数式接口中方法的声明一致,只要满足这个要求,可以返回任意类型的函数式接口

 //Function函数式接口,apply方法接收T类型的变量,返回R类型的结果
 @FunctionalInterface
 public interface Function<T, R> {
     R apply(T t);
 }
 ​
 Integer.paseInt()方法接收字符串类型参数,返回Integer类型的结果
    
 //比较两个int值大小,下面两种返回值类型均可
 Comparator<Integer> compare = Integer::compare;
 int compare1 = compare.compare(1, 2);
 ​
 IntBinaryOperator compare = Integer::compare;
 int compare1 = compare.applyAsInt(1, 2);

在java.util.function包下有很多函数式接口

image.png

自定义一个函数式接口

 //函数式接口的run方法会接收T和S类型两个参数,返回R类型的结果
 @FunctionalInterface
 public interface KiteFunction<T,R,S> {
     
     R run(T t, S s);
 }

自定义一个和KiteFunction的run方法队对应的方法

 //dateFormat方法的功能是按照指定的格式,把日期进行格式化,返回字符串的日期
 public class TimeConverse {
 ​
     public static String dateFormat(LocalDateTime localDateTime,String pattern){
         DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
         String dateStr = localDateTime.format(dateTimeFormatter);
         return dateStr;
     }
 }
 KiteFunction<LocalDateTime, String, String> dateFormat = TimeConverse::dateFormat;
 String dateStr = dateFormat.run(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");
 System.out.println(dateStr);

如果TimeConverse的dateFormat方法只会被调用一次,我们也可以直接将其在kiteFunction接口的run方法中实现(原理类似于Runnable接口创建启动一个新线程),这种是匿名内部类的方式

 String dateStr2 = new KiteFunction<LocalDateTime, String, String>() {
             @Override
             public String run(LocalDateTime localDateTime, String pattern) {
                 DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
                 return localDateTime.format(dateTimeFormatter);
             }
 ​
 }.run(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");

上面匿名内部类可以使用lambda表达式的形式进行替代

 String dateStr2 = ((KiteFunction<LocalDateTime, String, String>) (localDateTime, pattern) -> {
             DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
             return localDateTime.format(dateTimeFormatter);
         }).run(LocalDateTime.now(), "yyyy-MM-dd HH:mm:ss");

Stream流

Stream流中大量运用了方法引用和lambda表达式,并提供了很多简单易用封装好的工具API,使用这些API我们可以快速高效地对复杂数据进行处理

  1. flatMap 对多维数据进行扁平化处理
  2. peek函数类似于foreach函数,区别是foreach函数执行后没有返回结果,而peek返回了Stream,但是如果执行peek函数后,没有对数据进行收集操作,peek函数内部的方法不会被执行
  3. reduce函数,对数据进行累加操作
 //reduce方法定义
 Optional<T> reduce(BinaryOperator<T> accumulator);
 //实际用法,对List<Integer>中的元素进行累加求和
 Integer result = stream.reduce((x, y) -> x + y).get();
 x是Stream中的第一个元素,y是第二个元素
 //reduce重载方法定义
 T reduce(T identity, BinaryOperator<T> accumulator);
 //实际用户,累加求和时先赋一个初始值
 stream.reduce(100, (x, y) -> x + y)
 累加时,identity会作为第一个参数

Stream流程常用方法

 Trader raoul = new Trader("Raoul", "Cambridge");
 Trader mario = new Trader("Mario", "Milan");
 Trader alan = new Trader("Alan", "Cambridge");
 Trader brian = new Trader("Brian", "Cambridge");
 ​
 List<Transaction> transactions = Arrays.asList(
     new Transaction(brian, 2011, 300),
     new Transaction(raoul, 2012, 1000),
     new Transaction(raoul, 2011, 400),
     new Transaction(mario, 2012, 710),
     new Transaction(mario, 2012, 700),
     new Transaction(alan, 2012, 950)
 );
 //1、找到2011年所有的交易,并对交易值进行倒叙排序
 List<Transaction> collect = transactions.stream().filter(t -> t.getYear() == 2011).sorted(Comparator.comparing(Transaction::getValue).reversed()).collect(Collectors.toList());
 System.out.println(collect);
 //2、交易员都在哪些不同的城市工作过
 transactions.stream().map(Transaction::getTrader).map(Trader::getCity).distinct().forEach(e->System.out.println(e));
 //3、判断有无交易员在米兰城市待过
 boolean isExit = transactions.stream().map(Transaction::getTrader).map(Trader::getCity).anyMatch(e -> "Milan".equals(e));
 System.out.println(isExit);
 //4、打印生活在剑桥的交易员的所有交易金额
 long sum = transactions.stream().filter(transaction -> "Cambridge".equals(transaction.getTrader().getCity())).collect(Collectors.summarizingInt(Transaction::getValue)).getSum();
 System.out.println("剑桥总交易金额"+sum);
 //5、所有的交易中,交易值最大的是
 Transaction transaction = transactions.stream().max(Comparator.comparing(Transaction::getValue)).get();
 System.out.println(transaction);

总结

我们平时说的函数式编程,函数即方法引用,即Lambda表达式 Stream流可以提高我们的编程效率,但是也需要合理的使用StreamAPI,涉及到复杂的业务逻辑时需要写好注释