it-roy-ru.com

Java: создание объекта подкласса из родительского объекта

Вопрос новичка на Java. Скажи, что у меня есть:

public class Car{
  ...
}

public class Truck extends Car{
  ...
}

Предположим, у меня уже есть объект Car, как мне создать новый объект Truck из этого объекта Car, чтобы все значения объекта Car были скопированы в мой новый объект Truck? В идеале я мог бы сделать что-то вроде этого:

Car c = new Car();
/* ... c gets populated */

Truck t = new Truck(c);
/* would like t to have all of c's values */

Должен ли я написать свой собственный конструктор копирования? Это должно быть обновлено каждый раз, когда автомобиль получает новое поле ...

35
suprJosh

Да, просто добавьте конструктор в Truck. Возможно, вы захотите добавить конструктор в Car, но не обязательно публично:

public class Car {
    protected Car(Car orig) {
    ...
}

public class Truck extends Car {
    public Truck(Car orig) {
        super(orig);
    }
    ...
}

Как правило, лучше всего делать классы либо листовыми (и вы можете пометить их как окончательные), либо абстрактными.

Выглядит так, как будто вам нужен объект Car, а затем тот же экземпляр превращается в Truck. Лучший способ сделать это - делегировать поведение другому объекту внутри Car (Vehicle). Так:

public final class Vehicle {
    private VehicleBehaviour behaviour = VehicleBehaviour.CAR;

    public void becomeTruck() {
        this.behaviour =  VehicleBehaviour.TRUCK;
    } 
    ...
}

Если вы реализуете Cloneable, вы можете «автоматически» скопировать объект в экземпляр того же класса. Однако есть ряд проблем с этим, в том числе необходимость копировать каждое поле изменяемых объектов, которое подвержено ошибкам и запрещает использование final.

31
Tom Hawtin - tackline

Если вы используете Spring в своем проекте, вы можете использовать ReflectionUtils.

6
Pawel.Duleba

Да, вы должны сделать это вручную. Вам также нужно будет решить, как «глубоко» копировать вещи. Например, предположим, что у автомобиля есть коллекция шин - вы можете сделать мелкую копию коллекции (например, если исходный объект изменит содержимое своей коллекции, новый объект также увидит изменение) или вы мог сделать Deep копию, которая создала новую коллекцию.

(Здесь часто пригодятся неизменяемые типы, такие как String - нет необходимости их клонировать; вы можете просто скопировать ссылку и знать, что содержимое объекта не изменится.)

4
Jon Skeet

Должен ли я написать свой собственный конструктор копирования? Это должно быть обновляется каждый раз, когда автомобиль получает новое поле ...

Не за что!

Попробуйте так:

public class Car{
    ...
}

public class Truck extends Car{
    ...

    public Truck(Car car){
        copyFields(car, this);
    }
}


public static void copyFields(Object source, Object target) {
        Field[] fieldsSource = source.getClass().getFields();
        Field[] fieldsTarget = target.getClass().getFields();

        for (Field fieldTarget : fieldsTarget)
        {
            for (Field fieldSource : fieldsSource)
            {
                if (fieldTarget.getName().equals(fieldSource.getName()))
                {
                    try
                    {
                        fieldTarget.set(target, fieldSource.get(source));
                    }
                    catch (SecurityException e)
                    {
                    }
                    catch (IllegalArgumentException e)
                    {
                    }
                    catch (IllegalAccessException e)
                    {
                    }
                    break;
                }
            }
        }
    }
3
Christian

вы можете использовать отражение, я делаю это и отлично работаю для меня:

public Child(Parent parent){
    for (Method getMethod : parent.getClass().getMethods()) {
        if (getMethod.getName().startsWith("get")) {
            try {
                Method setMethod = this.getClass().getMethod(getMethod.getName().replace("get", "set"), getMethod.getReturnType());
                setMethod.invoke(this, getMethod.invoke(parent, (Object[]) null));

            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                //not found set
            }
        }
    }
 }
2
rcorbellini

Должен ли я написать свой собственный конструктор копирования? Это должно быть обновлено каждый раз, когда автомобиль получает новое поле ...

По сути, да - вы не можете просто конвертировать объект в Java. 

К счастью, вам не нужно писать весь код самостоятельно - изучите commons-beanutils , в частности такие методы, как cloneBean . Это дает дополнительное преимущество: вам не нужно обновлять его каждый раз, когда оно получает новое поле!

2
user7094

Вы можете использовать API отражения, чтобы пройтись по всем полям Car и присвоить значение эквивалентным полям Truck. Это можно сделать в грузовике. Кроме того, это единственный способ получить доступ к частным полям Car - по крайней мере, в автоматическом смысле, при условии, что менеджер безопасности отсутствует, и ограничить доступ к частному полю.

1
Martin OConnor

Решения, представленные выше, имеют ограничения, о которых вам следует знать. Вот краткое описание алгоритмов копирования полей из одного класса в другой.

  • Tom Hawtin : используйте это, если у вашего суперкласса есть конструктор копирования. Если это не так, вам понадобится другое решение.
  • Christian : используйте это, если суперкласс не расширяет какой-либо другой класс. Этот метод не копирует поля рекурсивно вверх.
  • Шон Патрик Флойд : Это общее решение для рекурсивного копирования всех полей вверх. Обязательно прочитайте комментарий @ jett о том, что для предотвращения бесконечного цикла необходимо добавить одну строку.

Я воспроизвожу функцию analyze Шона Патрика Флойда с отсутствующим оператором:

private static Map<String, Field> analyze(Object object) {
    if (object == null) throw new NullPointerException();

    Map<String, Field> map = new TreeMap<String, Field>();

    Class<?> current = object.getClass();
    while (current != Object.class) {
        Field[] declaredFields = current.getDeclaredFields();
        for (Field field : declaredFields) {
            if (!Modifier.isStatic(field.getModifiers())) {
                if (!map.containsKey(field.getName())) {
                    map.put(field.getName(), field);
                }
            }
        }

        current = current.getSuperclass();   /* The missing statement */
    }
    return map;
}
0
Moshe Rubin

Вы всегда можете использовать картографический фреймворк, такой как Dozer . По умолчанию (без дальнейшая настройка ) он сопоставляет все поля с одинаковыми именами из одного объекта в другой, используя методы getter и setter.

Зависимость:

<dependency>
    <groupId>net.sf.dozer</groupId>
    <artifactId>dozer</artifactId>
    <version>5.5.1</version>
</dependency>

Код:

import org.dozer.DozerBeanMapper;
import org.dozer.Mapper;

// ...

Car c = new Car();
/* ... c gets populated */

Truck t = new Truck();
Mapper mapper = new DozerBeanMapper();
mapper.map(c, t);
/* would like t to have all of c's values */
0
schnatterer

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

0
tvanfosson