【问题标题】:java8 - absent variable & Optionaljava8 - 缺少变量和可选
【发布时间】:2015-07-16 13:56:42
【问题描述】:

我正在解析输入 JSON。对于一个字段,有3种可能:

  • 该字段不存在;
  • 值设置为空;
  • 该值设置为有效值。

实现了不同的行为:对于JSON中不存在的值,将默认值插入到数据库中;对于 JSON 中的空值,将空值插入到数据库中。

我想过用Optional 来建模:

public class Data {
    private Optional<String> field;
}

以下两个选项中哪个最有意义?

  1. 如果 field 为 null,则 JSON 中不存在该字段。如果 field 为 Optional.empty,则 JSON 中的字段为 null
  2. 如果 field 为 null,则 JSON 中的字段为 null。如果 field 是 Optional.empty,则 JSON 中不存在该字段。

FWIW,我正在使用带有模块 jackson-datatype-jdk8 的 Jackson 来解析输入 JSON。

【问题讨论】:

  • 字段被省略是什么意思?字段为空是什么意思?是一样的吗?
  • 在缺少字段的情况下的行为应该与在 null 的情况下不同吗?
  • 实现了不同的行为。对于不存在的值,将默认值插入到数据库中。对于空值,将空值插入到数据库中。
  • 以同样的理由你可以拥有Optional&lt;Optional&lt;String&gt;&gt;。拥有三态 Property&lt;String&gt;("default") 或这样的特殊类在语义上似乎更容易。
  • @JoopEggen 你能详细说明一下答案吗?

标签: java json jackson java-8


【解决方案1】:

我认为您不应该在这种情况下使用Optional。正如@dkatzel 在他的回答中提到的那样,它应该被用作 API 返回值而不是字段。

尽管有这种学术讨论,但您只需将 Data 类中的字段初始化为其默认值即可完成您想要的操作:

public class Data {
    private String field = DEFAULT_VALUE;
}

然后让杰克逊做剩下的。


根据 OP 的评论进行编辑:

当您的 JSON 带有 fieldnull 值时,Jackson 会将其设置为 null,这就是将存储在数据库中的内容。

当您的 JSON 不包含该字段时,DEFAULT_VALUE 将自动加载到您的 Data 实例中。

当您的 JSON 确实包含 field 的值时,Jackson 将设置它,并且该值将到达数据库。


编辑 2,考虑到 OP 要求在解析 JSON 输入后找出 field 是否已填写、设置为 null 或在输入 JSON 中不存在:

如果,解析输入 JSON 后,您需要知道 field 是否被填写、设置为 null 或不存在,然后考虑这个例子,它显示了方法我会接受:

public class Data {

    private String field1 = "hello";

    private Integer field2 = 10;

    private Double field3 = 3.75;

    private static final Data DEFAULTS = new Data(); // defaults will be kept here

    public String getField1() {
        return this.field1;
    }

    public void setField1(String field1) {
        this.field1 = field1;
    }

    public Integer getField2() {
        return this.field2;
    }

    public void setField2(Integer field2) {
        this.field2 = field2;
    }

    public Double getField3() {
        return this.field3;
    }

    public void setField3(Double field3) {
        this.field3 = field3;
    }

    @Override
    public String toString() {
        return "Data [field1=" + this.field1 + 
                   ", field2=" + this.field2 + 
                   ", field3=" + this.field3 + "]";
    }

    public boolean isDefault(Function<Data, Object> getter) {
        Object defaultProperty = getter.apply(DEFAULTS);
        Object actualProperty = getter.apply(this);
        return defaultProperty != null // needed to support fields with no default value
            && defaultProperty.equals(actualProperty);
    }

    public boolean isNull(Function<Data, Object> getter) {
        return getter.apply(this) == null;
    }

    public boolean isSet(Function<Data, Object> getter) {
        return !this.isNull(getter) && !this.isDefault(getter);
    }
}

这里我使用了private static 属性来保存Data 的默认值,并使用3 种方法来查询任何字段状态(默认、null 或设置)。为了确定要查询哪个字段,这些方法接收一个Function&lt;Data, Object&gt;,给它们一个Data 实例并返回一个应该是所需字段的Object。 (如果你停下来想一想,getter 可以被视为将实例作为输入并返回实例的特定字段的函数。

所以稍后,当您需要知道某个字段是如何到达您的 JSON 输入时,只需使用这 3 种查询方法来找出:

ObjectMapper m = new ObjectMapper();

String json = "{\"field1\":null,\"field2\":20}";
Data data = m.readValue(json, Data.class);

System.out.println(data); // Data [field1=null, field2=20, field3=3.75]

System.out.println("field1 default ? " + data.isDefault(Data::getField1)); // false
System.out.println("field1 null ? " + data.isNull(Data::getField1)); // true
System.out.println("field1 set ? " + data.isSet(Data::getField1)); // false

System.out.println("field2 default ? " + data.isDefault(Data::getField2)); // false
System.out.println("field2 null ? " + data.isNull(Data::getField2)); // false
System.out.println("field2 set ? " + data.isSet(Data::getField2)); // true

System.out.println("field3 default ? " + data.isDefault(Data::getField3)); // true
System.out.println("field3 null ? " + data.isNull(Data::getField3)); // false
System.out.println("field3 set ? " + data.isSet(Data::getField3)); // false

【讨论】:

  • 这意味着无法区分缺失值和之后的 null 值,这是需要的。
  • @Dave 等等,您需要稍后知道什么时候使用了默认值吗?我以为你问的是只解析 JSON。
  • @Dave 再次更新了我的答案,希望这一次会有用。
  • @Dave 在这种情况下,isDefault() 将返回 true。我认为如果传入的值等于默认值,那么该值是否设置都没有关系。在这种情况下,我选择让isDefault() 返回true
  • @Dave 关于equals()==,我建议您始终使用equals() 并在第一行检查==
【解决方案2】:

我会说第一个选项最符合语义。它还可能使计算更容易。

java中的字段为null,表示缺少值,匹配第一个选项。

我建议您将这些字段存储在哈希映射中,其中键是 JSON 字段名称,值是 JSON 字段的值。我还建议您不要在此处使用可选项(因为它会增加不必要的复杂性),而应在 hashmap 中使用 null 或非 null 对象。

HashMap<String, Value> jsonFields = new HashMap<String, Value>();
boolean hasField1 = false;
Value field1Value = null;
if(jsonFields.contains("field1"){ // It is present in the JSON file
    field1Value = jsonFields.get("field1"); // "null" here would mean that the JSON field was set to "null"
    hasField1 = true;
}

【讨论】:

  • 这正是 jackson-datatype-jdk8 正在做的事情。这对我来说似乎与直觉相反,因此是这个问题。
  • @DaveTeezo 什么似乎违反直觉?
  • null 的意思是 absentOptional.empty null
  • 顺便说一句,我的评论是在您对哈希映射进行编辑之前发表的。我指的是您的评论,说第一个选项最有意义。
  • @DaveTeezo 啊,我想这是一个主观问题。我想说 Optional.empty() 也是一种直接说“不存在”的方式,但以一种防止 NullPointerExceptions 的方式。 huguesjohnson.com/programming/java/java8optional.html
【解决方案3】:

第二个选择对我来说更有意义。 null 表示null,空表示不存在。

但是,Optional 不应该真正用作字段。 It's supposed to be used as an API return value.

您能否将数据存储在允许空值的Map 中?如果地图中不存在键(您的字段),则返回Optional.empty

【讨论】:

  • 这意味着所有字段都缩减为一个Map?
  • 可能,不一定是一切,只是我猜可能是可选的那些
【解决方案4】:

也没有?我会用@DefaultValue() 注释我的 POJO 字段。那么您的可能性是 JSON 中指定的空值或非空值,或者如果从 JSON 中省略该字段,则为默认值。然后,您可以只保留 POJO,而无需任何特殊的每个字段分析。

【讨论】:

  • 数据库保存默认值,可以是复杂查询。使用@DefaultValue 意味着您将其复制到应用程序代码中,并且您将默认值发送到数据库。
  • 那么如果您没有在查询中包含默认值,那么数据库会提供默认值吗?然后同意注释默认值不是一个好的选择。您可以使用特殊的默认字符串值(例如“USE_DB_DEFAULT”)进行注释,只要存在不会与有效值冲突的字符串即可。持久化时检查该特殊字符串。
【解决方案5】:

如果您处理的是Object 而不是String,这是一个我觉得很优雅的解决方案:

  • 如果没有值则使用Optional.empty();
  • 如果有值则使用Optional.of(value)
  • 如果值为空,则使用Optional.of(specialValue)

其中specialValue 是一个您可以轻松测试的静态单例,例如:ObjectUtils.NULL(来自 commons.lang)。

然后您可以轻松测试您的可选:

if (optional.isPresent()) {
    if (ObjectUtils.NULL.equals(optional.get())) {
        // value is there and null
    } else {
        // value is there and not null
    }
} else {
    // value is not there 
}

【讨论】:

    猜你喜欢
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-03
    • 2010-09-16
    • 2011-04-28
    • 2015-07-25
    • 1970-01-01
    相关资源
    最近更新 更多