簡體   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