代码之家  ›  专栏  ›  技术社区  ›  atamanroman

Java14记录空对象模式?

  •  3
  • atamanroman  · 技术社区  · 5 年前

    public class Id {
    
      public static final Id NULL_ID = new Id();
    
      private String id;
    
      public Id(String id) {
        this.id = Objects.requireNonNull(id);
      }
    
      private Id() {}
    }
    

    但这行不通,因为每个构造函数都需要经过规范( Id(String id super() 绕过不变量。

    public record Id(String id) {
      public static final Id NULL_ID = null; // how?
    
      public Id {
        Objects.requireNonNull(id);
        // ...
      }
    }
    

    现在我和

    public Id {
      if (NULL_OBJECT != null)
        Objects.requireNonNull(id);
    }
    

    我没有发现很多关于记录背后的设计思想的讨论,这可能已经讨论过了。如果它是这样保持简单,这是可以理解的,但这感觉很尴尬,我已经在小样本中多次碰到这个问题。

    0 回复  |  直到 5 年前
        1
  •  2
  •   Alex R    5 年前

    不,在Java14中当前的记录定义中,您所需要的是不可能的。每个记录类型都有一个规范构造函数,可以隐式定义,也可以显式定义。每个非规范构造函数都必须从调用此记录类型的另一个构造函数开始。这基本上意味着,对任何其他构造函数的调用肯定会导致对规范构造函数的调用。 [8.10.4 Record Constructor Declarations in Java 14]

    如果这个规范构造函数执行参数验证(它应该,因为它是公共的),那么您的选项是有限的。要么遵循前面提到的建议/解决方法,要么只允许用户通过接口访问API。如果选择最后一种方法,则必须从记录类型中删除参数验证并将其放入接口中,如下所示:

    public interface Id {
        Id NULL_ID = new IdImpl(null);
    
        String id();
    
        static Id newIdFrom(String id) {
            Objects.requireNonNull(id);
            return new IdImpl(id);
        }
    }
    
    record IdImpl(String id) implements Id {}
    

    我不知道你的用例,所以这可能不是你的选择。但是,你想要的现在是不可能的。

    JavaDoc for Records in Java 15 ,这似乎没有改变。我找不到真正的规范,JavaDoc中指向它的链接导致404,所以可能他们已经放宽了规则,因为有些人 complained about them .

        2
  •  2
  •   rzwitserloot    5 年前

    代码中的基本错误

    final 显然应该是这样。

    有两个概念看似相似,甚至相同,但实际上并非如此。

    这是 未知/未找到/不适用 概念。例如:

    Map<String, Id> studentIdToName = ...;
    String name = studentIdToName.get("foo");
    

    应该怎么办 name 如果 "foo"

    别那么快-在你回答之前:好吧,也许吧 "" -那会导致各种各样的问题。如果您编写的代码错误地认为所使用的id肯定在这个映射中,那么这是一个既成事实:这个代码被窃听了。句号。我们现在所能做的就是确保这个错误被尽可能“好”地处理。

    说这话 名称 null 这里,是绝对优越的:bug现在是显式的,堆栈跟踪指向有问题的代码。没有堆栈跟踪并不能证明代码没有错误-根本不是。如果这段代码返回空字符串,然后发送一封电子邮件到一个空邮件地址,该地址的正文中包含一个空字符串,其名称应该是空的,这比抛出NPE的代码糟糕得多。

    对于这样一个值(notfound/unknown/not applicable),java中没有任何内容比得上 作为价值。

    然而,在使用记录了可能返回的api时,会经常发生什么 ,或“找不到”的值,或“未找到”不适用的“API” 调用者希望将其视为已知的方便对象 .

    例如,如果我总是大写并修改学生姓名,并且有些ID已经映射到“not Enrolized Another”,并且这显示为已映射到空字符串,那么调用者就可以非常方便地希望 对于这个特定的用例 Map API满足这一要求:

    String name = map.getOrDefault(key, "").toUpperCase().trim();
    if (name.isEmpty()) return;
    // do stuff here, knowing all is well.
    

    .

    所以,既然我们已经确定了“空对象”不是您想要的,但是“空对象”是很好的选择,请注意它们应该很方便。调用者已经决定了他们想要的某些特定行为;他们明确地选择了这种行为。他们不想仍然要处理那些需要特殊处理的独特的价值观 Id id 字段为空无法通过便利性测试。

    您需要的大概是一个快速、不可变、易于访问的Id,并且Id.notnull有一个空字符串。像 "" ,或类似的 List.of() . "".length() 有效,并返回0。 someListIHave.retainAll(List.of()) 有效,并清除列表。这就是工作上的方便。这是一种危险的便利性(如果您不希望看到一个具有某些已知行为的虚拟对象,那么不当场出错可以隐藏错误),但这就是为什么调用方必须显式地选择使用它,例如通过使用 getOrDefault(k, THE_DUMMY) .

    private static final Id EMPTY = new Id("");
    

    您可能需要空值具有特定的行为。例如,有时您希望空对象也具有唯一的属性;其他Id实例都不能被视为与它相等。

    你可以用两种方法来解决这个问题:

    1. 隐藏的布尔值。

    我认为“隐藏的布尔”足够明显。一个私有布尔字段,私有构造函数可以将其初始化为true,所有可公开访问的构造函数都设置为false。

    使用空作为标识有点棘手。例如,它看起来像这样:

    @Override public boolean equals(Object other) {
        if (other == null || !other.getClass() == Id.class) return false;
        if (other == this) return true;
        if (other == EMPTY || this == EMPTY) return false;
        return ((Id) other).id.equals(this.id);
    }
    

    EMPTY.equals(new Id("")) 事实上是假的,但是 EMPTY.equals(EMPTY) 是真的。

    推荐文章