可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I try to use a simple example for better undersanding: I've got a class Tool
and child classes which are extending class Tool
: Hammer
, Saw
. Both have defined some fields like weight
and both are overriding method getCost
with own implementation.
Tool first_tool = new Hammer();
Tool second_tool = new Saw();
I need a method in Tool
class, that will to do a copy of any tool, such way, that first_tool_copy
is from 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 some simple copy constructor common for all subclasses.
回答1:
I would make Tool
abstract, and add an abstract copy
method to Tool
. Then each subclass is forced to provide its own implementation. This is a fairly OO approach, and takes advantage of dynamic dispatch.
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'd provide a concrete implementation in Tool
which you'd have to update every time to want to handle a new subclass of Tool
. This method would have to use instanceof
, getClass()
, or similar non-OO techniques to create the right class. Ew.
Remember what Effective Java Item 10 tells us: Override clone
judiciously.
I should mention, that copy is made just by copying the weight attribute.
Assuming the Tool
implementation looks something like the below class, you could do this with 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 wouldn't recommend this, though. It's just smelly and ugly to me.
回答2:
There are possibly many solutions for this case, but I believe the simplest one would be using reflection to create the 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 this doesn't look like a real issue anyway.
Here's how it would look like:
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's 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 JUnit test case showing how it would 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() );
}
}
回答3:
If you don't want a specific copy for all the subclasses (what is fair enough), you have to use reflection. And if you are ok using reflection, you may want to use BeanUtils.
I would have a utility class, say CopyUtil and a copy method in it to copy an object (using BeanUtils). For some reason if you need the copy method as an instance method of Tool, just write one and call this utility method from there.
回答4:
I'd just like to build off of the first part of Matt Ball's answer and mention that, since Java 5, return type covariance is supported. What this means is that a child class can override a method by returning a more specific type. This would 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're working with an assortment of Tool
objects, you know you can call copy()
and get Tool
back. When you're working with only Hammer
objects, you know copy()
will return a Hammer
(cast is not required).