首页 > 解决方案 > 将 Java 构造函数注释为实现 @FunctionalInterface

问题描述

我想使用 Spring 的这个功能接口:

@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

这是一种通过显式声明将只调用构造函数的 RowMapper 常量来使用它的方法:

namedParameterJdbcTemplate.queryForObject(sql, parameters, ValueObject.ROW_MAPPER);

public class ValueObject {
    public static final RowMapper<ValueObject> ROW_MAPPER = (resultSet, rowNum) -> new ValueObject(resultSet);

    public final long field;

    public ValueObject(ResultSet resultSet) throws SQLException {
        field = resultSet.getLong("FIELD");
    }
}

你看:我不使用 rowNum 参数。

我想要一个更简洁和富有表现力的代码。我想直接使用构造函数而不必声明 RowMapper:

namedParameterJdbcTemplate.queryForObject(sql, parameters, ValueObject::new);

public class ValueObject {
    public final long field;

    public ValueObject(ResultSet resultSet, int unusedRowNumFromRowMapperInterface) throws SQLException {
        field = resultSet.getLong("FIELD");
    }
}

干净多了,但 IDE 和 Sonar 现在抱怨未使用的参数。

我可以将 @SuppressWarnings({"unused", "java:S1172"}) 添加到该参数。但这会污染干净的解决方案:我不希望项目中的其他开发人员盲目地为他们创建的每个 ValueObject 复制/粘贴这个 vaudou 咒语。而且我也不希望他们创建一个常量+一个构造函数样板。

有没有办法通知编译器构造函数实际上正在实现 RowMapper @FunctionalInterface,以便它知道第二个参数是必需的,即使未使用?

或者另一种不太直接的方式来干净地摆脱警告?

我尝试创建此注释以使用有意义的名称注释未使用的参数,封装删除警告的实现细节,但这也不起作用:

@Target(ElementType.PARAMETER)
@Inherited
@SuppressWarnings({"unused", "java:S1172"})
public @interface ThisParameterIsFromRowMapperInterface {
}

标签: javalambdaconstructorwarningsfunctional-interface

解决方案


将 Java 构造函数注释为实现 @FunctionalInterface

这就像问“为绿色添加一个角”。这毫无意义。

FunctionalInterface 的要点是将接口标记为定义“函数”,在某种意义上,您可以在需要该接口的值的地方编写 lambda 语法( (a, b) -> result)或方法引用构造( ),然后 javac 将foo::bar自动为您修复问题,使其正常工作。

您不会注释适合该模式并且可以用作方法引用的方法(或构造函数)。

干净多了,但 IDE 和 Sonar 现在抱怨未使用的参数。

你听过关于医生的比喻吗?

一位病人问医生:“医生,我按这里就疼!”

医生说:“好吧。好吧,那就别这样了!”

问题在于您的 IDE/Sonar,而不是您的代码。关闭“检查器”/“linter”功能,这很愚蠢,默认情况下是关闭的,所以有人打开它,认为(错误地)这是一个有用的检查。

此检查有一个正确的版本:当且仅当 linter 工具具有整个方法层次结构的完整视图(因此不仅是方法本身,而且覆盖它的所有方法,该方法覆盖的所有方法,以及所有external-to-this-codebase 方法,将来可能会覆盖它),并且所有这些方法的参数都会被忽略,那么就可以发出警告。

鉴于 linter 没有水晶球,首先要确保方法是有效final或有效(包)私有的,以便将不可知的外部集合减少到 0。检查不能适用于任何非最终公开的任何东西:也许该参数的存在是为了覆盖该方法的代码。(想一想:当你有一个abstract方法时,所有参数都被“忽略”,因为根本没有代码!)

如果启用了更智能的拍摄,这里就不会出现警告:根据定义,lambdas 会覆盖某些东西。

有没有办法通知编译器构造函数实际上正在实现 RowMapper @FunctionalInterface,以便它知道第二个参数是必需的,即使未使用?

那没有。您可以创建第二个构造函数,它采用第二个参数(类型int),只是为了完全忽略此参数,但如果它是智能 linting,实际上触发该 linter 警告,因为根据定义,构造函数不能覆盖某些东西,也不能被覆盖,因此有资格进行“忽略参数”检查是有用的,这不是很讽刺吗?

我强烈建议不要创建一个有效地需要以下 javadoc 的构造函数:

/**
 * This constructor completely ignores the second parameter.
 * It is intended to be used in the form of `MyType::new`,
 * when you need a `RowMapper`.
 * <strong>NB: Any other use is neccessarily a bug</strong>.
 */

因为,好吧,读它。使用粗体警告来解释事物的预期用途令人惊讶是不好的:您不希望代码库中出现意外,也不希望在不阅读文档的情况下可能会被误解的方法。

我试图创建这个注解来用有意义的名称来注解未使用的参数,封装删除警告的实现细节,但这也不起作用

那是行不通的;注释不会像这样“元”。您可以对注释定义进行注释,但这并不意味着“使用此注释注释事物意味着使用所有这些注释对事物进行注释”。这可能意味着,但前提是注释(和相关的工具!)被定义为这样工作,因为它不是内置在 java 中的。@SuppressWarnings不能那样工作。

好的,那么在这种情况下我该怎么

我建议你尝试这样的事情:

public class WhateverYouHaveThere {
    public static RowMapper asRowMapper() {
        return (rs, idx) -> new WhateverYouHaveThere(rs);
    }
}

这个方法不需要javadoc也不需要注释:方法名+static修饰符100%覆盖了它的目的,实现一点也不奇怪。

然后,当然,修复你的 linter 中的愚蠢设置 :) - 它会警告这个方法的事实足以证明它只是妨碍你,而不是让你对可疑代码有任何有意义的见解。

然后它让你写:

public static final RowMapper<ValueObject> ROW_MAPPER = ValueObject.asRowMapper();

注意:您也可以将其设为静态字段:

public class ValueObject {
    public static final RowMapper<ValueObject> ROW_MAPPER =
      (rs, idx) -> new ValueObject(rs);
}

但我不会。更多的是一种风格。许多支持 getter 而非公共 final 字段的论点也适用于此。基本上:在将来给自己更多的灵活性来添加日志记录、更改实现等。


推荐阅读