Thu. Mar 28th, 2024

Java-based servers or applications often have to deal with large amounts of data.  Whether the data is from a database, or from a local file, processing this data in an efficient manner is a priority for maintainability. In this article, we will discuss the types of Java Data Objects and additional steps that should be applied to each.

  • Model – A Java object that represents a row from a database.  A model is a specific data structure that must correspond to the format of the columns from a table. 
  • Data Transfer Object (DTO) – A Java object that contains data fields that may come from one or more models.  Used to limit the data instead of including unnecessary fields.

Methods to Override

Java objects contain some basic methods, these methods need to be overridden or implemented.  Data Models or DTO’s will easier to use and work with when these are in place.

  • Object::equals() override to provide a method for checking for equivalency between two objects.  This method is used by many different actions throughout the JDK that enable much functionality.  
  • Object::toString() override to provide a readable string representation of the Object.  Java IDE’s make great use of this for the debuggers.  This will assist logging practices, enabling logging of an entire Model/DTO. This should be implemented in consideration of your environment.  If you are using Hibernate, then pulling every field can be a costly in terms of the number of database queries.
  • Object::hashCode() override to generate a unique hashcode that is unique to each Model/DTO instances. HashMaps, HashMaps, and Hash anything this will be used.  
  • Cloneable::clone() – needs to provide a shallow clone of the object.  Must add implement Cloneable interface.  Not always a necessity, but extremely useful when needed. 
  • Comparable::compareTo() – method to compare to objects.  Must implement Comparable interface.  Will make Collections::sort() and Streams.sorted().

hashcode() does not include ID

The hashcode() method should not include the ID on Models, when calculating the hashcode.  The ID field corresponds to a row in the database, it is not part of the information that your evaluating.  It can also change, without effecting the remaining data.  So when implementing your hashcode() method leave the ID out.

Implementing equals()

When impelementing equals() on Model’s your going to have to make a project decision.  There are three ways you can implement equals on models.

  1. equals() only compares the ID fields, to determine if the two objects represent the same row.  This is fast, and when using third party libraries such as Hibernate can significantly reduce database lookups. The downside is you don’t know if the data has changed between two instances.
  2. equals() only compares the non-ID fields.  This is doing a comparison of the actual data.  Not the ID that would indicate where the model is in the database.  This is helpful as it will tell you if you have two models that are identical.  The downside is if you are using something like Hibernate then it can result in multiple lookups.
  3. equals() compares everything.  It does a compare on the ID and all other fields in the Model.  This tells you if you have the same Model, and if they have any differences.  Again this has it’s place.

In general you should decide on a single standard way for a project.  However you should not be afraid to use one of the other methods if it is necessary.  Often you have those few objects that are just different and required different handling than everything else.

Implementing compareTo()

The compareTo() method is a funny beast.  In the examples I provide below it’s easy to implement as it sorts by last name, first name, and then age.  Not all Model/DTO’s are so easily sorted.  Often times you may find you need multiple different comparisons.

The compareTo() should be the default method that sorts the data object into the order you would normally want them. This could be based on a single field, or multiple fields, your data will dictate this to you.

However if you need to be able to compare by age only?  Well just implement your own compareToAge() method.  It’s not an override, but having this convenience function around will make your development much easier. Personally when creating a special compareTo() method that will compare only select fields, I like to add the field names to the method name, thus compareToAge().

Constructors

Debate rages on between use of  constructors or factories.  I find each have their use.  However my preference is to use constructors for converting between Objects.  

  • Default Constructor – Every object should have a default constructor.  Zero parameters, for instantiations of those objects.  
  • Copy Constructor – Given an object of the same type it should create a copy a Model / DTO with the exception of an ID
  • translation constructors – If the Model / DTO is used to translate from one or more Model / DTO’s to this Model / DTO it should have a constructor for creating the new object

Clone v’s Copy Constructor

You may ask why should you have both a clone() and a copy constructor.  For Models there is a big difference the clone() method should generate an exact copy of the object, including an ID.  For DTO’s there is not ID, however for consistency it is wise to generate both.  For Models the copy constructor should generate a new object with the exact same data, however it should not contain the ID if it is a model.

Person Model Example


public class PersonModel implements Comparable<PersonModel>, Cloneable {

	private Long	id;
	private String	firstName;
	private String	lastName;
	private Integer	age;

	public PersonModel() {
		super();
	}

	public PersonModel(PersonDTO copy) {
		this(copy.getFirstName(), copy.getLastName(), copy.getAge());
	}

	public PersonModel(PersonModel copy) {
		this(copy.getFirstName(), copy.getLastName(), copy.getAge());
	}

	public PersonModel(String firstName, String lastName, Integer age) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.age = age;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		PersonModel clone = new PersonModel(this.getFirstName(), this.getLastName(), this.getAge());
		clone.setId(this.getId());

		return clone;
	}

	@Override
	public int compareTo(PersonModel o) {

		int lastNameCompare = this.getLastName().compareTo(o.getLastName());

		if (lastNameCompare == 0) {
			int firstNameCompare = this.getFirstName().compareTo(o.getFirstName());

			if (firstNameCompare == 0) {
				return this.getAge().compareTo(o.getAge());
			}

			return firstNameCompare;
		}

		return lastNameCompare;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		PersonModel other = (PersonModel) obj;
		if (this.age == null) {
			if (other.age != null) {
				return false;
			}
		} else if (!this.age.equals(other.age)) {
			return false;
		}
		if (this.firstName == null) {
			if (other.firstName != null) {
				return false;
			}
		} else if (!this.firstName.equals(other.firstName)) {
			return false;
		}
		if (this.lastName == null) {
			if (other.lastName != null) {
				return false;
			}
		} else if (!this.lastName.equals(other.lastName)) {
			return false;
		}
		return true;
	}

	/**
	 * @return the age
	 */
	public Integer getAge() {
		return this.age;
	}

	/**
	 * @return the firstName
	 */
	public String getFirstName() {
		return this.firstName;
	}

	/**
	 * @return the id
	 */
	public Long getId() {
		return this.id;
	}

	/**
	 * @return the lastName
	 */
	public String getLastName() {
		return this.lastName;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + ((this.age == null) ? 0 : this.age.hashCode());
		result = (prime * result) + ((this.firstName == null) ? 0 : this.firstName.hashCode());
		result = (prime * result) + ((this.lastName == null) ? 0 : this.lastName.hashCode());
		return result;
	}

	/**
	 * @param age
	 *            the age to set
	 */
	public void setAge(Integer age) {
		this.age = age;
	}

	/**
	 * @param firstName
	 *            the firstName to set
	 */
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	/**
	 * @param id
	 *            the id to set
	 */
	public void setId(Long id) {
		this.id = id;
	}

	/**
	 * @param lastName
	 *            the lastName to set
	 */
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.firstName + " " + this.lastName + " age " + this.age;
	}
}

Person DTO Example


public class PersonDTO implements Comparable<PersonDTO>, Cloneable {

	private String	firstName;
	private String	lastName;
	private Integer	age;

	public PersonDTO() {
		super();
	}

	public PersonDTO(PersonDTO copy) {
		this(copy.getFirstName(), copy.getLastName(), copy.getAge());
	}

	public PersonDTO(PersonModel copy) {
		this(copy.getFirstName(), copy.getLastName(), copy.getAge());
	}

	public PersonDTO(String firstName, String lastName, Integer age) {
		super();
		this.firstName = firstName;
		this.lastName = lastName;
		this.age = age;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		PersonDTO clone = new PersonDTO(this);

		return clone;
	}

	@Override
	public int compareTo(PersonDTO o) {

		int lastNameCompare = this.getLastName().compareTo(o.getLastName());

		if (lastNameCompare == 0) {
			int firstNameCompare = this.getFirstName().compareTo(o.getFirstName());

			if (firstNameCompare == 0) {
				return this.getAge().compareTo(o.getAge());
			}

			return firstNameCompare;
		}

		return lastNameCompare;
	}
	
	public int compareToAge(PersonDTO o) {
		return this.getAge().compareTo(o.getAge());
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (this.getClass() != obj.getClass()) {
			return false;
		}
		PersonDTO other = (PersonDTO) obj;
		if (this.age == null) {
			if (other.age != null) {
				return false;
			}
		} else if (!this.age.equals(other.age)) {
			return false;
		}
		if (this.firstName == null) {
			if (other.firstName != null) {
				return false;
			}
		} else if (!this.firstName.equals(other.firstName)) {
			return false;
		}
		if (this.lastName == null) {
			if (other.lastName != null) {
				return false;
			}
		} else if (!this.lastName.equals(other.lastName)) {
			return false;
		}
		return true;
	}

	/**
	 * @return the age
	 */
	public Integer getAge() {
		return this.age;
	}

	/**
	 * @return the firstName
	 */
	public String getFirstName() {
		return this.firstName;
	}

	/**
	 * @return the lastName
	 */
	public String getLastName() {
		return this.lastName;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = (prime * result) + ((this.age == null) ? 0 : this.age.hashCode());
		result = (prime * result) + ((this.firstName == null) ? 0 : this.firstName.hashCode());
		result = (prime * result) + ((this.lastName == null) ? 0 : this.lastName.hashCode());
		return result;
	}

	/**
	 * @param age
	 *            the age to set
	 */
	public void setAge(Integer age) {
		this.age = age;
	}

	/**
	 * @param firstName
	 *            the firstName to set
	 */
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}

	/**
	 * @param lastName
	 *            the lastName to set
	 */
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return this.firstName + " " + this.lastName + " age " + this.age;
	}
}

By Jeffery Miller

I am known for being able to quickly decipher difficult problems to assist development teams in producing a solution. I have been called upon to be the Team Lead for multiple large-scale projects. I have a keen interest in learning new technologies, always ready for a new challenge.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d