Skip to main content

Java Cloning - Even Copy Constructors Are Not Sufficient

This is my third article on Java Cloning series, In previous articles Java Cloning and Types of Cloning (Shallow and Deep) in Details with Example and Java Cloning - Copy Constructor versus Cloning I had discussed Java cloning in detail and explained every concept like what is cloning, how does it work, what are the necessary steps we need to follow to implement cloning, how to use Object.clone(), what is Shallow and Deep cloning, how to achieve cloning using Serialization and Copy constructors and advantages copy of copy constructors over Java cloning.

If you have read those articles you can easily understand why it is good to use Copy constructors over cloning or Object.clone(). In this article, I am going to discuss why copy constructor are not sufficient?

Why-Copy-Constructors-Are-Not-Sufficient

Yes, you are reading it right copy constructors are not sufficient by themselves, copy constructors are not polymorphic because constructors do not get inherited to the child class from the parent class. If we try to refer a child object from parent class reference, we will face problems in cloning it using the copy constructor. To understand it let’s take examples of two classes Mammal and Human where Human extends MammalMammal class have one field type and two constructors, one to create object and one copy constructor to create copy of an object

class Mammal {

protected String type;

public Mammal(String type) {
this.type = type;
}

public Mammal(Mammal original) {
this.type = original.type;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Mammal mammal = (Mammal) o;

if (!type.equals(mammal.type)) return false;

return true;
}

@Override
public int hashCode() {
return type.hashCode();
}

@Override
public String toString() {
return "Mammal{" + "type='" + type + "'}";
}
}

And Human class which extends Mammal class, have one name field, one normal constructor and one copy constructor to create copy

class Human extends Mammal {

protected String name;

public Human(String type, String name) {
super(type);
this.name = name;
}

public Human(Human original) {
super(original.type);
this.name = original.name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;

Human human = (Human) o;

if (!type.equals(human.type)) return false;
if (!name.equals(human.name)) return false;

return true;
}

@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + name.hashCode();
return result;
}

@Override
public String toString() {
return "Human{" + "type='" + type + "', name='" + name + "'}";
}
}

Here in both copy constructors we are doing deep cloning.

Now let’s create objects for both classes

Mammal mammal = new Mammal("Human");
Human human = new Human("Human", "Naresh");

Now if we want to create clone for mammal or human, we can simply do it by calling their respective copy constructor

Mammal clonedMammal = new Mammal(mammal);
Human clonedHuman = new Human(human);

We will get no error in doing this and both objects will be cloned successfully, as we can see below tests

System.out.println(mammal == clonedMammal); // false
System.out.println(mammal.equals(clonedMammal)); // true

System.out.println(human == clonedHuman); // false
System.out.println(human.equals(clonedHuman)); // true

But what if we try to refer object of Human from reference of Mammal

Mammal mammalHuman = new Human("Human", "Mahesh");

In order to clone mammalHuman, we can not use constructor Human, It will give us compilation error because type mammalHuman is Mammal and constructor of Human class accept Human.

Mammal clonedMammalHuman = new Human(mammalHuman); // compilation error

And if we try clone mammalHuman using copy constructor of Mammal, we will get the object of Mammal instead of Human but mammalHuman holds object of Human

Mammal clonedMammalHuman = new Mammal(mammalHuman);

So both mammalHuman and clonedMammalHuman are not the same objects as you see in the output below code

System.out.println("Object " + mammalHuman + " and copied object " + clonedMammalHuman + " are == : " + (mammalHuman == clonedMammalHuman));
System.out.println("Object " + mammalHuman + " and copied object " + clonedMammalHuman + " are equal : " + (mammalHuman.equals(clonedMammalHuman)) + "\n");

Output:

Object Human{type='Human', name='Mahesh'} and copied object Mammal{type='Human'} are == : false
Object Human{type='Human', name='Mahesh'} and copied object Mammal{type='Human'} are equal : false

As we can see copy constructors suffer from inheritance problems and they are not polymorphic as well. So how can we solve this problem, Well there various solutions like creating static Factory methods or creating some generic class which will do this for us and the list will go on.

But there is a very easy solution which will require copy constructors and will be polymorphic as well.We can solve this problem using defensive copy methods, a method which we are going to include in our classes and call copy constructor from it and again override it the child class and call its copy constructor from it.

Defensive copy methods will also give us advantage of dependency injection, we can inject dependency instead of making our code tightly coupled we can make it loosely coupled, we can even create an interface which will define our defensive copy method and then implement it in our class and override that method.

So in Mammal class we will create a no-argument method cloneObject however, we are free to name this method anything like clone or copy or copyInstance

public Mammal cloneObject() {
return new Mammal(this);
}

And we can override same in “Human” class

@Override
public Human cloneObject() {
return new Human(this);
}

Now to clone mammalHuman we can simply say

Mammal clonedMammalHuman = mammalHuman.clone();

And for last two sys out we will get below output which is our expected behavior.

Object Human{type='Human', name='Mahesh'} and copied object Human{type='Human', name='Mahesh'} are == : false
Object Human{type='Human', name='Mahesh'} and copied object Human{type='Human', name='Mahesh'} are equal : true

As we can see apart from getting the advantage of polymorphism this option also gives us freedom from passing any argument.

You can found complete code in CopyConstructorExample Java file on Github and please feel free to give your valuable feedback.

Comments

Post a Comment

Popular posts from this blog

Why Single Java Source File Can Not Have More Than One public class

According to Java standards and common practices we should declare every class in its own source file. And even if we declare multiple classes in the single source file (.java) still each class will have its own class file after compilation. But the fact is that we can declare more than one class in a single source file with below constraints, Each source file should contain only one public class and the name of that public class should be similar to the name of the source file. If you are declaring the main method in your source file then main should lie in that public class If there is no public class in the source file then main method can lie in any class and we can give any name to the source file. If you are not following 1st constraint then you will receive a compilation error saying “ The public type A must be defined in its own file ”.  While if you are not following the second constraint you will receive an error “ Error: Could not find or load main class User ” after ...

Java Cloning - Copy Constructor versus Cloning

In my previous article Java Cloning and Types of Cloning (Shallow and Deep) in Details with Example , I have discussed Java Cloning in details and answered questions about how we can use cloning to copy objects in Java, what are two different types of cloning (Shallow & Deep) and how we can implement both of them, if you haven’t read it please go ahead. In order to implement cloning, we need configure our classes to follow below steps Implement Cloneable interface in our class or its superclass or interface, Define clone() method which should handle CloneNotSupportedException (either throw or log), And in most cases from our clone() method we call the clone() method of the superclass. And super.clone() will call its super.clone() and chain will continue until call will reach to clone() method of the Object class which will create a field by field mem copy of our object and return it back. Like everything Cloning also comes with its advantages and disadvantages. However, Java c...

Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically

In any business application auditing simply means tracking and logging every change we do in the persisted records which simply means tracking every insert, update and delete operation and storing it. Auditing helps us in maintaining history records which can later help us in tracking user activities. If implemented properly auditing can also provide us similar functionality like version control systems. I have seen projects storing these things manually and doing so become very complex because you will need to write it completely by your own which will definitely require lots of code and lots of code means less maintainability and less focus on writing business logic. But why should someone need to go to this path when both JPA and Hibernate provides Automatic Auditing which we can be easily configured in your project. And here in this article, I will discuss how we can configure JPA to persist CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate columns automatically for any ...