优雅的Java:01.数据更新如何更优雅

在这里插入图片描述

编者按:如何写出更优雅的代码?这是一个恒久的问题,

在 Java 的世界里,SpringBoot 框架最为流程,几乎已经成为事实标准。本系列将围绕 SpringBoot 实战案例进行,介绍如何写出更优雅的 Java 代码。

笔者试图通过一个个的实际案例,抽丝剥茧,探讨 Spring 的设计哲学,探讨 Java 美学。

如何实现优雅的数据更新,最终我们呈现的美学将是这个样子:

在这里插入图片描述

数据更新案例

数据更新的业务逻辑,很常见,举例例子,这里有一个更新发票信息的业务逻辑,按常规的思路,很容易写出这样的代码:

public function updateInvoice(UpdateInvoiceForm form) {
    Invoice updateInvoice = new Invoice();
    updateInvoice.setId(form.getId());
    // 更新发票号
    if(StringUtils.isNotBlank(form.getInvoiceTaxNumber())) {
        updateInvoice.setInvoiceTaxNumber(form.getInvoiceTaxNumber());
    }
    // 税额
    if(form.getTaxAmount() != null) {
        updateInvoice.setTaxAmount(form.getTaxAmount());
    }
    //  税前金额
    if(form.getDrawDate() != null) {
        updateInvoice.setDrawDate(form.getDrawDate());
    }

    invoiceMapper.updateByPrimaryKeySelective(updateInvoice);
}

上面的逻辑也简单清晰,只有当某个上传的字段不为空时,才会更新数据库中相应的字段。那么,如果字段很多时,这样的代码就看起来非常冗余,那么,如何优化呢?

我们知道,Java 8 的 Optional 类,有着这样的用户:

  optionalVal.ifPresent(value -> xxx);

当前我们可以直接使用 Optional 类,将字段的更新逻辑封装在 Optional 类中,这样,代码就显得非常简洁,就像这样:

  Optional.ofNullable(form.getTaxAmount()).ifPresent(taxAmount -> {updateInvoice.setTaxAmount(taxAmount);})

不过这也有个问题,ifPresent 只支持判断对象是否为 null,无法过滤字符串为空的情况,基于这样的需要,我们可以仿照 Optional 类,自己实现一个封装

其核心代码像这样:

    public OptionalUtil<T> ifNotNull(Consumer<T> action) {
        if (value != null) {
            action.accept(value);
        }
        return this;
    }

    public OptionalUtil<T> ifNotBlank(Consumer<String> action) {
        if (value instanceof String && StringUtils.isNotBlank((String) value)) {
            action.accept((String) value);
        }
        return this;
    }

上面定义了两个核心方法,ifNotNull 和 ifNotBlank,分别用于判断对象是否为 null 和字符串是否为空,当满足条件时,再执行闭包里面的操作。

以下是完整的代码:

import java.util.function.Consumer;

import org.apache.commons.lang3.StringUtils;

public class OptionalUtil<T> {

    private final T value;

    private OptionalUtil(T value) {
        this.value = value;
    }

    public static <T> OptionalUtil<T> valueOf(T value) {
        return new OptionalUtil<>(value);
    }

    public OptionalUtil<T> ifBlank(Consumer<String> action) {
        if (value instanceof String && StringUtils.isBlank((String) value)) {
            action.accept((String) value);
        }
        return this;
    }

    public OptionalUtil<T> ifNotBlank(Consumer<String> action) {
        if (value instanceof String && StringUtils.isNotBlank((String) value)) {
            action.accept((String) value);
        }
        return this;
    }

    public OptionalUtil<T> ifNull(Consumer<T> action) {
        if (value == null) {
            action.accept(value);
        }
        return this;
    }

    public OptionalUtil<T> ifNotNull(Consumer<T> action) {
        if (value != null) {
            action.accept(value);
        }
        return this;
    }

    public OptionalUtil<T> ifZero(Consumer<T> action) {
        if (isZeroValue(value)) {
            action.accept(value);
        }
        return this;
    }

    private boolean isZeroValue(Object value) {
        if (value instanceof Number && ((Number) value).doubleValue() == 0.0d) {
            return true;
        }
        if (value instanceof java.util.Date && ((java.util.Date) value).getTime() <= 0) {
            return true;
        }
        return false;
    }

    public OptionalUtil<T> ifNotZero(Consumer<T> action) {
        if (!isZeroValue(value)) {
            action.accept(value);
        }
        return this;
    }

    public void execute() {
        // 可以用于触发链式调用的结束,也可以扩展为执行某些默认操作
    }
}

可以看到,这里面封装了一个 OptionalUtil 类,用于处理 Optional 类的链式调用。里面包括一个静态构造方法 valueOf, 同时支持各类不同的类型,
不论是 String,还是继承 Number 的类,如 Integer,Long,BigDecimal 等,进行判断。

时间 ifZero 也支持时间类型,如果传的时间为 1970-01-01 00:00:00 及以前的时间,也视做零值。

这样,我们的更新代码就可以写成这样:

public function updateInvoice(UpdateInvoiceForm form) {
    Invoice updateInvoice = new Invoice();
    updateInvoice.setId(form.getId());
    // 更新发票号
    OptionalUtil.valueOf(form.getInvoiceTaxNumber()).ifNotBlank(updateInvoice::setInvoiceTaxNumber);
    // 税额
    OptionalUtil.valueOf(form.getTaxPreAmount()).ifNotNull(updateInvoice::setTaxPreAmount);
    // 开票日期
    OptionalUtil.valueOf(form.getDrawDate()).ifNotZero(updateInvoice::setDrawDate);

    invoiceMapper.updateByPrimaryKeySelective(updateInvoice);
}

这样,优雅的数据更新就实现了,你觉得怎么样呢,欢迎在评论区交流。

java.lang.NullPointerException loginAction.LoginAction.abc(LoginAction.java:31) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) java.base/java.lang.reflect.Method.invoke(Method.java:566) ognl.OgnlRuntime.invokeMethod(OgnlRuntime.java:873) ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1539) ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68) com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethodWithDebugInfo(XWorkMethodAccessor.java:96) com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethod(XWorkMethodAccessor.java:88) ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1615) ognl.ASTMethod.getValueBody(ASTMethod.java:91) ognl.SimpleNode.evaluateGetValueBody(SimpleNode.java:212) ognl.SimpleNode.getValue(SimpleNode.java:258) ognl.Ognl.getValue(Ognl.java:467) ognl.Ognl.getValue(Ognl.java:431) com.opensymphony.xwork2.ognl.OgnlUtil$3.execute(OgnlUtil.java:351) com.opensymphony.xwork2.ognl.OgnlUtil.compileAndExecuteMethod(OgnlUtil.java:403) com.opensymphony.xwork2.ognl.OgnlUtil.callMethod(OgnlUtil.java:349) com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:436) com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:291) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:252) org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:253) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:247) com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:177) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterIntercept
03-15
<think>嗯,用户遇到了Java中的NullPointerException异常,具体是在LoginAction的第31行。我需要先理解NullPointerException通常发生的原因。这个异常通常是因为试图访问一个未被初始化的对象,或者对象为null的情况下调用了它的方法或访问了属性。所以,用户需要检查第31行附近的代码,看看是否有对象可能为null。 首先,我应该让用户检查第31行的代码,确定哪一部分引发了异常。比如,是否有方法调用,比如user.getName(),或者某个对象被访问了属性或方法。例如,如果user对象是null,那么调用user.getName()就会抛出NullPointerException。 然后,建议用户使用调试工具或者在异常发生的地方打印相关变量的值,确认哪个变量是null。另外,可以提醒用户检查方法的参数传递是否正确,是否有可能返回null的方法调用,比如从数据库查询用户信息时没有找到结果,返回了null,这时候如果不做判空处理,就会出问题。 还有,用户提到的引用[1]中的情况,是否有异常处理器导致了递归的问题。虽然用户的问题看起来是NullPointerException,但可能和异常处理配置有关,比如在异常处理时跳转到某个页面,而该页面又触发了同样的异常,导致循环。不过这种情况可能比较少见,但可以作为检查点之一。 另外,建议用户进行空值检查,比如在调用对象的方法之前,使用if (object != null)来确保对象不为null。或者使用Java 8的Optional类来优雅地处理可能的null值。 还有可能是在获取用户输入或者配置时,某些参数没有被正确初始化,比如表单提交的数据没有正确绑定到对象,导致某些字段为null。这时候需要检查数据绑定的过程是否正确,是否有验证机制。 最后,可以给出一个示例代码,展示如何在代码中加入空值检查,或者使用try-catch块来捕获异常,但好的做法是预防而非捕获,所以重点还是在预防null的情况。 用户可能需要具体的代码示例,所以提供一个简单的例子,比如在调用方法前检查对象是否为null,或者使用断言等机制。同时,提醒用户单元测试的重要性,通过测试覆盖可能的null情况,提前发现问题。 总结一下,解决步骤应该是:1.定位问题代码行,找出可能的null对象;2.检查数据来源,确保对象被正确初始化;3.添加空值检查或使用Optional处理;4.检查异常处理逻辑是否导致递归等问题;5.测试验证修复效果。</think>### 解决Java中NullPointerException异常问题的步骤 #### 1. 定位问题代码行 检查`LoginAction`第31行的具体代码,例如: ```java String username = user.getName(); // 假设user对象为null时触发异常 ``` 此时若`user`未初始化或未正确赋值,调用`getName()`会抛出`NullPointerException`。 #### 2. 空值检查与防御性编程 - **添加判空逻辑**: ```java if (user != null) { String username = user.getName(); } else { // 处理null情况(例如记录日志或返回错误信息) } ``` - **使用Java 8 Optional**: ```java Optional.ofNullable(user) .ifPresent(u -> System.out.println(u.getName())); ``` #### 3. 数据来源验证 - 若`user`来自外部输入(如表单提交或数据库查询),需确保数据绑定或查询逻辑正确: ```java // 例如使用Spring框架时,检查@ModelAttribute绑定 public String login(@ModelAttribute("user") User user) { if (user == null) { throw new IllegalArgumentException("用户数据未正确绑定"); } // 其他逻辑 } ``` #### 4. 异常处理逻辑检查 若异常处理器(如全局`ExceptionHandler`)将`NullPointerException`重定向到`login!input`,而该路径又可能触发相同异常,会导致递归[^1]。需确认异常处理逻辑是否闭环: ```java @ExceptionHandler(NullPointerException.class) public String handleNPE() { // 避免直接重定向到可能触发相同异常的路径 return "errorPage"; } ``` #### 5. 单元测试验证 通过测试覆盖可能为null的场景: ```java @Test(expected = IllegalArgumentException.class) public void testLoginWithNullUser() { LoginAction action = new LoginAction(); action.login(null); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少湖说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值