如何为不可变类创建默认构造函数
如何为不可变类创建默认构造函数
我喜欢根据这篇文章(为什么对象必须是不可变的)来使我的对象不可变。
然而,我正在尝试使用Jackson对象映射器解析对象。最初我遇到了JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.
我可以按这里所述进行修复,提供一个默认构造函数并使我的字段非final。
import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.NonNull; @AllArgsConstructor // @NoArgsConstructor(access = AccessLevel.PRIVATE) @Builder @Data public class School { @NonNull private final String schoolId; @NonNull private final String schoolName; }
有什么好的编程风格可以解决这个问题吗?唯一的解决方法是使我的对象可变吗?
我能不能使用不使用默认构造函数的不同映射器呢?
太长不想读?使用 lombok 并避免默认构造函数
- 使用
@Value
创建不可变数据类 - 使用
@JsonProperty("name-of-property")
为所有字段加注解 - 在
lombok.config
中添加lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty
,以将它们复制到生成的构造函数中 - 创建一个带有
@JsonCreator
注解的所有参数构造函数
示例:
@Value @AllArgsConstructor(onConstructor_ = @JsonCreator) class School { @JsonProperty("schoolId") String schoolId; @JsonProperty("schoolName") String schoolName; }
详细解答
有一个更好的替代方式来使用带有 @JsonCreator
注解的静态工厂方法,那就是拥有一个包含所有元素的构造函数(对于不可变类,这是必需的)。将其注释为 @JsonCreator
,并将所有参数用 @JsonProperty
注释,如下所示:
class School { //fields @JsonCreator public School( @JsonProperty("id") String id, @JsonProperty("name") String name) { this.schoolId = id; this.schoolName = name; } //getters }
@JsonCreator
注解提供了这些选项。它在文档中这样描述它们:
- 只有一个带有 JsonProperty 注释的构造方法或工厂方法,用于参数:如果是这样的话,这被称为“委托创建者”,在这种情况下,Jackson 首先将 JSON 绑定到参数的类型,然后调用创建者。这通常与 JsonValue(用于序列化)配合使用。
- 对于每个参数都使用 JsonProperty 或 JacksonInject 进行注释的构造方法/工厂方法,以指示要绑定的属性名称
在某些情况下,您甚至可能不需要明确指定参数名称。有关 @JsonCreator
进一步说明此类情况的文档如下:
此外,所有的JsonProperty注解必须指定实际名称(不是空字符串"default"),除非你使用其中一个扩展模块,可以检测参数名称。这是因为默认的JDK 8之前的版本无法从字节码中存储和/或检索参数名称。但是使用JDK 8(或使用像Paranamer这样的辅助库,或其他JVM语言,如Scala或Kotlin),指定名称是可选的。此外,版本为1.18.3或更高版本的lombok也可以与此良好地协作,其中您可以将lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty添加到lombok.config中,因此将其复制JsonProperty注解到构造函数,只要您确实使用它注释了所有字段(在我看来应该这样做)。要在构造函数上使用@JsonCreator注解,您可以使用实验性的onX功能。对于不可变数据类,您可以使用lombok的@Value,您的DTO可能看起来像这样(未经测试):
@Value //@AllArgsConstructor(onConstructor = @__(@JsonCreator)) // JDK7 or below @AllArgsConstructor(onConstructor_ = @JsonCreator) // starting from JDK8 class School { @JsonProperty("schoolId") String schoolId; @JsonProperty("schoolName") String schoolName; }
。
您可以使用 Jackson 工厂(用@JsonCreator注释的方法)从地图中读取字段并调用非默认构造函数:
class School { //fields public School(String id, String name) { this.schoolId = id; this.schoolName = name; } @JsonCreator public static School create(Mapobject) { return new School((String) object.get("schoolId"), (String) object.get("schoolName")); } //getters }
Jackson 将使用 JSON 的 Map
版本调用 create
方法。 这有效地解决了问题。
我认为您的问题是寻找 Jackson 解决方案,而不是新的模式/风格。