常见 Java 代码缺陷及规避方式(上)

简介: 常见 Java 代码缺陷及规避方式(上)



在日常开发过程中,我们会碰到各种各样的代码缺陷或者 Bug,比如 NPE、 线程安全问题、异常处理等。这篇文章总结了一些常见的问题及应对方案,希望能帮助到大家。



问题列表

 空指针异常


NPE 或许是编程语言中最常见的问题,被 Null 的发明者托尼·霍尔Tony Hoare)称之为十亿美元的错误。在 Java 中并没有内置的处理 Null 值的语法,但仍然存在一些相对优雅的方式能够帮助我们的规避 NPE。

  • 使用 JSR-305/jetbrain 等注解


  1. NotNull
  2. Nullable



通过在方法参数、返回值、字段等位置显式标记值是否可能为 Null,配合代码检查工具能够在编码阶段规避绝大部分的 NPE 问题建议至少在常用方法或者对外 API 中使用该注解,能够对调用方提供显著的帮助。


  • 用 Optional 处理链式调用


Optional 源于 Guava 中的 Optional 类,后 Java 8 内置到 JDK 中。Optional 一般作为函数的返回值强制提醒调用者返回值可能不存在并且能够通过链式调用优雅的处理空值。


public class OptionalExample {

    public static void main(String[] args) {
        // 使用传统空值处理方式
        User user = getUser();
        String city = "DEFAULT";
        if (user != null && user.isValid()) {
            Address address = user.getAddress();
            if (adress != null) {
                city = adress.getCity();
            }
        }
        System.out.println(city);

        // 使用 Optional 的方式
        Optional<User> optional = getUserOptional();
        city = optional.filter(User::isValid)
                .map(User::getAddress)
            .map(Adress::getCity)
              .orElse("DEFAULT")
        System.out.println(city);
    }

    @Nullable
    public static User getUser() {
        return null;
    }

    public static Optional<User> getUserOptional() {
        return Optional.empty();
    }

    @Data
    public static class User {
        private Adress address;
        private boolean valid;
    }

    @Data
    public static class Address {
        private String city;
    }
}


  • 用 Objects.equals(a,b) 代替 a.equals(b)


equals方法是 NPE 的高发地点,用 Objects.euqals来比较两个对象,能够避免任意对象为 null 时的 NPE。

  • 使用空对象模式


空对像模式通过一个特殊对象代替不存在的情况,代表对象不存在时的默认行为模式。常见例子:


用 Empty List 代替 null,EmptyList 能够正常遍历:

public class EmptyListExample {

    public static void main(String[] args) {
        List<String> listNullable = getListNullable();
        if (listNullable != null) {
            for (String s : listNullable) {
                System.out.println(s);
            }
        }

        List<String> listNotNull = getListNotNull();
        for (String s : listNotNull) {
            System.out.println(s);
        }
    }

    @Nullable
    public static List<String> getListNullable() {
        return null;
    }

    @NotNull
    public static List<String> getListNotNull() {
        return Collections.emptyList();
    }
}


空策略

public class NullStrategyExample {

    private static final Map<String, Strategy> strategyMap = new HashMap<>();

    public static void handle(String strategy, String content) {
        findStrategy(strategy).handle(content);
    }

    @NotNull
    private static Strategy findStrategy(String strategyKey) {
        return strategyMap.getOrDefault(strategyKey, new DoNothing());
    }

    public interface Strategy {
        void handle(String s);
    }

    // 当找不到对应策略时, 什么也不做
    public static class DoNothing implements Strategy {
        @Override
        public void handle(String s) {

        }
    }
}


 对象转化


在业务应用中,我们的代码结构往往是多层次的,不同层次之间经常涉及到对象的转化,虽然很简单,但实际上繁琐且容易出错。


反例 1:

public class UserConverter {

    public static UserDTO toDTO(UserDO userDO) {
        UserDTO userDTO = new UserDTO();
        userDTO.setAge(userDO.getAge());
        // 问题 1: 自己赋值给自己
        userDTO.setName(userDTO.getName());
        return userDTO;
    }

    @Data
    public static class UserDO {
        private String name;
        private Integer age;
        // 问题 2: 新增字段未赋值
        private String address;
    }

    @Data
    public static class UserDTO {
        private String name;
        private Integer age;
    }
}


反例2:

public class UserBeanCopyConvert {

    public UserDTO toDTO(UserDO userDO) {
        UserDTO userDTO = new UserDTO();
        // 用反射复制不同类型对象.
        // 1. 重构不友好, 当我要删除或修改 UserDO 的字段时, 无法得知该字段是否通过反射被其他字段依赖
        BeanUtils.copyProperties(userDO, userDTO);
        return userDTO;
    }
}


  • 使用 Mapstruct


Mapstruct 使用编译期代码生成技术,根据注解, 入参出参自动生成转化代码并且支持各种高级特性比如:

  1. 未映射字段的处理策略在编译期发现映射问题
  2. 复用工具方便字段类型转化
  3. 生成 spring Component 注解通过 spring 管理
  4. 等等其他特性
@Mapper(
    componentModel = "spring",
    unmappedSourcePolicy = ReportingPolicy.ERROR,
    unmappedTargetPolicy = ReportingPolicy.ERROR,
    // convert 逻辑依赖 DateUtil 做日期转化
    uses = DateUtil.class
)
public interface UserConvertor  {

    UserDTO toUserDTO(UserDO userDO);

    @Data
    class UserDO {
        private String name;
        private Integer age;
        //private String address;
        private Date birthDay;
    }

    @Data
    class UserDTO {
        private String name;
        private Integer age;
        private String birthDay;
    }

}

public class DateUtil {
    public static String format(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return simpleDateFormat.format(date);
    }
}


使用示例:

@RequiredArgsConstructor
@Component
public class UserService {
    private final UserDao userDao;
    private final UserCovertor userCovertor;

    public UserDTO getUser(String userId){
        UserDO userDO = userDao.getById(userId);
        return userCovertor.toUserDTO(userDO);
    }
}


编译期校验:


生成的代码:


@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2023-12-18T20:17:00+0800",
    comments = "version: 1.3.1.Final, compiler: javac, environment: Java 11.0.12 (GraalVM Community)"
)
@Component
public class UserConvertorImpl implements UserConvertor {

    @Override
    public UserDTO toUserDTO(UserDO userDO) {
        if ( userDO == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setName( userDO.getName() );
        userDTO.setAge( userDO.getAge() );
        userDTO.setBirthDay( DateUtil.format( userDO.getBirthDay() ) );

        return userDTO;
    }
}


常见 Java 代码缺陷及规避方式(中):https://developer.aliyun.com/article/1480647


目录
相关文章
|
11天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
25天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
35 5
Java反射机制:解锁代码的无限可能
|
21天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
57 3
|
27天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
75 10
|
22天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
21天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
29天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
31 6
|
7天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
6天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
6天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
下一篇
无影云桌面