Java: How to copy an object so that it is from the same subclass?

I am trying to use a simple example for a clearer definition: I have a Tool class and child classes that extend the Tool : Hammer , Saw class. Both define some fields, such as weight , and both are a getCost overriding method with their own implementation.

  Tool first_tool = new Hammer(); Tool second_tool = new Saw(); 

I need a method in the Tool class that will make a copy of any tool, so that first_tool_copy belongs to the same subclass as first_tool . How can I make this possible? I need something like:

  /* Copy tool, change parameters of copy, the original won't change */ /* first_tool_copy will be instance of Hammer class */ first_tool_copy = first_tool.copy first_tool_copy.weight = 100 

Conclusions. I would like to have a simple copy constructor common to all subclasses.

+6
source share
4 answers

I would make an abstract Tool and add an abstract copy method to the Tool . Then each subclass is forced to provide its own implementation. This is a pretty OO approach and uses dynamic dispatching.

 abstract class Tool { // snip... public abstract Tool copy(); // snip... } class Hammer { // snip... public Tool copy() { Hammer h = new Hammer(); // copy fields from this to h return h; } // snip... } 

Otherwise, you would provide a specific implementation in the Tool that you had to update each time to process a new subclass of Tool . This method would have to use instanceof , getClass() or similar methods other than OO, to create the correct class. Eo.


Remember that Java Effective Article 10 tells us: it is wise to override clone .


I should mention that the copy is only done by copying the weight attribute.

Assuming the implementation of the Tool looks something like the class below, you can do it with a reflection:

 class Tool { // ... public Tool () {} public int getWeight () { // don't care about implementation } public void setWeight() { // don't care about implementation } // ignores all exceptions - not production code! public Tool copy() throws Exception { Tool copy = this.getClass().getConstructor().newInstance(); copy.setWeight(this.getWeight()); return copy; } // ... } 

I would not recommend this. It is just smelly and ugly for me.

+3
source

In this case, many solutions are possible, but I believe that the simplest one will use reflection to create a cloned object and copy the fields from the original to the copy. The only requirement this code has is that your subclasses must have a default constructor, but in any case, this does not seem like a real problem.

Here's how it would look:

 import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Tool implements Cloneable { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Object clone() { try { Tool instance = this.getClass().newInstance(); List<Field> fields = new ArrayList<Field>(); Class<?> kind = this.getClass(); while ( kind != null ) { fields.addAll( Arrays.asList( kind.getDeclaredFields() ) ); kind = kind.getSuperclass(); } for ( Field field : fields ) { field.setAccessible(true); int mod = field.getModifiers(); if ( !Modifier.isStatic( mod ) && !Modifier.isFinal( mod ) && !Modifier.isNative(mod) ) { Object value = field.get( this ); field.set(instance, value); } } return instance; } catch (Exception e) { throw new UnsupportedOperationException(e); } } } 

And here is your subclass that would not have anything special:

 public class Saw extends Tool { private int weight; public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } } 

And a sample JUnit test showing how it will work:

 public class SawTest { @Test public void testClone() { Saw original = new Saw(); original.setName("Some saw"); original.setWeight( 10 ); Saw clone = (Saw) original.clone(); Assert.assertTrue( original != clone ); Assert.assertTrue( original.getClass().equals( clone.getClass() ) ); Assert.assertEquals( original.getName(), clone.getName() ); Assert.assertEquals( original.getWeight(), clone.getWeight() ); } } 
+5
source

If you do not need a specific copy for all subclasses (which is fair enough), you should use reflection. And if you use reflection well, you can use BeanUtils .

I would have a utility class, say CopyUtil and a copy method, to copy an object (using BeanUtils). For some reason, if you need a copy method as an instance method of a tool, just write it and call this utility method from there.

+1
source

I just wanted to build the first part of Matt Ball's answer and mention that since Java 5, return type covariance is supported. This means that the child class can override the method by returning a more specific type. This will allow you to do the following:

 abstract class Tool { public abstract Tool copy(); } class Hammer { @Override public Hammer copy() { Hammer h = new Hammer(); // copy fields from this to h return h; } } class Saw { @Override public Saw copy() { Saw s = new Saw(); // copy fields from this to s return s; } } 

So, when you work with an assortment of Tool objects, you know that you can call copy() and get the Tool back. When you work only with Hammer objects, you know that copy() will return Hammer (no cast needed).

+1
source

Source: https://habr.com/ru/post/895023/


All Articles