首页 > 编程语言 >Java 实际开发中积累的几个小技巧

Java 实际开发中积累的几个小技巧

时间:2024-11-19 17:45:37浏览次数:3  
标签:积累 return 技巧 对象 判空 Java null public String

一、枚举类的注解

看起来很常见的枚举,可能也隐藏着使用上的问题:你有没有在代码里不小心做过改变枚举值的操作?或者为怎么合理规范地写构造方法/成员方法而烦恼?

那么不妨来看看我的示例,注释写得比较清楚了:

@Getter// 只允许对属性 get,不允许 set
@RequiredArgsConstructor// 为枚举的每个属性生成有参构造
public enum ProjectStatusEnum {
    SURVEY("已调研"),
    APPROVAL("已立项"),
    PROGRESSING("进行中"),
    COMPLETED("已完成");
    // 对该成员变量使用 final 来修饰,表明一旦赋值就不可变
    private final String name;
}

为什么要分享这个呢?团队里开发的时候还真有人在使用枚举的时候 set() 改变了枚举值,编译通过但运行在一定条件触发后,导致了 bug 排查了一下午。


二、RESTful 接口

本节的内容其实更像是一种规范,因为见过不少别的部门同事写的项目代码,接口的风格真是迥异(写什么的都有),当我接手重构的时候真是头皮发麻。

首先就是禁止使用 Swagger 和 Knife4j 接口文档生成工具,原因无它:代码侵入性太强和需要写的注解太多,而且还是公司安全漏洞扫描单上的常客。

其次可以使用开源的 smart-doc 来代替,只要遵循 Javadoc 的标准注释写法即可。

  • 请求方式和参数

在 Controller 中一般只使用 GET 请求和 POST 请求,无需使用 PUT 和 DELETE。

GET 请求一般只使用 @GetMapping,主要配合 @RequestParam、@PathVariable 使用,请求的拼接参数一般不超过 5 个。

POST 请求一般只使用 @PostMapping,主要配合 @RequestBody 使用,请求参数统一使用 DTO 对象。

如果入参需要带上请求头,可以加上 @RequestHeader 注解;如果是提交表单,可以加上 @Valid 做参数校验,如 @NotBlank 和 @NotNull 等。

  • 统一返回

主要包括:返回体(返回码+信息+返回数据)+ 封装VO + 统一异常,这些基础的东西肯定是遵顼团队/公司的开发规范,不需要再自己造轮子。

一个简单的 Controller 示例如下:

/**
 * 测试接口
 */
@RestController
@RequestMapping("/study")
public class StudyController {

    @Resource
    private StudyService studyService;

    /**
     * 新增xx
     * @return 是否成功
     */
    @PostMapping("/add")
    //还可以加上其它必要注解,如:登录/权限/日志记录等
    public Response<Boolean> addStudy(@RequestBody @Valid StudyDTO studyDTO) {
        return ResultUtils.success(studyService.addStudy(studyDTO));
    }

    /**
     * xx列表(不分页)
     * @return 列表数据
     */
    @GetMapping("/list")
    public Response<List<StudyListVO>> getList(@RequestParam("id") String id) {
        return ResultUtils.success(studyService.getList(id));
    }
}

三、类属性转换

在实际 Java 开发中,关于 VO、Entity、DTO 等对象属性之间的赋值是我们经常遇见的,最简单使用 @Data 去逐个 .set() 或者 @Builder 链式 .build(),其实都是很靠谱的办法,而且可以控制颗粒度。但属性一多起来的话,比如二十个以上,那么代码就会显得很长。所以有没有办法一行代码就搞定类属性转换呢?

首先不推荐使用 BeanUtils.copyProperties() 作类属性的拷贝,以下是几个常见的坑:

  1. 同一字段分别使用包装类型和基本类型,会出现转换异常,不会灵活识别转换

  2. null 值覆盖导致数据异常,即源属性有值为 null,但是目标属性有正常值,拷贝后会被 null 覆盖

  3. 内部类属性无法正常拷贝,即使类型和字段名均相同也无法拷贝成功,这个真的很坑

推荐泛型 + JSON组合的方式来实现类属性的转换,具体步骤如下:

  • 定义一个父类 CommonBean,让项目里所有 VO、Entity、DTO 等类都继承该类,类里面就只定义一个公共的泛型方法即可:

public class CommonBean implements Serializable {
    /**
     * @apiNote 全局类型转换方法:入参和返参均支持泛型
     * @param target
     * @return 目标类型
     * @param <T>
     */
    public <T> T copyProperties(Class<T> target) {
        //本质上就是进行了 Object -> json字符串 -> 到指定类型的转换
        return JSON.parseObject(JSON.toJSONString(this), target);
    }
}

  • 在需要转换的地方,直接调用上面定义的方法即可完成转换:

    @Test
    public void testCopyProperties(){
        //Worker 和 WorkerVO 都需要 extends 上述的 CommonBean
        Worker worker = new Worker();
        worker.setName("Alex");
        worker.setStatus(NumberUtils.INTEGER_ONE);
        //直接使用,得到需要的目标 VO 对象
        WorkerVO workerVO = worker.copyProperties(WorkerVO.class);
        log.info("转换结果:{}",workerVO);
    }

四、Stream 流

  • map() 流元素的映射、collect() 收集流,这两个就不展开讲了,几乎可以说是 Stream 流用的最多的方法,到处都是例子。

  • filter() 流按条件过滤和 sort() 流元素排序,这两个组合可以简单举个例子:

    /**
     * Stream 流的过滤与排序
     * @param id
     * @return 列表数据
     */
    @Override
    public List<StudyVO> getList(String id) {
       List<StudyVO> resultList = this.list(new LambdaQueryWrapper<Study>()
                .eq(Study::getIsDelete, NumberUtils.INTEGER_ZERO)).stream()
                .filter(e -> Constants.USER_ROLE_USER.equals(e.getUserRole()))
                .sorted(Comparator.comparing(Study::getAge).reversed())
                .map(e -> e.copyProperties(StudyVO.class)).collect(Collectors.toList());
        return Optional.of(resultList).orElse(null);
    }

像上述从MySQL 里查表数据的例子,其实能在数据库做的操作就没必要在 Stream 流里操作。像 .select()、.eq()、.gt()、.orderByDesc() 等都可以完成,非数据库语句查询的情况下,使用 Stream 操作集合还是有必要的。

  • anyMatch() 平时用的不多,但当遇到了之后,它的用法还是能让人眼前一亮的:

 /**
     * 测试 Stream 的 AnyMatch 方法
     * @return
     */
    public List<ArticleVO> testStreamAnyMatch(){
        List<Article> articleList = this.list(new LambdaQueryWrapper<Article>().eq(Article::getIsDelete, NumberUtils.INTEGER_ZERO));
        if (CollectionUtils.isNotEmpty(articleList)){
            //AnyMatch() 方法返回的是一个布尔,用来判断流中是否有满足条件的元素
            final boolean flag = articleList.parallelStream().anyMatch(e -> Objects.nonNull(e)
                    //文章要有内容
                    && Objects.nonNull(e.getContent())
                    //文章要有标题
                    && StringUtils.isNotBlank(e.getTitle()));
            if (flag){
                return articleList.parallelStream().map(e -> e.copyProperties(ArticleVO.class)).collect(Collectors.toList());
            }
        }
        return new ArrayList<>();
    }

  • skip() 跳过流的某些元素和 limit() 指定流元素的位置,组合起来使用可以实现自分页。这个在线上问题-如何避免集合内存溢出的文章中会拿出来单独讲一下。


五、判空和断言

5.1判空部分

首先什么情况下需要判空?基本是以下这 3 种情况:

1、当进行对象引用操作时

为了避免 NPE 通常需要先判断该对象是否为 null。比如,在调用对象的方法或访问其属性之前,应该首先判断该对象不为 null 。

@Test
public void testNullMethod(){
    //以下对象为 null,即表示该对象的变量在内存中不引用任何对象地址
    Study study = null;
    //则下面调用该对象的 get() 方法试图获得其属性,则会导致 NPE
    String userName = study.getUserName();
    log.info("用户名称:{}", userName);
}

2、从外部获取数据返回时

比如:从数据库查询数据返回、调用另一个接口的方法返回的数据,有可能返回对象的结果为 null:

    @Override
    public StudyVO detail(String id) {
        //查 MySQL 返回的集合数据,即使 null 赋值也没有问题
        List<Study> resultList = this.list(new LambdaQueryWrapper<Study>()
                .eq(Study::getIsDelete, NumberUtils.INTEGER_ZERO)
                .orderByDesc(Study::getAge));
        //但是同样需要对集合对象进行判空,有值再进行下一步
        if (CollectionUtils.isNotEmpty(resultList))
        {
            return resultList.get(NumberUtils.INTEGER_ZERO).copyProperties(StudyVO.class);
        }
        return new StudyVO();
    }

3、当集合进行元素操作时

由于集合本身并没有提供直接的判空机制,所以在进行元素操作之前,需要进行集合是否为空的判断:

    /**
     * 举个反例
     */
	@Override
    public StudyVO detail(String id) {
        //调用其它方法,并将返回结果赋值,是 null 也没问题
        List<StudyVO> voList = this.getList(id);
        //但在使用 get() 方法前不判断集合里有没有元素,那么会报数组越界
        return voList.get(NumberUtils.INTEGER_ZERO);
    }

那么,常用判空的工具有哪些呢?从我个人的开发经验来说主要有以下几种:

  • 对象的判空

推荐统一使用 java.util 包的 Objects.nonNull() 等方法。

  • 集合的判空

推荐统一使用 org.apache.commons.collections.CollectionUtils 包的 .isNotEmpty() 等方法。

  • Map 对象判空

推荐统一使用 Map 自带的 .isEmpty() 、 .containsKey()、.equals() 这三者配合使用。

  • 字符串的判空

推荐统一使用 org.apache.commons.lang3.StringUtils 包的 .isNotBlank() 等方法。

  • Optional类

 Optional
     //of(T value)方法用于创建一个包含指定值的 Optional 对象,该方法接收一个非 null 值作为参数
   .of()
     //ofNullable(T value)方法用于创建一个包含指定值的 Optional 对象,该方法接收一个可能为 null 的值作为参数
     .ofNullable()
     //isPresent()方法用于判断 Optional 对象中是否存在非 null 值,有值就返回 true ,否则返回 false
     .isPreset()
     //orElse(T other)方法顾名思义,泛型 T 表示其它的类型
     .orElse()
     //ifPresent(Consumer<? super T> consumer) 判断该对象是否值,有则调用传入的 Consumer 类型函数处理该值。否则,什么也不做
     .ifPresent()  
     //map(Function<? super T, ? extends U> mapper) 用于对 Optional 对象中的值进行映射,并返回一个新的 Optional 对象
     .map()
     //filter(Predicate<? super T> predicate) 用于过滤 Optional 对象中的值,只有当值满足特定条件时才保留
     .filter()

什么情况下可以不需要判空?

答:一般当方法允许直接返回 null 时,可以不对返回值进行判空。

5.2断言部分

断言的应用场景可能就比较单一了,我自己的理解是:在业务因为无数据且需要强制中断业务时,可以使用到断言。那么接口允许返回空、或者返回约定的状态码等这些情况除外。

一定程度上可以简化“先判断,再抛异常”的代码。即替换以下代码,三行变一行:

    if (Objects.nonNull(Object obj)){
        throw new BusinessException("obj error");
    }

//对象型断言:如果该对象为 null ,则抛出 String 类型的异常信息
Assert.notNull(@Nullable Object object, String message);

常用的断言如下:

//布尔型断言:如果布尔表达式为 false,则抛出 String 类型的异常信息
Assert.isTrue(boolean expression, String message);
    
//对象型断言:如果该对象为 null ,则抛出 String 类型的异常信息
Assert.notNull(@Nullable Object object, String message);

//字符串型断言:如果该字符串无内容,则抛出 String 类型的异常信息
Assert.hasText(@Nullable String text, String message)
    
//集合型断言:如果集合(包括 Map)对象无内容,则抛出 String 类型的异常信息
Assert.notEmpty(@Nullable Map<?, ?> map, String message);
Assert.notEmpty(@Nullable Collection<?> collection, String message);

文章小结

作为一个系列文章的开头,本文的内容偏基础。在之后的文章中我会分享一些关于真实项目中关于线上 bug 处理、缓存的使用、异步/解耦等内容,敬请期待。

那么 Java 实际开发中值得注意的几个小技巧的分享到这里就暂时结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

文章转载自:CodeBlogMan

原文链接:https://www.cnblogs.com/CodeBlogMan/p/18005657

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

标签:积累,return,技巧,对象,判空,Java,null,public,String
From: https://blog.csdn.net/kfashfasf/article/details/143876920

相关文章

  • JavaScript函数式编程指南
    前言本文内容来自于《JavaScript函数式编程指南》,可以看作是对原书内容进行提炼和总结,若您有需要或感觉有出入请参原书。一、走进函数式面向对象编程(OOP)通过封装变化使得代码更易理解。函数式编程(FP)通过最小化变化使得代码更易理解。——MichaelFeathers(Twitter)函......
  • cat-3.1.0 单机搭建监控 java
    官网:https://github.com/dianping/cat/wiki/readme_server下载:https://github.com/dianping/cat/releases/download/3.1.0/cat-home.warhttps://github.com/dianping/cat/archive/refs/tags/3.1.0.tar.gz参考文档:https://github.com/dianping/cat/wiki/readme_server本次......
  • JAVA反序列化学习-CommonsCollections5(基于ysoserial)
    环境准备JDK1.8(8u421)我以本地的JDK8版本为准、commons-collections(3.x4.x均可这里使用3.2版本)cc3.2:<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2</version>&l......
  • 来记录一下Java开发三年左右的心得体会
    Java开发三年心得大概一年多没有在CSDN上发表文章了,最近休息想来记录一下这几年的开发心得。深圳比亚迪的外包之路~开始北漂~24年被裁一周内又找到工作啦。最后的心得大概一年多没有在CSDN上发表文章了,最近休息想来记录一下这几年的开发心得。2021年大概冬天的时候......
  • java中使用Jackson代替fastjson进行序列化处理
    方法详解这里会列出常用方法的详解,更多方法可查阅jacksonapi文档ObjectMapper类是Jackson库的主要类。它提供一些功能将转换成Java对象匹配JSON结构对象转json字符串ObjectMapper通过writeValue系列方法将java对象序列化为json,并将json存储成不同的格式:String(writeVa......
  • 大学生HTML期末大作业——HTML+CSS+JavaScript南宁绿城
    HTML+CSS+JS【旅游网站】网页设计期末课程大作业web前端开发技术web课程设计网页规划与设计......
  • Java防止反编译的技术方案
    背景由于Java字节码的抽象级别较高,因此它们较容易被反编译。本文介绍了几种常用的方法,用于保护Java字节码不被反编译。通常,这些方法不能够绝对防止程序被反编译,而是加大反编译的难度而已,因为这些方法都有自己的使用环境和弱点。不同保护技术比较表以下几种技术都有不同的应用......
  • JAVA反序列化学习-CommonsCollections4(基于ysoserial)
    环境准备JDK1.8(8u421)这里ysoserial没有提及JDK版本的影响,我以本地的JDK8版本为准、commons-collections4(4.0以ysoserial给的版本为准)、javassist(3.12.1.GA)cc4.0、ClassPool<dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections......
  • python+vue基于django/flask的连锁超市销售管理系统(超市库存与销售管理平台)java+nodej
    目录技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取技术栈和环境说明本系统以Python开发语言......
  • python+vue基于django/flask的奖学金评定系统(奖学金申请与管理平台)java+nodejs+php-计
    目录技术栈和环境说明具体实现截图预期达到的目标系统设计详细视频演示技术路线解决的思路性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示研究方法感恩大学老师和同学源码获取技术栈和环境说明本系统以Python开发语言......