Java: How to copy an object so it will be from the

2019-04-28 18:26发布

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.

4条回答
孤傲高冷的网名
2楼-- · 2019-04-28 18:37

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() );

}

}
查看更多
SAY GOODBYE
3楼-- · 2019-04-28 18:52

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楼-- · 2019-04-28 18:52

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).

查看更多
不美不萌又怎样
5楼-- · 2019-04-28 19:00

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.

查看更多
登录 后发表回答