2009-03-12 14 views
31

Pregunta de Java para principiantes. Decir que tengo:Java: creación de un objeto de subclase a partir de un objeto principal

public class Car{ 
    ... 
} 

public class Truck extends Car{ 
    ... 
} 

Supongamos que ya tienen un objeto de coches, ¿cómo puedo crear un nuevo objeto de camiones de este objeto de coches, de modo que todos los valores del objeto de automóviles se copian en mi nuevo objeto de camiones? Lo ideal sería que pudiera hacer algo como esto:

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

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

¿Tendría que escribir mi propio constructor de copia? Esto debería actualizarse cada vez que el automóvil obtenga un nuevo campo ...

Respuesta

30

Sí, simplemente añada un constructor a Camión. Es probable que desee añadir un constructor de coches también, aunque no necesariamente pública:

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

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

Como regla general, es la mejor manera de hacer que las clases ya sea la hoja (y es posible que desee marcar los final) o abstracta.

Parece que quiere un objeto Car y luego tiene la misma instancia que se convierte en Truck. Una mejor forma de hacerlo es delegar el comportamiento a otro objeto dentro de Car (Vehicle). Por lo tanto:

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

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

Si implementa Cloneable entonces usted puede "automáticamente" copiar un objeto a una instancia de la misma clase. Sin embargo, hay una serie de problemas con eso, como tener que copiar cada campo de objetos mutables que es propenso a errores y prohíbe el uso de final.

+0

implementación de 'Clonable' es [probablemente una mala idea] (https://stackoverflow.com/questions/4081858/about- java-cloneable # 4081895). – jpaugh

+0

@jpaugh su enlace tiene seis años ... ¿está seguro de que 'Clonable' no ha mejorado mientras tanto? – Mindwin

+0

@Mindwin Sospecho que hay buenas alternativas a Clonable ahora, sí. Las cosas sobre Clonable que lo hacen malo es parte de la API. No se puede cambiar sin romper la compatibilidad con versiones anteriores. Encontré varias referencias que decían lo mismo que el anterior, con varias fechas. – jpaugh

2

¿Debo escribir mi propio constructor de copia? Esto debería actualizarse cada vez que el automóvil obtiene un nuevo campo ...

Esencialmente, sí, no se puede simplemente convertir un objeto en Java.

Afortunadamente no tiene que escribir todo el código usted mismo - consulte commons-beanutils, específicamente métodos como cloneBean. ¡Esto tiene la ventaja adicional de que no tiene que actualizarlo cada vez que obtiene un nuevo campo!

0

Necesitará un constructor de copia, pero el constructor de la copia puede usar la reflexión para buscar los campos comunes entre los dos objetos, obtener sus valores del objeto "prototipo" y establecerlos en el objeto secundario.

4

Sí, tienes que hacer esto manualmente. También deberá decidir cómo copiar las cosas "profundamente". Por ejemplo, supongamos que el automóvil tiene una colección de llantas; podría hacer una copia superficial de la colección (de modo que si el objeto original cambia el contenido de su colección, el nuevo objeto vería el cambio también) o podría hacer una copia profunda que creó una nueva colección.

(aquí es donde los tipos inmutables como String menudo vienen bien - no hay necesidad de clonar ellos, sólo puede copiar la referencia y saber que el contenido del objeto no van a cambiar.)

1

Usted podría utilice la API de reflexión para recorrer cada uno de los campos del automóvil y asigne el valor a los campos del camión equivalentes. Esto se puede hacer dentro del camión.Además, es la única forma de acceder a los campos privados de Automóvil, al menos en un sentido automático, siempre que no haya un administrador de seguridad y restrinja el acceso al campo privado.

+1

Sí, puede hacer este tipo de cosas con reflexión, o posiblemente con algún marco preexistente. Cuidado con la reflexión, sin embargo. Está bien en algunos casos, pero todavía tiene algunos problemas de rendimiento, por lo que no se puede usar para cosas de alto volumen. –

+0

sí me gusta esta idea! –

5

Si está utilizando la primavera en su proyecto puede usar ReflectionUtils.

+0

+1 desde la prueba inicial esto funciona de manera brillante. ¿Por qué alguna de estas otras cosas cuando la primavera ya lo hace por ti? –

2

se puede utilizar la reflexión lo hago y funciona bien para mí:

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

¿Tendría que escribir mi propio constructor de copia? Esto debería ser actualizado cada vez que el automóvil obtiene un nuevo campo ...

¡Nada!

Trate de esta manera:

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

Como OP desea copiar TODOS los campos, se deben realizar dos cambios en su respuesta: use getDeclaredFields en lugar de getFields, y acceda a los campos de la clase Parent, no a la clase misma: 'Field [] fieldsTarget = target.getClass() .getSuperclass(). getDeclaredFields(); ' – cabad

0

Siempre se puede utilizar un marco de mapeo como Dozer. De forma predeterminada (sin further configuration), mapea todos los campos del mismo nombre de un objeto a otro utilizando los métodos getter y setter.

Dependencia:

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

Código:

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

Las soluciones presentadas anteriormente tienen limitaciones que deben tener en cuenta. Aquí hay un breve resumen de algoritmos para copiar campos de una clase a otra.

  • Tom Hawtin: Use esto si su superclase tiene un constructor de copia. Si no lo hace, necesitará una solución diferente.
  • Christian: Utilice esta opción si la superclase no se extiende ninguna otra clase. Este método no copia los campos recursivamente hacia arriba.
  • Sean Patrick Floyd: Se trata de una solución genérica para copiar todos los campos de forma recursiva hacia arriba. Asegúrese de leer el comentario de @ jett de que se debe agregar una sola línea para evitar un bucle infinito.

reproduzco función de Sean Patrick Floyd analyze con la afirmación de que falta:

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