Java: Как скопировать объект, чтобы он был из того же подкласса? - PullRequest
6 голосов
/ 14 августа 2011

Я пытаюсь использовать простой пример для лучшего понимания: у меня есть класс Tool и дочерние классы, которые расширяют класс Tool: Hammer, Saw. Оба определили некоторые поля, такие как weight, и оба переопределяют метод getCost с собственной реализацией.

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

Мне нужен метод класса Tool, который будет делать копию любого инструмента, таким образом, что first_tool_copy относится к тому же подклассу, что и first_tool. Как я могу сделать это возможным? Мне нужно что-то вроде:

    /* 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

Выводы: я хотел бы иметь простой конструктор копирования, общий для всех подклассов.

Ответы [ 4 ]

5 голосов
/ 14 августа 2011

Возможно, есть много решений для этого случая, но я полагаю, что самым простым было бы использование отражения для создания клонированного объекта и копирования полей из оригинала в копию.Единственное требование этого кода состоит в том, что ваши подклассы должны иметь конструктор по умолчанию, но в любом случае это не выглядит настоящей проблемой.

Вот как это будет выглядеть:

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

}

}

А вот ваш подкласс, в котором не было бы ничего особенного:

public class Saw extends Tool {

private int weight;

public int getWeight() {
    return weight;
}

public void setWeight(int weight) {
    this.weight = weight;
}

}

И тестовый пример JUnit, показывающий, как он будет работать:

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 голосов
/ 14 августа 2011

Я бы сделал Tool абстрактным и добавил бы abstract copy метод к Tool. Затем каждый подкласс вынужден предоставлять свою собственную реализацию. Это довольно оригинальный подход, использующий динамическую диспетчеризацию.

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

В противном случае вы бы предоставили конкретную реализацию в Tool, которую вам нужно будет обновлять каждый раз, чтобы обрабатывать новый подкласс Tool. Этот метод должен использовать instanceof, getClass() или аналогичные не-OO методы для создания правильного класса. Еа.


Помните, что Эффективная Java Пункт 10 говорит нам: Переопределить clone разумно.


Следует отметить, что эта копия создается только путем копирования атрибута weight.

Предполагая, что реализация Tool выглядит примерно как приведенный ниже класс, вы могли бы сделать это с помощью отражения:

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;
    }

    // ...
}

Я бы не рекомендовал это, хотя. Это просто вонючий и безобразный для меня.

1 голос
/ 14 августа 2011

Я просто хотел бы построить первую часть ответа Мэтта Болла и упомянуть, что начиная с Java 5 поддерживается ковариация возвращаемого типа. Это означает, что дочерний класс может переопределять метод, возвращая более конкретный тип. Это позволит вам сделать следующее:

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;
    }
}

Итак, когда вы работаете с ассортиментом Tool объектов, вы знаете, что можете позвонить copy() и получить Tool обратно. Когда вы работаете только с Hammer объектами, вы знаете, copy() вернет Hammer (приведение не требуется).

1 голос
/ 14 августа 2011

Если вам не нужна конкретная копия для всех подклассов (что достаточно справедливо), вы должны использовать рефлексию. И если у вас все в порядке с отражением, вы можете использовать BeanUtils .

Я хотел бы иметь служебный класс, скажем, CopyUtil и метод copy для копирования объекта (используя BeanUtils). По какой-то причине, если вам нужно метод копирования в качестве метода экземпляра Tool, просто напишите его и оттуда вызовите этот служебный метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...