dromara / hutool

🍬A set of tools that keep Java sweet.
https://hutool.cn
Other
29.09k stars 7.5k forks source link

疑惑:使用Assert.notNull断言后,IDEA依然提示Method invocation 'string' may produce 'NullPointerException' #2028

Closed ahaha-vip closed 2 years ago

ahaha-vip commented 2 years ago

版本情况

JDK版本: java version "1.8.0_291" hutool版本: 5.6.2 IntelliJ IDEA 2021.3

问题描述(包括截图)

  1. 使用了Assert.notNull断言,IDEA检测到依然可能出现空指针,使用org.springframework.util.Assert.notNull则不会有这种提示。感觉是IDEA检测不到Hutool的断言做了非空校验,但是又没有找到比较好的方式来关闭这种提示。

image image

复现代码

    @Test
    public void okHttp() {
        OkHttpClient okHttpClient = new OkHttpClient().newBuilder().build();
        Request qryRequest = new Builder()
                .url("https://baidu.com")
                .build();

        try (Response response = okHttpClient.newCall(qryRequest).execute()) {
            // 解析响应码
            Assert.state(response.isSuccessful(), "调用接口失败");

            // 解析响应体
            ResponseBody responseBody = response.body();
            Assert.notNull(responseBody, "响应体为空");

            // 解析响应数据、
            // responseBody.string()方法IDEA提示“Method invocation 'string' may produce 'NullPointerException' ”
            String responseData = responseBody.string();
            System.out.println(responseData);
        } catch (IOException ignored) {
        }
    }
  1. 堆栈信息

  2. 测试涉及到的文件(注意脱密)

比如报错的Excel文件,有问题的图片等。

qclucky7 commented 2 years ago

我使用过程中ObjectUtil.isEmpty()也有这种问题, 换成ObjectUtil.isNull()就没有, 不太清楚idea是怎么检测的这种

looly commented 2 years ago

哎,我也奇怪,我本地也会有这个问题,IDEA不喜欢Hutool,哈哈。

cbqqkcel commented 2 years ago

spirng 使用的是简单语句做判断,所以 idea 能静态检查

public static void isTrue(boolean expression, String message) { if (!expression) { throw new IllegalArgumentException(message); } }

hutool 不知道为啥这么简单的判断非得写个函数调用,就无法触发静态语句检查了。 public static void isTrue(boolean expression, String errorMsgTemplate, Object... params) throws IllegalArgumentException { isTrue(expression, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params))); }

ahaha-vip commented 2 years ago

spirng 使用的是简单语句做判断,所以 idea 能静态检查

public static void isTrue(boolean expression, String message) { if (!expression) { throw new IllegalArgumentException(message); } }

hutool 不知道为啥这么简单的判断非得写个函数调用,就无法触发静态语句检查了。 public static void isTrue(boolean expression, String errorMsgTemplate, Object... params) throws IllegalArgumentException { isTrue(expression, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params))); }

因为重载的方法里面支持自定义异常,这里相当于代码复用了

cbqqkcel commented 2 years ago

可以提供一个类似 spring 简单的方法,再说Assert就是简单的判断,如果用自定义异常就没必要使用断言来做判断了。

ahaha-vip commented 2 years ago

加一个简单判断的重载确实可以解决这个warn,断言自定义异常可以三行变一行呀 [手动滑稽]=。=

cbqqkcel commented 2 years ago

不是复用的问题,应该是用了 lambda 函数调用的问题,简单函数调用都是没有问题的

下面的代码都是能正常检查出来的。


class Test {
    static void notNull(Object obj) {
        notNull0(obj);
    }

    static void notNull0(Object obj) {
        notNull1(obj);
    }

    static void notNull1(Object obj) {
        notNull2(obj);
    }

    static void notNull2(Object obj) {
        if (obj == null) {
            throw new IllegalArgumentException();
        }
    }
}
ahaha-vip commented 2 years ago
    public static <T> T notNull(T object) throws IllegalArgumentException {
        return notNull(object, "[Assertion failed] - this argument is required; it must not be null");
    }

    public static <T> T notNull(T object, String errorMsgTemplate, Object... params) throws IllegalArgumentException {
        return notNull(object, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params)));
    }

    public static <T, X extends Throwable> T notNull(T object, Supplier<X> errorSupplier) throws X {
        if (null == object) {
            throw errorSupplier.get();
        }
        return object;
    }

hutool 不知道为啥这么简单的判断非得写个函数调用,就无法触发静态语句检查了 因为重载的方法里面支持自定义异常,这里相当于代码复用了

我的意思是hutool断言的最下层有个支持自定义异常(用的函数式)的重载,因此就没有像Spring这样简单的固定抛出IllegalArgumentException,IntelliJ IDEA没有检测到的原因应该就是lambda。

    public static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new IllegalArgumentException(message);
        }
    }
cbqqkcel commented 2 years ago

嗯,是的,这个问题已经讨论清楚了。

ahaha-vip commented 2 years ago

今天研究了下,在断言方法上增加jetbrains的注解@Contract可以解决这个问题,缺点也就是有点侵入源码了

    <dependency>
        <groupId>org.jetbrains</groupId>
        <artifactId>annotations</artifactId>
        <version>23.0.0</version>
    </dependency>
    @Test
    public void testAssert() {
        Boolean obj = null;
        if (RandomUtil.randomBoolean()) {
            obj = true;
        }
        Optional<Object> optional = Optional.ofNullable(obj);

        isTrue(optional.isPresent(), RuntimeException::new);
//        cn.hutool.core.lang.Assert.isTrue(optional.isPresent(), RuntimeException::new);

        Object o = optional.get();
    }

    @org.jetbrains.annotations.Contract("true, _ -> fail")
    public static <X extends Throwable> void isFalse(boolean expression, Supplier<X> errorSupplier) throws X {
        if (expression) {
            throw errorSupplier.get();
        }
    }

    @org.jetbrains.annotations.Contract("false, _ -> fail")
    public static <X extends Throwable> void isTrue(boolean expression, Supplier<? extends X> supplier) throws X {
        if (!expression) {
            throw supplier.get();
        }
    }

https://youtrack.jetbrains.com/issue/IDEA-182726/Method-invocation-may-produce-NullPointerException-warning-while-its-checked-before

https://www.jetbrains.com/help/idea/contract-annotations.html

ahaha-vip commented 2 years ago

不侵入代码的解决方案:https://youtrack.jetbrains.com/issue/IDEA-301217

looly commented 2 years ago

@ahaha-vip 也就是需要设置IDE是吧?那就是用户端设置?

ahaha-vip commented 2 years ago

@looly 是的,用户端在断言方法上设置Contract的表达式就行

image image