简体   繁体   English

如何为不可变类创建默认构造函数

[英]How to create default constructor for immutable class

I like to make my objects immutable based on this article (Why objects must be immutable) . 我喜欢根据本文使我的对象不可变(为什么对象必须不可变)

However, I am trying to parse an object using Jackson Object Mapper. 但是,我正在尝试使用Jackson Object Mapper解析对象。 I was initially getting JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object. 我最初得到的是JsonMappingException: No suitable constructor found for type [simple type, class ]: cannot instantiate from JSON object.

I could fix it as mentioned here , by providing a default constructor and making my fields non-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;
}

What is a good programming style that I should follow to overcome this problem? 为了克服这个问题,我应该遵循什么良好的编程风格? Is the only way around is to make my objects mutable? 唯一的解决方法是使我的对象可变吗?

Can I use a different mapper that does not use the default constructor? 我可以使用不使用默认构造函数的其他映射器吗?

You can use a Jackson factory (method annotated with @JsonCreator ) that reads fields off a map and calls your non-default constructor: 您可以使用Jackson工厂(用@JsonCreator注释的方法)从地图上读取字段并调用您的非默认构造函数:

class School {
    //fields

    public School(String id, String name) {
        this.schoolId = id;
        this.schoolName = name;
    }

    @JsonCreator
    public static School create(Map<String, Object> object) {
        return new School((String) object.get("schoolId"), 
                          (String) object.get("schoolName"));
    }

    //getters
}

Jackson will call the create method with a Map version of the json. 杰克逊将使用JSON的Map版本调用create方法。 And this effectively solves the problem. 这有效地解决了问题。

I believe your question looks for a Jackson solution, rather than a new pattern/style. 我相信您的问题是寻找Jackson解决方案,而不是寻求新的模式/风格。

TL;DR: using lombok and avoiding a default constructor TL; DR:使用lombok并避免使用默认构造函数

  • make immutable data class using @Value 使用@Value制作不可变的数据类
  • annotate all your fields with @JsonProperty("name-of-property") @JsonProperty("name-of-property")注释所有字段
  • add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config to copy those to generated constructors lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty添加到您的lombok.config ,将其复制到生成的构造函数中
  • create an all-args constructor annotated with @JsonCreator 创建一个用@JsonCreator注释的全参数构造@JsonCreator

example: 例:

@Value
@AllArgsConstructor(onConstructor_ = @JsonCreator)
class School {
    @JsonProperty("schoolId")
    String schoolId;
    @JsonProperty("schoolName")
    String schoolName;
}

long answer 长答案

There is an imo better alternative to a static factory method annotated with @JsonCreator , and that is having a constructor for all Elements (as is required for immutable classes anyway). 使用@JsonCreator注释的静态工厂方法有一个imo更好的替代方法,该方法为所有Elements具有构造函数(无论如何,这是不可变类的要求)。 Annotate that with @JsonCreator and also annotate all parameters with @JsonProperty like this: 注释 @JsonCreator也注释了所有参数@JsonProperty是这样的:

class School {
    //fields

    @JsonCreator
    public School(
            @JsonProperty("id") String id,
            @JsonProperty("name") String name) {
        this.schoolId = id;
        this.schoolName = name;
    }

    //getters
}

Those are the options the @JsonCreator annotation gives you. 这些是@JsonCreator批注为您提供的选项。 It describes them like this in its documentation: 它在其文档中像这样描述它们:

  • Single-argument constructor/factory method without JsonProperty annotation for the argument: if so, this is so-called "delegate creator", in which case Jackson first binds JSON into type of the argument, and then calls creator. 没有参数的JsonProperty批注的单参数构造函数/工厂方法:如果是这样,则这就是所谓的“委托创建者”,在这种情况下,Jackson首先将JSON绑定到参数的类型,然后调用creator。 This is often used in conjunction with JsonValue (used for serialization). 这通常与JsonValue(用于序列化)结合使用。
  • Constructor/factory method where every argument is annotated with either JsonProperty or JacksonInject, to indicate name of property to bind to 构造函数/工厂方法,其中每个参数都用JsonProperty或JacksonInject注释,以指示要绑定的属性的名称

You might not even need to explicitly specify the parameter name under some circumstances. 在某些情况下,甚至可能不需要显式指定参数名称。 The documentation regarding that for @JsonCreator further states: 有关@JsonCreator的文档进一步指出:

Also note that all JsonProperty annotations must specify actual name (NOT empty String for "default") unless you use one of extension modules that can detect parameter name; 还要注意,除非您使用可以检测参数名称的扩展模块之一,否则所有JsonProperty批注都必须指定实际名称(“ default”不为空字符串)。 this because default JDK versions before 8 have not been able to store and/or retrieve parameter names from bytecode. 这是因为8之前的默认JDK版本无法存储和/或从字节码中检索参数名称。 But with JDK 8 (or using helper libraries such as Paranamer, or other JVM languages like Scala or Kotlin), specifying name is optional. 但是对于JDK 8(或使用诸如Paranamer之类的帮助程序库,或诸如Scala或Kotlin之类的其他JVM语言),指定名称是可选的。

Alternatively this will also work nicely with lombok version 1.18.3 or up, where you can add lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonProperty to your lombok.config and therefore have it copy the JsonProperty annotations to the constructor, given that you do annotate all fields with it (which one should do anyway imo). 另外这也将与龙目岛版本的工作很好1.18.3或向上,在这里你可以添加lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonPropertylombok.config ,因此有它的复制JsonProperty注释构造,假设您确实用它注释了所有字段(无论如何,imo都应该这样做)。 To put the @JsonCreator -annotation on the constructor, you can use the experimental onX feature . 要将@JsonCreator -annotation放在构造函数上,可以使用实验性onX功能 Using lombok's @Value for immutable data classes, your DTO then might just look like this (untested): 对不可变数据类使用@Value@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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM