【问题标题】:Avoid null checks?避免空检查?
【发布时间】:2013-06-28 21:16:58
【问题描述】:

我想知道是否有可能在 Java 中“避免”空检查,以这段代码为例:

@Override
public List<AccountBean> search(AccountConstraint... c) {
    if (c.length == 0) {
        throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
    }
    try {
        List<AccountBean> beans = new ArrayList<>();
        for (AccountConstraint ac : c) {
            Builder builder = new QueryBuilder.Builder("SELECT * FROM accounts");
            if (ac.getAccountId() != null) {
                builder.clause("accountId >= " + ac.getAccountId().getMin() + " AND accountId <= " + ac.getAccountId().getMax());
            }
            if (ac.getUsername() != null) {
                builder.clause("username = \"" + ac.getUsername() + "\"");
            }
            if (ac.getPassword() != null) {
                builder.clause("password = \"" + ac.getPassword() + "\"");
            }
            if (ac.getEmail() != null) {
                builder.clause("email = \"" + ac.getEmail() + "\"");
            }
            PreparedStatement ps = connection.prepareStatement(builder.build().getQuery());
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                beans.add(new AccountBean(rs));
            }
        }
        return beans;
    } catch (SQLException ex) {
        throw new RuntimeException(ex);
    }
}

它必须检查 4 次 != null,否则代码将失败。

如果没有 NullPointerException,是否可以将 if (object != null) 语句转换为 执行的单行语句?当出现异常时,应该忽略该行。

我在这里不是在谈论通用语言功能,我说的是只有在您明确决定这样做时才会上交的功能。

例如:NullCheck(builder.clause("username = \"" + ac.getUsername() + "\"")); 将是建议代码的 sn-p。

在 Java 中可以实现类似的功能吗?

如果不可能的话,Java 8 中是否可以直接在方法中使用方法(voids)?

那么这样的代码真的可以工作吗?

public static NullCheck(Void void) {
    try {
        void.execute();
    }
    catch (NullPointerException e) {
        //ignore
    }
}

我知道我可以将方法放在它自己的类中,该类扩展具有方法 execute() 的接口,然后传递该类,但这会破坏摆脱空检查或任何可能的目的的目的更复杂。

问候。

警告:我在这里使用 PreparedStatement 的方式很容易发生 SQL 注入。不要重复使用此代码。

【问题讨论】:

  • 我也想知道这个。我认为没有——如果你看看像 Oozie 这样的大型开源包,它们有一个空类检查或函数。
  • 我希望这种方法不用于用户输入(它可能是),因为它是等待发生的巨大 SQL 注入攻击。您应该正确使用 PreparedStatement(使用参数,而不是硬编码值)。
  • @jtahlborn 感谢您的提醒。我确实完全忘记了正确使用它,但是我在插入/删除中正确使用了它,所以只是遗憾地忘记了它。再次感谢。
  • 考虑这是否值得。你在感知的优雅中获得了什么,你在清晰中失去了。这看起来很难看的基本原因是单个语句 if 的 { } (这是一个常见的误解,即它们是良好风格所必需的)。没有它,声明就是if (ac.getEmail() != null) builder.clause("email = \"" + ac.getEmail() + "\"");,它比任何“优雅”的解决方案都简洁易懂。

标签: java nullpointerexception


【解决方案1】:

您可以通过假设这些方法不返回空值来避免这些检查。

你怎么能假设这个?通过让AccountConstraint 的规范这样说。然后AccountConstraint 中的代码负责确保值不为空,而不是您的search 方法负责处理空值。您可能需要更改 AccountConstraint 的设计才能执行此操作。

如果你的假设是错误的,会发生什么?也就是说,如果AccountConstraint 有问题。将引发异常,这是您没有预料到的。但这就是当您有错误时可能发生的情况:引发意外异常。调试代码会很容易,因为堆栈跟踪会显示AccountConstraint 的哪个方法返回了无效的空值。

【讨论】:

  • 不能做出此假设,因为AccountConstraint 中的null 值表明该特定字段没有约束。也是一个友好的提示,请修改您的答案,因为整个 null 的东西与 AccountConstraint 而不是 AccountBean 有关。
【解决方案2】:

是和否。

有两种方法可以解决空问题:

特殊运算符,例如 Groovy 中的安全导航运算符。如果x.y 抛出NullPointerException x?.y,则只返回null。由于 Java 不允许创建新的运算符,因此您不能在 Java 中执行此操作。像这样的运算符在 JDK8 中被考虑过,但在哪里被丢弃。如果您想要这样的东西,请切换到 Groovy 或具有此功能的许多其他语言之一。

特殊类许多语言都有一个特殊的接口来表示可能是null 的值。在 Scala 中,它被称为 Option。 Option 有两种实现方式:None + Some。无替换 null。每当您想对该值做某事时,您都不会直接使用它,而是在 Option 上调用 map 并使用函数作为参数。如果它实际上是一个 None,则什么也不会发生,您只需返回 None。如果它是 Some,则函数会在该值上执行,并且您会得到一个带有结果的 Option。这样您就可以一直使用 Options,而不必担心 nulls。

实际上它现在很特别,因此您可以使用 Java 自己创建这样的类。问题只是,Java 没有函数,所以你必须使用匿名类。这使得整个事情变得非常麻烦,并且只是一个理论上的选择。

JDK8 有一个Option 类。据我所知,它缺少map 方法,这在我看来使整个事情成为一个糟糕的笑话。但是由于最重要的工具(匿名函数)是由常见的嫌疑人之一(谷歌、Apache ...)提供的适当的选项实现

【讨论】:

    【解决方案3】:

    就目前而言,您可能可以编写类似的方法

    public void clauseIfNotNull(Builder builder, String format, Object o) {
      if (o != null) {
        builder.clause(String.format(format, o));
      }
    }
    

    然后看起来像clauseIfNotNull(builder, "username = \"%s\"", ac.getUsername());

    除此之外,Java 7 无能为力。

    【讨论】:

    • Java 8 怎么样?是否已经对此有任何具体了解?
    • 当然,您可以编写一个方法来表示“如果这会引发 NullPointerException,则取消它,它甚至可以在 Java 8 中获得良好的可读性,尽管它不会那么高效。
    • @skiwi 我实际上并不认为你能够在 Java 8 中写出更好的东西。我认为这个答案看起来很不错,我会做同样的事情。
    【解决方案4】:

    在 Builder 上创建一个最小的适配器对象

    class NotNullClauseAdapter
    {
       private final Builder builder;
       public NotNullClauseAdapter(Builder builder) {
          this.builder = builder;
       }
       public void clause(String format, Object o) {
          if (o != null) {
            builder.clause(String.format(format, o));
          }
       }
    }
    

    在您的代码中使用它:

    for (AccountConstraint ac : c) {
      Builder builder = new QueryBuilder.Builder("SELECT * FROM accounts");
      NotNullClauseAdapter adapter = new NotNullClauseAdapter(builder);
      if (ac.getAccountId() != null) {
          builder.clause("accountId >= " + ac.getAccountId().getMin() + " AND accountId <= " + ac.getAccountId().getMax());
      }
      adapter.clause("username = \"%s\"", ac.getUserName());
      adapter.clause("password = \"%s\"", ac.getPassword));
      adapter.clause("email = \"%s\"", ac.getEmail());
      PreparedStatement ps = connection.prepareStatement(builder.build().getQuery());
      ResultSet rs = ps.executeQuery();
      while (rs.next()) {
        beans.add(new AccountBean(rs));
      }
    }
    

    您可以通过向适配器添加更多子句方法来扩展以处理特定对象(如范围),以便转换 accountId 等内容,例如

    public void clauseMinMax(String format, Range r) {
      if (r != null) {
         builder.clause(String.format(format, r.getMin(), r.getMax()));
      }
    }
    

    accountId 行然后变为(如果 getAccountId() 返回 Range 对象):

    adapter.clauseMinMax("accountId >= %d AND accountId <= %d", ac.getAccountId());
    

    【讨论】:

      【解决方案5】:

      使用 JSR305 并使用适当的 @Nonnull 注释,您不必进行空值检查,这些注释会为您完成。

      使用来自 JSR305 的 @Nonnull@CheckReturnValue 注释有助于表达对 null 和返回值检查的需求。开发人员最好描述实现的预期行为以供以后使用和静态代码分析。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-01
        • 1970-01-01
        • 2019-12-07
        • 2016-12-01
        • 1970-01-01
        • 2020-04-01
        • 2017-03-21
        • 1970-01-01
        相关资源
        最近更新 更多