Неизменяемые объекты в Java — это объекты, которые не имеют внутреннего состояния, или объекты, состояние которых не меняется после создания.
Пример неизменяемого класса:
1 2 3 4 5 6 7 8 9 10 11 |
public class Point { private final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public int getY() { return y; } } |
У неизменяемого класса нет методов установки значений, вместо этого значения x и y устанавливаются в конструкторе. Преимущество подобных классов в том, что их можно без опасений применять в многопоточном приложении без всякой синхронизации. Неизменяемые объекты используются в качестве ключей java.util.Map.
В самом простом случае для поддержки неизменяемых объектов используется аннотация @JsonCreator:
1 2 3 4 5 6 7 8 9 |
public class Point { private final int x, y; @JsonCreator public Point(@JsonProperty("x") int x, @JsonProperty("y") int y) { this.x = x; this.y = y; } } |
С этой аннотацией Jackson будет использовать конструктор для передачи параметров x и y, в таком случае аннотации @JsonProperty обязательны.
Можно также использовать методы-фабрики, для этого их нужно пометить аннотацией @JsonFactory:
1 2 3 4 5 6 |
public class Point { @JsonFactory public static Point makeAPoint(@JsonProperty("x") int x, @JsonProperty("y") int y) { return new Point(x, y); } } |
Можно также смешивать использование конструктора с методами установки значений и полями, а также использование фабрики с методами установки значений и полями.
Могут быть неизменяемые классы по поведению, но имеющие не final поля. У таких классов нет публичных методов, изменяющих значения полей, но сами поле не final . В таком случае можно просто аннотировать эти поля @JsonProperty:
1 2 3 4 5 6 7 8 9 10 11 |
public class Point { @JsonProperty private int x; private int y; public Point() { } @JsonProperty private void setY(int y) { this.y = y; } public int getX() { return x; } public int getY() { return y; } } |
Ещё один способ добиться неизменямости — это разделить интерфейс и объект на несколько частей: публичную часть только для чтения и изменяемую часть. Пусть мы имеем интерфейс Point и реализацию PointImpl. Проблема возникает с зависимостями. Давайте рассмотрим класс:
1 2 3 4 |
public class Circle { public Point center; public int radius; } |
Каким образом Jackson сможет узнать, что нужно использовать реализацию PointImpl для поля center? Проще всего использовать аннотацию @JsonDeserialize, с помощью которой можно указать конкретный тип (можно использовать также для коллекций):
1 2 3 4 5 6 |
public class Circle { @JsonDeserialize(as=PointImpl.class) public Point center; public int radius; } |
Есть ещё одна интересная штука — материализатор:
1 2 |
mapper.getDeserializationConfig().setAbstractTypeResolver(new AbstractTypeMaterializer()); |
и затем Jackson будет создавать реализации для любого класса или интерфейса, где все абстрактные методы являются методами установки или получения значений, и использовать его при десериализации.