整理沃尔玛项目中Java 8使用到的相关方法及使用

简介: Java8应用学习

Stream

Java 8 API添加了一个新的java.util.stream工具包,称为流Stream,可以以一种声明的方式处理数据

+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

用一个简单的代码演示:

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取集合中大于2、并且经过排序、平方去重的有序集合
List<Integer> squaresList = numbers
        .stream()
        .filter(x -> x > 2)
        .sorted((x,y) -> x.compareTo(y))
        .map( i -> i*i).distinct().collect(Collectors.toList());

流的来源可以是 ==集合,数组,I/O channel, 产生器generator==等
limit方法用于获取指定数量的流
sorted方法用于对流进行排序
map方法用于映射每个元素到对应的结果
filter方法用于通过设置的条件过滤出元素
forEach方法用于迭代流中的每个数据
Collectors类实现了很多归约操作,例如将流转换成集合和聚合元素Collectors可用于返回列表或字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);

在沃尔玛项目中Stream类用到是非常广泛的,例如:

DeductionHandleEnum dedcutionHandleEnum = optionalDedcutionHandleEnum.get();  
Set<String> purchaseNoSet = deductBillDataList.stream().map(deductBillBaseData -> deductBillBaseData.getPurchaserNo()).collect(Collectors.toSet());  
Set<String> sellerNoSet =  deductBillDataList.stream().map(deductBillBaseData -> deductBillBaseData.getSellerNo()).collect(Collectors.toSet());

Lambda 表达式

Lambda 表达式,也称为闭包,它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。
Lambda 编程风格,可以总结为四类:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号
  • 可选的的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值

    可选类型声明

    在使用过程中,我们可以不用显示声明参数类型,编译器可以统一识别参数类型,例如:
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));

上面代码中的参数s1s2的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

Collections.sort(names, (String s1, String s2) -> s1.compareTo(s2));

运行之后,两者结果一致!

可选的参数圆括号

当方法那只有一个参数时,无需定义圆括号,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

但多个参数时,需要定义圆括号,例如:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

可选的大括号

当主体只包含了一行时,无需使用大括号,例如:

Arrays.asList( "a", "b", "c" ).forEach( e -> System.out.println( e ) );

当主体包含多行时,需要使用大括号,例如:

Arrays.asList( "a", "b", "c" ).forEach( e -> {
   
    System.out.println( e );
    System.out.println( e );
} );

可选的返回关键字

如果表达式中的语句块只有一行,则可以不用使用return语句,返回值的类型也由编译器推理得出,例如:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

如果语句块有多行,可以在大括号中指明表达式返回值,例如:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
   
    int result = e1.compareTo( e2 );
    return result;
} );

变量作用域

还有一点需要了解的是,Lambda 表达式可以引用类成员和局部变量,但是会将这些变量隐式得转换成final,例如:

String separator = ",";
Arrays.asList( "a", "b", "c" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "c" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );

两者等价!

同时,Lambda 表达式的局部变量可以不用声明为final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义),例如:

int num = 1;
Arrays.asList(1,2,3,4).forEach(e -> System.out.println(num + e));
num =2;
//报错信息:Local variable num defined in an enclosing scope must be final or effectively final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量,例如:

int num = 1;
Arrays.asList(1,2,3,4).forEach(num -> System.out.println(num));
//报错信息:Variable 'num' is already defined in the scope

沃尔玛中的Lambda表达式应用

比如协议单合并结算单的定时任务中

lockClient.tryLock(KEY, () -> {
     
            log.info("Agreement-MergeSettlement job 获取锁");  
            try {
     
                agreementSchedulerService.makeSettlementByScheduler(false);  
            } catch (Exception e) {
     
                log.error(e.getMessage(), e);  
            }  
            log.info("Agreement-MergeSettlement job 获取锁结束");  
        }, -1, 1);

java.lang.Runnable函数作为一个方法的参数,在拿到锁后再run运行函数。
在记录日志履历中

if(CollectionUtils.isNotEmpty(mergeData.getMergeDeductList())){
     
    mergeData.getMergeDeductList().forEach(entity -> operateLogService.addDeductLog(entity.getId(),  
            entity.getBusinessType(), TXfDeductStatusEnum.getEnumByCode(entity.getStatus()),  
            entity.getRefSettlementNo(), OperateLogEnum.AGREEMENT_MATCH_BLUE_INVOICE_FAILED,  
            "组合生成结算单", 0L, "系统"));  
}

对合并数据遍历用Lambda表达式将每个数据的信息都添加到日志当中。

函数式接口

数接口指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式。

但是在实践中,函数式接口非常脆弱,只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface,举个简单的函数式接口的定义:

@FunctionalInterface
public interface GreetingService {
   

    void sayMessage(String message);
}

Java7 只能通过匿名内部类进行编程,例如:

GreetingService greetService = new GreetingService() {
   

    @Override
    public void sayMessage(String message) {
   
        System.out.println("Hello " + message);
    }
};
greetService.sayMessage("world");

Java8 可以采用 Lambda 表达方进行编程,例如:

GreetingService greetService = message -> System.out.println("Hello " + message);
greetService.sayMessage("world");

目前 Java 库中的所有相关接口都已经带有这个注解了,实践上java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子!

方法引用

方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码
方法引用使用一对冒号::,通过方法的名字来指向一个方法。

public class Car {
   

    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
   
        return supplier.get();
    }

    public static void collide(final Car car) {
   
        System.out.println("Collided " + car.toString());
    }

    public void follow(final Car another) {
   
        System.out.println("Following the " + another.toString());
    }

    public void repair() {
   
        System.out.println("Repaired " + this.toString());
    }
}

构造器引用

它的语法是Class::new,或者更一般的Class< T >::new,实例如下:

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

静态方法引用

它的语法是Class::static_method,实例如下:

cars.forEach( Car::collide );

类的成员方法引用

cars.forEach( Car::repair );

实例对象的成员方法的引用

它的语法是instance::method,实例如下

final Car police = Car.create( Car::new );
cars.forEach( police::follow );

沃尔玛项目中经常使用的是Stream流与方法引用的联合使用,例如超期协议单合并结算单(仅供定时任务调用),兼容数电类型方法中有:

List<TXfBillDeductEntity> plusDeductList = tXfBillDeductExtDao.querySuitablePositiveBillList(overdueDayNum  
            ,TXfDeductionBusinessTypeEnum.AGREEMENT_BILL.getValue()  
            , TXfDeductStatusEnum.AGREEMENT_NO_MATCH_SETTLEMENT.getCode()  
            , TXfDeductStatusEnum.UNLOCK.getCode());  
List<String> businessNos = plusDeductList.stream().map(TXfBillDeductEntity::getBusinessNo).collect(Collectors.toList());

先再数据库中读取出业务单据数组,然后用Stream流将各个业务单据映射成各个业务单据的业务单据编号,最后将流转换成数组返回。

Optional类

Java应用中最常见的bug就是空值异常。在Java8之前,GoogleGuava引入了Optional是类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java8也将Optional加入了官方库。
Optional 提供了一些有用的方法来避免显式的 null 检查,我们可以通过以下实例来更好的了解 Optional 类的使用!

public class OptionalTester {
   

    public static void main(String[] args) {
   
        OptionalTester tester = new OptionalTester();
        Integer value1 = null;
        Integer value2 = new Integer(10);

        // Optional.ofNullable - 允许传递为 null 参数
        Optional<Integer> a = Optional.ofNullable(value1);

        // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
        Optional<Integer> b = Optional.of(value2);
        System.out.println(tester.sum(a,b));
    }

    public Integer sum(Optional<Integer> a, Optional<Integer> b){
   

        // Optional.isPresent - 判断值是否存在

        System.out.println("第一个参数值存在: " + a.isPresent());
        System.out.println("第二个参数值存在: " + b.isPresent());

        // Optional.orElse - 如果值存在,返回它,否则返回默认值
        Integer value1 = a.orElse(new Integer(0));

        //Optional.get - 获取值,值需要存在
        Integer value2 = b.get();
        return value1 + value2;
    }
}

在沃尔玛项目中Optional类也经常被用到,例如在判断数电与税控时需要判断isUpgrades字段的值,但之前没有数电升级就没有这个字段,所以有个对象isUpgrades值为空,就需要赋予默认值0

mergeData.setIsUpgrades(Optional.ofNullable(plusDeduct.getIsUpgrades()).orElse(IsUpgradesEnum.IS_NOT_UPGRADES.getCode()));
相关文章
|
18小时前
|
设计模式 Java 开发者
如何在Java项目中实现领域驱动设计(DDD)
如何在Java项目中实现领域驱动设计(DDD)
|
19小时前
|
消息中间件 Java 中间件
如何在Java项目中实现分布式事务管理
如何在Java项目中实现分布式事务管理
|
19小时前
|
消息中间件 Java 中间件
如何在Java项目中实现高效的消息队列系统
如何在Java项目中实现高效的消息队列系统
|
1天前
|
前端开发 搜索推荐 JavaScript
如何成功完成一个Java项目答辩
如何成功完成一个Java项目答辩
5 1
|
1天前
|
SQL XML JavaScript
【若依Java】15分钟玩转若依二次开发,新手小白半小时实现前后端分离项目,springboot+vue3+Element Plus+vite实现Java项目和管理后台网站功能
摘要: 本文档详细介绍了如何使用若依框架快速搭建一个基于SpringBoot和Vue3的前后端分离的Java管理后台。教程涵盖了技术点、准备工作、启动项目、自动生成代码、数据库配置、菜单管理、代码下载和导入、自定义主题样式、代码生成、启动Vue3项目、修改代码、以及对代码进行自定义和扩展,例如单表和主子表的代码生成、树形表的实现、商品列表和分类列表的改造等。整个过程详细地指导了如何从下载项目到配置数据库,再到生成Java和Vue3代码,最后实现前后端的运行和功能定制。此外,还提供了关于软件安装、环境变量配置和代码自动生成的注意事项。
|
1天前
|
设计模式 Java
设计模式在Java项目中的实际应用
设计模式在Java项目中的实际应用
|
1天前
|
Java 编译器
一篇文章讲明白java键盘输入多种方法
一篇文章讲明白java键盘输入多种方法
|
1天前
|
Java
用java 将 jb2 文件转图片的方法
用java 将 jb2 文件转图片的方法
|
2天前
|
jenkins Java 持续交付
运用Jenkins实现Java项目的持续集成与自动化部署
在新建的Jenkins Job中,我们需要配置源码管理,通常选择Git、SVN等版本控制系统,并填入仓库地址和凭据。接着,设置构建触发器,如定时构建、轮询SCM变更、GitHub Webhook等方式,以便在代码提交后自动触发构建过程。
21 2
|
3天前
|
监控 Java
优化Java应用的日志记录方法
优化Java应用的日志记录方法