I have a problem in realisation of Builder pattern.
I have 2 classes:
package course_2;
import java.util.Date;
public class Student {
private static int idStart = 0;
private final int id = idStart++;
private String name;
private String surname;
private String secondName;
private Date birthDate;
private String address;
private String phone;
private int course;
private int group;
public static class Builder {
// Обязательные параметры
private final String name;
private final String surname;
private final Date birthDate;
// Необязательные параметры, инициализация по умолчанию
private String secondName = "";
private String address = "";
private String phone = "";
private int course = 1;
private int group = 1;
public Builder(String name, String surname, Date birthDate) {
this.name = name;
this.surname = surname;
this.birthDate = (Date) birthDate.clone();
}
public Builder SecondName(String secondName) {
this.secondName = secondName;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder course(int course) {
this.course = course;
return this;
}
public Builder group(int group) {
this.group = group;
return this;
}
}
private Student(Builder builder) {
this.name = builder.name;
this.surname = builder.surname;
this.secondName = builder.secondName;
this.birthDate = builder.birthDate;
this.address = builder.address;
this.phone = builder.phone;
this.course = builder.course;
this.group = builder.group;
}
}
The problem is when I'm trying to call a Builder from my client code:
Student studentOne = new Student.Builder("Andrue", "Booble", /*Date variable here*/);
I'm getting a compiler problem :
Error:(24, 30) java: incompatible types: course_2.Student.Builder
cannot be converted to course_2.Student
Can somebody help me with understanding, why does this happen and how I can solve it? Thanks!
You need to add the following to your Builder:
public Student build(){
return new Student(this);
}
And call it like this:
Student studentOne = new Student.Builder("Andrue", "Booble", null).build();
new Student.Builder("Andrue", "Booble", /*Date variable here*/); returns you builder object not student.
Your factory is missing method create which invoke Student constructor
it should looks like this
public Student create(){
return new student (this);
}
and be implemented inside Builder class
now if you want to create Student, you call
Student studentOne = new Student.Builder("Andrue", "Booble", /*Date variable here*/).create();
Related
I have created a Person, class and a Professor class that both use the Builder Pattern to create objects. The Professor class takes a Person object as an argument in its constructor. I am trying to use both classes together, but when I attempt to print out a professor, get the following output: null null (instead of Bob Smith).
Here's what I tried so far:
Person:
public class Person {
private String firstname;
private String lastname;
private int age;
private String phoneNumber;
private String emailAddress;
private char gender;
public Person(){}
// builder pattern chosen due to number of instance fields
public static class PersonBuilder {
// required parameters
private final String firstname;
private final String lastname;
// optional parameters
private int age;
private String phoneNumber;
private String emailAddress;
private char gender;
public PersonBuilder(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public PersonBuilder emailAddress(String emailAddress) {
this.emailAddress = emailAddress;
return this;
}
public PersonBuilder gender(char gender) {
this.gender = gender;
return this;
}
public Person build() {
return new Person(this);
}
}
// person constructor
private Person(PersonBuilder builder) {
this.firstname = builder.firstname;
this.lastname = builder.lastname;
this.age = builder.age;
this.phoneNumber = builder.phoneNumber;
this.emailAddress = builder.emailAddress;
this.gender = builder.gender;
}
#Override
public String toString() {
return this.firstname + " " + this.lastname;
}
}
Here's the Professor class:
package com.example.hardcodedloginform;
import java.util.List;
public class Professor extends Person{
private Person professor;
private double salary;
private String courseTaught;
private List<Student> students;
private int professorID;
public static class ProfessorBuilder {
// required fields
private Person professor;
private int professorID;
// optional fields
private double salary;
private String courseTaught;
private List<Student> students;
public ProfessorBuilder(Person professor, int professorID) {
this.professor = professor;
this.professorID = professorID;
}
public ProfessorBuilder salary(double salary) {
this.salary = salary;
return this;
}
public ProfessorBuilder courseTaught(String courseTaught) {
this.courseTaught = courseTaught;
return this;
}
public ProfessorBuilder students(List<Student> students) {
this.students = students;
return this;
}
public Professor build() {
return new Professor(this);
}
}
private Professor(ProfessorBuilder builder) {
this.salary = builder.salary;
this.courseTaught = builder.courseTaught;
this.students = builder.students;
}
#Override
public String toString() {
return "" + super.toString();
}
}
And here is the Main class where I try to print out a professor object:
public class Main {
public static void main(String[] args) {
Person profBobs = new Person.PersonBuilder("Bob", "Smith")
.age(35)
.emailAddress("bob.smith#SNHU.edu")
.gender('M')
.phoneNumber("818-987-6574")
.build();
Professor profBob = new Professor.ProfessorBuilder(profBobs, 12345)
.courseTaught("MAT101")
.salary(15230.01)
.build();
System.out.println(profBob);
}
}
I would like the printout in the console to be "Bob Smith", but what I am seeing is: null null. I checked and found that the Person object profBobs is, in fact, created properly and does print out the name "Bob Smith" when I attempt to print it the same way. I don't know why my Professor prints: null null.
Your Professor constructor fails to initialise any member fields of its base class.
There are multiple ways to solve this. One solution has ProfessorBuilder extend PersonBuilder:
public class Professor extends Person {
// Remove the `person` field! A professor *is-a* person, it does not *contain* it.
private double salary;
private String courseTaught;
private List<Student> students;
private int professorID;
public static class ProfessorBuilder extends Person.PersonBuilder {
// required fields
private int professorID;
// optional fields
private double salary;
private String courseTaught;
private List<Student> students;
public ProfessorBuilder(Person professor, int professorID) {
super(professor);
this.professorID = professorID;
}
// …
}
private Professor(ProfessorBuilder builder) {
super(builder);
this.salary = builder.salary;
this.courseTaught = builder.courseTaught;
this.students = builder.students;
}
}
For this to work you also need to mark the Person constructor as protected rather than private.
Furthermore, your Professor.toString method implementation made no sense: it essentially just called the base class method, so there’s no need to override it. And prepending the empty string does nothing.
I am learning the Java Builder Pattern from Section 6.1 (Builder Pattern Example) at: https://www.baeldung.com/creational-design-patterns#factory-method
But after I run my own code, it shows the error, wonder if could anyone help to point out where is wrong? Thanks a lot in advance.
class Test {
public static void main(String[] args) {
BankAccount newAccount = new BankAccount.BankAccountBuilder("Jon", "22738022275").withEmail("jon#example.com").wantNewsletter(true).build();
System.out.print(newAccount);
}
}
public class BankAccount {
private String name;
private String accountNumber;
private String email;
private boolean newsletter;
// constructors/getters
public static class BankAccountBuilder {
private String name;
private String accountNumber;
private String email;
private boolean newsletter;
public BankAccountBuilder(String name, String accountNumber) {
this.name = name;
this.accountNumber = accountNumber;
}
public BankAccountBuilder withEmail(String email) {
this.email = email;
return this;
}
public BankAccountBuilder wantNewsletter(boolean newsletter) {
this.newsletter = newsletter;
return this;
}
public BankAccount build() {
return new BankAccount(this);
}
}
}
You are missing the private constructor, something like:
//The constructor that takes a builder from which it will create the object
//the access to this is only provided to builder
private BankAccount(BankAccountBuilder builder) {
this.name = builder.name;
this.accountNumber = builder.accountNumber;
this.email = builder.email;
this.newsletter = builder.newsletter;
}
I assume the article leaves it to the reader in the section that says // constructors/getters on the code.
In addition, not completely related but the System.out.print you have will not print the object but its reference, you don't have a toString() method.
This is probably a duplicate but you just didn't declare a constructor. This code needs a constructor:
public class BankAccount {
private String name;
private String accountNumber;
private String email;
private boolean newsletter;
// constructors/getters
// ** you didn't put anything here **
So just add a constructor
// constructors/getters
private BankAccount( BankAccountBuilder b ) {
// initialize your class here
}
I am trying to merge JSON objects with the new #JsonMerge annotation. I found a sample online that works when I run it in my IDE. Here's a snippet to run:
#Test
void mergeTest() throws IOException {
final Employee employee = new Employee("Serializon", new Address("Street 1", "City 1", "ZipCode1"));
final Employee newEmployee = new Employee("Serializon", new Address("Street 2", "City 2", "ZipCode2"));
assertThat(employee.getAddress().getCity()).isEqualTo("City 1");
final ObjectMapper objectMapper = new ObjectMapper();
final Employee mergedEmployee = objectMapper.readerForUpdating(employee).readValue(JSONUtil.toJSON(newEmployee));
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mergedEmployee));
assertThat(newEmployee.getAddress().getCity()).isEqualTo("City 2");
assertThat(mergedEmployee.getAddress().getCity()).isEqualTo("City 2");
}
public class Employee {
private String name;
#JsonMerge
private Address address;
public Employee(final String name, final Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
}
public class Address {
private String street;
private String city;
private String zipCode;
public Address(final String street, final String city, final String zipCode) {
this.street = street;
this.city = city;
this.zipCode = zipCode;
}
public String getStreet() {
return street;
}
public String getCity() {
return city;
}
public String getZipCode() {
return zipCode;
}
}
When I try to reproduce this with my own class, it fails with the following error:
Deserialization of [simple type, class package.State] by passing existing instance (of package.State) not supported
My class in question is a POJO with some lists and primitive properties, all with getters. It is constructed using a builder and is immutable. It looks like this:
#JsonDeserialize(builder = State.Builder.class)
public class State {
private final String id;
#JsonMerge
private final List<Module> modules;
protected State(final Builder builder) {
this.id = builder.id;
this.modules = builder.modules;
}
public static Builder builder() {
return new Builder();
}
public String getId() {
return id;
}
public List<Module> getModules() {
return modules;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static final class Builder {
private String id;
private List<Module> modules;
private Builder() {
}
public Builder withId(final String id) {
this.id = id;
return this;
}
public Builder withModules(final List<Module> modules) {
this.modules = modules;
return this;
}
public State build() {
return new State(this);
}
}
}
The merge annotation states the following:
Merging is only option if there is a way to introspect current state: if there is accessor (getter, field) to use. Merging can not be enabled if no accessor exists or if assignment occurs using a Creator setter (constructor or factory method), since there is no instance with state to introspect.
So I thought perhaps the builder might the problem, but retrofitting the Employee/Address sample with a builder still works:
#Test
void mergeTest() throws IOException {
final Employee employee = Employee.newBuilder()
.withName("Serializon")
.withAddress(Address.newBuilder()
.withStreet("Steet 1")
.withCity("City 1")
.withZipCode("ZipCode1")
.build())
.build();
assertThat(employee.getAddress().getCity()).isEqualTo("City 1");
final Employee newEmployee = Employee.newBuilder()
.withName("Serializon")
.withAddress(Address.newBuilder()
.withStreet("Steet 2")
.withCity("City 2")
.withZipCode("ZipCode2")
.build())
.build();
final ObjectMapper objectMapper = new ObjectMapper();
final Employee mergedEmployee = objectMapper.readerForUpdating(employee).readValue(JSONUtil.toJSON(newEmployee));
System.out.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(mergedEmployee));
assertThat(newEmployee.getAddress().getCity()).isEqualTo("City 2");
assertThat(mergedEmployee.getAddress().getCity()).isEqualTo("City 2");
}
#JsonDeserialize(builder = Employee.Builder.class)
public class Employee {
private String name;
#JsonMerge
private Address address;
private Employee(final Builder builder) {
name = builder.name;
address = builder.address;
}
public static Builder newBuilder() {
return new Builder();
}
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
public static final class Builder {
private String name;
private Address address;
private Builder() {
}
public Builder withName(final String name) {
this.name = name;
return this;
}
public Builder withAddress(final Address address) {
this.address = address;
return this;
}
public Employee build() {
return new Employee(this);
}
}
}
#JsonDeserialize(builder = Address.Builder.class)
public class Address {
private String street;
private String city;
private String zipCode;
private Address(final Builder builder) {
street = builder.street;
city = builder.city;
zipCode = builder.zipCode;
}
public static Builder newBuilder() {
return new Builder();
}
public String getStreet() {
return street;
}
public String getCity() {
return city;
}
public String getZipCode() {
return zipCode;
}
public static final class Builder {
private String street;
private String city;
private String zipCode;
private Builder() {
}
public Builder withStreet(final String street) {
this.street = street;
return this;
}
public Builder withCity(final String city) {
this.city = city;
return this;
}
public Builder withZipCode(final String zipCode) {
this.zipCode = zipCode;
return this;
}
public Address build() {
return new Address(this);
}
}
}
Finally I tried to have a list of addresses instead, and accepting the list in the builder as withAddresses instead. So, for brevity:
#JsonDeserialize(builder = Employee.Builder.class)
public class Employee {
#JsonMerge
private List<Address> addresses;
public static final class Builder {
public Builder withAddresses(final List<Address> addresses) {
this.addresses = addresses;
return this;
}
}
}
And when I run the testcase again, this fails with the same error as my own code:
Deserialization of [simple type, class se.itab.locker.core.util.Employee] by passing existing instance (of se.itab.locker.core.util.Employee) not supported
What is actually going on here, and can I resolve it somehow or is this an unsupported use case or bug?
Update
So I found that this works:
//#JsonDeserialize(builder = Employee.Builder.class)
public class Employee {
#JsonCreator
public Employee(final Employee employee) {
name = employee.name;
addresses = employee.addresses;
stringAddresses = employee.stringAddresses;
}
But then serializing causes an infinite loop instead.
im trying to learn how to use pattern builder. i could get it to work until i tried to use enum.
I tried to change the code couple of times and each time had different error. right now the error is Incompatible types.
Please can you help bringing this code to working state and if you have suggestions to improve the code it would be great.
thanks.
EDIT:
now it seems to be okay, but how do i use it with the builder inside the main?
this was the code i used
main:
Person person3 = new Person.PersonBuilder("Julliete", "Kaplan" )
.status(); // what should i write here to set the status?
person class
public class Person
{
private final String name;
private final String lastname;
private final int age;
//My enum im trying to use
private Status status;
public enum Status
{
SINGLE ("Single"), MARRIED ("Married"), WIDOWER ("Widower");
private String status;
private Status(String status)
{
this.status = status;
}
public String getStatus()
{
return this.status;
}
}
//builder
private Person(PersonBuilder builder) {
this.name = builder.name;
this.lastname = builder.lastname;
this.age = builder.age;
this.status = builder.status;
}
//GETTERS
public String getName() {
return name;
}
public String getLastname() {
return lastname;
}
public int getAge() {
return age;
}
#Override
public String toString() {
return "Person : "+this.name+", "+this.lastname+", "+this.age;
}
//PersonBuilder
public static class PersonBuilder
{
private final String name;
private final String lastname;
private int age;
private Status status;
public PersonBuilder(String name, String lastname) {
this.name = name;
this.lastname = lastname;
}
public PersonBuilder age(int age) {
this.age = age;
return this;
}
public PersonBuilder status(Status status)
{
this.status = status;
return this;
}
public Person build() {
Person person = new Person(this);
return person;
}
}
Don't define another Status enum inside the builder: reuse the one defined in the Person class.
Otherwise, you've got to map from instances of PersonBuilder.Status to instances of Person.Status: they are entirely separate types.
Currently this mapping is trivial: you can use Person.Status.valueOf(personBuilderStatus.name()) - but you have to ensure that you update both at the same time to have identical values (or at least that PersonBuilder.Status maps to a subset of Person.Status), which is an unnecessary maintenance burden going forwards.
I am playing with the Jackson examples and am having some trouble getting deserialization to work with immutable classes and interfaces.
Below is my code:
package com.art.starter.jackson_starter;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
/** * Hello world! * */ public class App {
public static void main( String[] args ) throws JsonGenerationException, JsonMappingException, IOException
{
System.out.println( "Hello World!" );
AddressImpl.AddressBuilder builder = new AddressImpl.AddressBuilder();
NameImpl.Builder nameBuilder = new NameImpl.Builder();
UserImpl.Builder userBuilder = new UserImpl.Builder();
Name name = nameBuilder.first("FirstName")
.last("LastName")
.build();
Address address = builder.setCity("TestCity")
.setCountry("TestCountry")
.setState("PA")
.setStreet("TestAddress")
.setZip(123)
.build();
User user = userBuilder.address(address)
.gender(User.Gender.MALE)
.isVerified(true)
.userImage(new byte[5])
.build();
System.out.println(address);
System.out.println(name);
System.out.println(user);
StringWriter sw = new StringWriter();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(sw, user);
System.out.println(sw);
StringReader sr = new StringReader("{\"address\":{\"state\":\"PA\",\"country\":\"TestCountry\",\"street\":\"TestAddress\",\"city\":\"TestCity\",\"zip\":123},\"verified\":true,\"gender\":\"MALE\",\"userImage\":\"AAAAAAA=\"}");
/*
This line throws the Exception
*/
User user2 = mapper.readValue(sr, UserImpl.class);
System.out.println(user2);
} }
package com.art.starter.jackson_starter;
import java.util.Arrays;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
public final class UserImpl implements User
{
private final Address address;
private final Gender gender;
private final byte[] userImage;
private final boolean isVerified;
public static class Builder
{
private Address address;
private Gender gender;
// private Name name;
private byte[] userImage;
private boolean isVerified;
public Builder address(Address address)
{
this.address = address;
return this;
}
public Builder gender(Gender gender)
{
this.gender = gender;
return this;
}
// public Builder name(Name name)
// {
// this.name = name;
// return this;
// }
public Builder userImage(byte[] userImage)
{
this.userImage = userImage;
return this;
}
public Builder isVerified(boolean isVerified)
{
this.isVerified = isVerified;
return this;
}
public UserImpl build()
{
return new UserImpl(address, gender, userImage, isVerified);
}
}
#JsonCreator
public UserImpl(#JsonProperty("address") Address address, #JsonProperty("gender") Gender gender, #JsonProperty("userImage") byte[] userImage,
#JsonProperty("verified") boolean isVerified)
{
super();
this.address = address;
this.gender = gender;
this.userImage = userImage;
this.isVerified = isVerified;
}
public Address getAddress()
{
return address;
}
public Gender getGender()
{
return gender;
}
public byte[] getUserImage()
{
return userImage;
}
public boolean isVerified()
{
return isVerified;
}
#Override
public String toString()
{
StringBuilder builder2 = new StringBuilder();
builder2.append("UserImpl [address=");
builder2.append(address);
builder2.append(", gender=");
builder2.append(gender);
builder2.append(", isVerified=");
builder2.append(isVerified);
builder2.append(", name=");
builder2.append(", userImage=");
builder2.append(Arrays.toString(userImage));
builder2.append("]");
return builder2.toString();
}
}
package com.art.starter.jackson_starter;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
public final class AddressImpl implements Address
{
private final String city;
private final String country;
private final String street;
private final String state;
private final int zip;
public static class AddressBuilder
{
private String city;
private String country;
private String street;
private String state;
private int zip;
public AddressBuilder setCity(String city)
{
this.city = city;
return this;
}
public AddressBuilder setCountry(String country)
{
this.country = country;
return this;
}
public AddressBuilder setStreet(String street)
{
this.street = street;
return this;
}
public AddressBuilder setState(String state)
{
this.state = state;
return this;
}
public AddressBuilder setZip(int zip)
{
this.zip = zip;
return this;
}
public AddressImpl build()
{
return new AddressImpl(city, country, street, state, zip);
}
}
#JsonCreator
public AddressImpl(#JsonProperty("city") String city, #JsonProperty("country") String country, #JsonProperty("street") String street,
#JsonProperty("state") String state, #JsonProperty("zip") int zip)
{
this.city = city;
this.country = country;
this.street = street;
this.state = state;
this.zip = zip;
}
public String getCity()
{
return city;
}
public String getCountry()
{
return country;
}
public String getStreet()
{
return street;
}
public String getState()
{
return state;
}
public int getZip()
{
return zip;
}
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("AddressImpl [city=");
builder.append(city);
builder.append(", country=");
builder.append(country);
builder.append(", state=");
builder.append(state);
builder.append(", street=");
builder.append(street);
builder.append(", zip=");
builder.append(zip);
builder.append("]");
return builder.toString();
}
}
The issue appears to be with Address. I get this exception:
Exception in thread "main" org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.art.starter.jackson_starter.Address, problem: abstract types can only be instantiated with additional type information
at [Source: java.io.StringReader#785f8172; line: 1, column: 2]
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.StdDeserializationContext.instantiationException(StdDeserializationContext.java:212)
at org.codehaus.jackson.map.deser.AbstractDeserializer.deserialize(AbstractDeserializer.java:97)
at org.codehaus.jackson.map.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:230)
at org.codehaus.jackson.map.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:595)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:472)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:350)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2391)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1614)
at com.art.starter.jackson_starter.App.main(App.java:56)
I am sure this is because there is no way for Jackson to resolve Address which is an interface to AddressImpl which is a concrete implementation. I have been poking through the docs and have looked at a few articles regarding the #JsonDeserialize(as=AddressImpl.class),but it didn't work. So I am stumped. Has anyone ever gotten this to work, is it even supported?
It works like a champ if I replace Address with AddressImpl in the UserImpl class.
Just in case you hadn't seen it, here's a blog entry that discusses working with immutable objects and Jackson.
But you should definitely be able to use #JsonDeserialize(as=AddressImpl.class); either by adding it to Address.java interface (either directly or by using mix-ins), or by adding it to field or property. One thing to note is that for deserialization, it MUST be next to accessor you use; setter if you have one, if not, next to field. Annotations are not (yet) shared between accessors; so for example adding it to 'getter' would not work.
Jackson 1.8 also finally allows registration of abstract-to-concrete types (see http://jira.codehaus.org/browse/JACKSON-464 for more details) which might be the best option to indicate that 'AddressImpl' is to be used for 'Address'.