web-dev-qa-db-fra.com

Java: Création d'un objet sous-classe à partir d'un objet parent

Newbie Java question. Dis que j'ai:

public class Car{
  ...
}

public class Truck extends Car{
  ...
}

Supposons que j'ai déjà un objet Car, comment puis-je créer un nouvel objet Truck à partir de cet objet Car, de sorte que toutes les valeurs de l'objet Car soient copiées dans mon nouvel objet Truck? Idéalement, je pourrais faire quelque chose comme ceci:

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

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

Devrais-je écrire mon propre constructeur de copie? Cela devrait être mis à jour à chaque fois que la voiture reçoit un nouveau champ ...

35
suprJosh

Oui, ajoutez simplement un constructeur à Truck. Vous voudrez probablement ajouter un constructeur à Car également, mais pas nécessairement public:

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

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

En règle générale, il est généralement préférable de créer des classes feuille (et vous pouvez marquer celles-ci comme étant finales) ou abstraites.

Il semble que vous souhaitiez un objet Car, puis que la même instance devienne un Truck. Une meilleure façon de procéder consiste à déléguer le comportement à un autre objet dans Car (Vehicle). Alors:

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

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

Si vous implémentez Cloneable, vous pouvez "automatiquement" copier un objet dans une instance de la même classe. Cependant, cela pose un certain nombre de problèmes, notamment le fait de devoir copier chaque champ d’objets mutables, ce qui est sujet aux erreurs et interdit l’utilisation de final.

31

Si vous utilisez Spring dans votre projet, vous pouvez utiliser ReflectionUtils.

6
Pawel.Duleba

Oui, vous devez le faire manuellement. Vous aurez également besoin de décider "profondément" de copier les choses. Par exemple, supposons que la voiture ait une collection de pneus - vous pouvez faire une copie shallow de la collection (de telle sorte que si l'objet d'origine change le contenu de sa collection, le nouvel objet verra aussi le changement) ou vous pourrait faire une copie deep créant une nouvelle collection.

(C'est là que des types immuables tels que String sont souvent utiles - il n'est pas nécessaire de les cloner; vous pouvez simplement copier la référence et savoir que le contenu de l'objet ne changera pas.)

4
Jon Skeet

Devrais-je écrire mon propre constructeur de copie? Cela devrait être mis à jour à chaque fois que la voiture reçoit un nouveau champ ...

Pas du tout!

Essayez de cette façon:

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

vous pouvez utiliser la réflexion je le fais et fonctionne bien pour moi:

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

Devrais-je écrire mon propre constructeur de copie? Cela devrait être mis à jour à chaque fois que la voiture reçoit un nouveau champ ...

Oui, vous ne pouvez pas simplement convertir un objet en Java. 

Heureusement, vous n'avez pas à écrire tout le code vous-même - regardez dans commons-beanutils , en particulier des méthodes comme cloneBean . Cela a l'avantage supplémentaire de ne pas avoir à le mettre à jour à chaque fois qu'un nouveau champ est créé!

2
user7094

Vous pouvez utiliser l'API de réflexion pour parcourir chacun des champs Car et attribuer la valeur aux champs Truck correspondants. Cela peut être fait dans un camion. En outre, il s’agit du seul moyen d’accéder aux domaines privés de Car - du moins de façon automatique, dans la mesure où aucun responsable de la sécurité n’est en place et qui limite l’accès au champ privé.

1
Martin OConnor

Les solutions présentées ci-dessus ont des limites que vous devez connaître. Voici un bref résumé des algorithmes pour copier des champs d'une classe à une autre.

  • Tom Hawtin : Utilisez ceci si votre super-classe a un constructeur de copie. Si ce n'est pas le cas, vous aurez besoin d'une solution différente.
  • Christian : Utilisez ceci si la super-classe ne prolonge aucune autre classe. Cette méthode ne copie pas les champs de manière récursive.
  • Sean Patrick Floyd : Il s'agit d'une solution générique pour copier tous les champs de manière récursive. Assurez-vous de lire le commentaire de @ jett selon lequel une seule ligne doit être ajoutée pour éviter une boucle sans fin.

Je reproduis la fonction analyze de Sean Patrick Floyd avec la déclaration manquante:

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

Vous pouvez toujours utiliser un framework de mappage tel que Dozer . Par défaut (sans configuration ultérieure ), il mappe tous les champs du même nom d'un objet à un autre à l'aide des méthodes getter et setter.

Dépendance:

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

Code:

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

Vous aurez besoin d'un constructeur de copie, mais votre constructeur de copie peut utiliser la réflexion pour rechercher les champs communs entre les deux objets, obtenir leurs valeurs à partir de l'objet "prototype" et les définir sur l'objet enfant.

0
tvanfosson