微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决将 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,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 {
}

解决方法

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

这就像问“为绿色添加一个角”。没有任何意义。

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

您没有注释符合模式并可用作方法引用的方法(或构造函数)。

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

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

有病人问医生:“医生,按这里就疼!”

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

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

此检查有一个正确的版本:当且仅当 linter 工具具有整个方法层次结构的完整视图(因此不仅是方法本身,还有覆盖它的所有方法,该方法覆盖的所有方法) ,以及将来可能覆盖它的所有外部到此代码库的方法),并且对于所有这些方法忽略该参数,然后可以警告关于它。

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

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

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

没有。您可以创建一个带有第二个参数(abstract 类型)的第二个构造函数,只是为了让它完全忽略这个参数,但是如果它是一个智能linting,因为根据定义,构造函数不能覆盖某些东西,也不能被覆盖,因此将“忽略的参数”检查限定为有用,这不是很讽刺吗?

我强烈建议不要创建一个实际上需要以下 javadoc 的构造函数:

int

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

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

那不行;注释不会像这样“元”。您可以注释一个注释定义,但这并不意味着“用这个注释注释一个事物意味着用所有这些注释对事物进行注释”。这可能意味着,但前提是注释(和相关工具!)被定义为这样工作,因为它没有内置到 java.lang. /** * 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>. */ 不能那样工作。

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

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

@SuppressWarnings

这个方法不需要javadoc,也不需要注释:方法名+public class WhateverYouHaveThere { public static RowMapper asRowMapper() { return (rs,idx) -> new WhateverYouHaveThere(rs); } } 修饰符就100%覆盖了它的用途,实现一点也不奇怪。

然后,当然,修复你的 linter 中的笨蛋设置 :) – 它会警告这种方法的事实足以证明它只是妨碍并且没有给你任何关于可疑代码的有意义的见解。

>

然后它让你写:

static

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

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

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

,

感谢 rzwitserloot 的详细回答。 他让我想到了 asRowMapper 的另一种变体:

@FunctionalInterface
public interface SimpleRowMapper<T> {
    T mapRow(ResultSet rs) throws SQLException;
}

public class RowMapperUtil {
    public static <T> RowMapper<T> asRowMapper(SimpleRowMapper<T> mapper) {
        return (resultSet,rowNum) -> mapper.mapRow(resultSet);
    }
}

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

构造函数不依赖于一种用法:它可以被重用于其他目的(是否是另一个@FunctionalInterface),是干净的,并且 asRowMapper 不会被复制/粘贴/适应每个 ValueObject(如果我们添加方法的日志记录或其他问题,我们可以在一个地方完成)。

唯一的缺点是它是一个 Util 类:这些类很容易通过许多不相关的方法激增,所有这些方法都受到“Feature Envy”代码味道的影响。

为了解决这个问题,将我们的依赖项封装到我们拥有的接口/类中总是一个好主意,这样我们就可以根据项目的使用情况来定制它们。 这意味着将 NamedParameterJdbcTemplate 封装到我们拥有的一种代理类中,并且我们可以在其上添加我们自己的方法,采用 SimpleRowMapper 参数:

public <T> T queryForObject(String sql,SqlParameterSource paramSource,SimpleRowMapper<T> rowMapper) throws DataAccessException { ... }

这样,我们可以保留只有一个参数的构造函数,并且在没有 asRowMapper() 的情况下仍然使用它:

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

和/或,如果 Spring 认为用例足够重要以提供支持,请要求 Spring 将这个新方法包含到他们自己的 NamedParameterJdbcTemplate 中。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。