繁体   English   中英

Java:从父对象创建子类对象

[英]Java: Creating a subclass object from a parent object

新手Java问题。 说我有:

public class Car{
  ...
}

public class Truck extends Car{
  ...
}

假设我已经有一个 Car 对象,我如何从这个 Car 对象创建一个新的 Truck 对象,以便将 Car 对象的所有值复制到我的新 Truck 对象中? 理想情况下,我可以做这样的事情:

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

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

我是否必须编写自己的复制构造函数? 每次 Car 获得新字段时都必须更新...

是的,只需为 Truck 添加一个构造函数。 您可能还想向 Car 添加一个构造函数,但不一定是公开的:

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

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

通常,最好将类设为叶类(您可能希望将它们标记为 final)或抽象类。

看起来好像你想要一个Car对象,然后让相同的实例变成一个Truck 一个更好的方法是将行为委托给Car ( Vehicle ) 中的另一个对象。 所以:

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

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

如果您实现Cloneable那么您可以“自动”将对象复制到同一类的实例。 然而,这存在许多问题,包括必须复制易出错且禁止使用 final 的可变对象的每个字段。

如果您在项目中使用 Spring,则可以使用 ReflectionUtils。

我是否必须编写自己的复制构造函数? 每次 Car 获得新字段时都必须更新...

一点也不!

试试这个方法:

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

是的,您必须手动执行此操作。 您还需要决定复制事物的“深度”。 例如,假设 Car 有一个轮胎集合——你可以对这个集合做一个拷贝(这样如果原始对象改变了它的集合的内容,新对象也会看到变化)或者你可以做一个创建一个新集合的副本。

(这是像String这样的不可变类型经常派上用场的地方 - 不需要克隆它们;您只需复制引用并知道对象的内容不会改变。)

你可以使用反射,我做到了,对我来说效果很好:

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

我是否必须编写自己的复制构造函数? 每次 Car 获得新字段时都必须更新...

本质上,是的 - 您不能只在 Java 中转换对象。

幸运的是,您不必自己编写所有代码 - 查看commons-beanutils ,特别是像cloneBean这样的方法。 这有一个额外的好处,即您不必每次获得新字段时都更新它!

您始终可以使用诸如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 */

上面介绍的解决方案具有您应该注意的局限性。 这是将字段从一个类复制到另一个类的算法的简短摘要。

  • Tom Hawtin :如果您的超类有一个复制构造函数,请使用它。 如果不是,您将需要不同的解决方案。
  • Christian :如果超类不扩展任何其他类,请使用它。 此方法不会向上递归复制字段。
  • Sean Patrick Floyd :这是一个用于向上递归复制所有字段的通用解决方案。 请务必阅读@jett 的评论,即必须添加一行以防止无限循环。

我用缺失的语句重现了 Sean Patrick Floyd 的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;
}

我知道这是一个老问题,但我不想在情况有所改善时遗漏过时的答案。

使用 JSON 要容易得多。 将其转换为 JSON 并作为孩子再次返回。

这是一个 Android Kotlin 示例。

    val gson = Gson()    
    val childClass = gson.fromJson(
        gson.toJson(parentObject), 
        object: TypeToken<ChildObject>(){}.type
    ) as ChildObject

我认为在 Java 中基本上是这样。

Gson gson = new Gson()
ChildObject child = (ChildObject) gson.fromJson(
    gson.toJson(parentObject),
    TypeToken<ChildObject>(){}.getType()
) 

大功告成,没有杂乱,只是简单的 json 输入,json 输出。 如果您没有 gson,我相信您还有其他可用的 json 选项。

这比做反思和所有那些疯狂的事情要干净得多,速度也快得多。

您将需要一个复制构造函数,但您的复制构造函数可以使用反射来查找两个对象之间的公共字段,从“原型”对象中获取它们的值,并将它们设置在子对象上。

您可以使用反射 API 循环遍历每个 Car 字段并将值分配给等效的 Truck 字段。 这可以在卡车内完成。 此外,它是访问 Car 私有字段的唯一方法 - 至少在自动意义上,假设安全管理器未到位并限制对私有字段的访问。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM