web-dev-qa-db-ja.com

Java:親オブジェクトからのサブクラスオブジェクトの作成

初心者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が新しいフィールドを取得するたびに更新する必要があります...

37
suprJosh

はい、コンストラクタをトラックに追加するだけです。必ずしもパブリックではありませんが、おそらくCarにもコンストラクタを追加する必要があります。

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

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

原則として、クラスは葉(およびそれらを最終としてマークすることもできます)または抽象のいずれかにすることが一般的に最善です。

Carオブジェクトが必要で、同じインスタンスをTruckに変換するように見えます。これを行うより良い方法は、CarVehicle)内の別のオブジェクトに動作を委任することです。そう:

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

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

Cloneableを実装すると、オブジェクトを同じクラスのインスタンスに「自動的に」コピーできます。ただし、それには、エラーが発生しやすく、finalの使用を禁止する変更可能なオブジェクトの各フィールドをコピーする必要があるなど、いくつかの問題があります。

34

プロジェクトでSpringを使用している場合は、ReflectionUtilsを使用できます。

7
Pawel.Duleba

独自のコピーコンストラクタを作成する必要がありますか?これは、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;
                }
            }
        }
    }
4
Christian

はい、手動で行う必要があります。また、コピーを「深く」コピーする方法も決定する必要があります。たとえば、Carがタイヤのコレクションを持っているとします。shallowコレクションのコピーを実行できます(元のオブジェクトがコレクションのコンテンツを変更した場合、新しいオブジェクトも変更を確認できます) )または、新しいコレクションを作成するdeepコピーを実行できます。

(ここで、Stringのような不変の型がしばしば役に立ちます。それらを複製する必要はありません。参照をコピーするだけで、オブジェクトのコンテンツが変更されないことがわかります。)

4
Jon Skeet

あなたは私がそれをやって、私のためにうまく働く反射を使うことができます:

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

独自のコピーコンストラクタを作成する必要がありますか?これは、Carが新しいフィールドを取得するたびに更新する必要があります...

本質的には、はい-Javaでオブジェクトを変換することはできません。

幸い、すべてのコードを自分で書く必要はありません- commons-beanutils 、特に cloneBean のようなメソッドを調べてください。これには、新しいフィールドを取得するたびに更新する必要がないという利点もあります。

2
user7094

リフレクションAPIを使用して、各Carフィールドをループし、対応するTruckフィールドに値を割り当てることができます。これはトラック内で行うことができます。さらに、それはCarのプライベートフィールドにアクセスする唯一の方法です-少なくとも自動的に、セキュリティマネージャーが配置されておらず、プライベートフィールドへのアクセスを制限している場合。

0
Martin OConnor

上記のソリューションには、注意する必要がある制限があります。あるクラスから別のクラスにフィールドをコピーするためのアルゴリズムの簡単な要約を以下に示します。

  • Tom Hawtin :スーパークラスにコピーコンストラクタがある場合に使用します。そうでない場合は、別のソリューションが必要になります。
  • Christian :スーパークラスが他のクラスを拡張しない場合に使用します。このメソッドは、フィールドを再帰的に上方にコピーしません。
  • Sean Patrick Floyd :これは、すべてのフィールドを再帰的に上方向にコピーするための一般的なソリューションです。無限ループを防ぐために1行追加する必要があるという@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メソッドを使用して、同じ名前のすべてのフィールドを1つのオブジェクトから別のオブジェクトにマッピングします。

依存:

<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

コピーコンストラクターが必要になりますが、コピーコンストラクターはリフレクションを使用して、2つのオブジェクト間の共通フィールドを検索し、「プロトタイプ」オブジェクトからそれらの値を取得して、子オブジェクトに設定できます。

0
tvanfosson