开发者

Create instance from superclass instance

Consider the following case:

class A {
  int x;
  int y;
}

class B extends A {
  int z;
}

Now, somewhere in the code this classes are used like this:

A objA = getAFromSomewhere();
B objB = null;

And in a certain situation I want to do something like

objB = objA; // can't do this
objB.z = someZ;

Of course the real objects are a bit mo开发者_开发百科re complicated, so it's not just about copying two ints. But they aren't overly complex either.

I know I can write a constructor for B like this:

public B(A anA) {
  this.a = anA.a;
  this.b = anA.b;

  this.z = 0;
}

But if that's really the only way, I prefer merging the additional members of B into A.

update considering the answers

My question was not clear enough. I understand that objB = objA; can't work (thus I asked for "something like", meaning something with comparable code complexity) and I know about the issues with shallow vs deep copies.

What I was looking for is a possibility to copy the members of a base class (let's say using clone()). You may understand that copying every member manually is a bad solution as it adds complexity and redundancy to the code. Thanks for your replies anyway!


There's no trivial solution to this because there's no one-size-fits-all solution. Basically you don't have all the information within a B, so you can't guarantee you would have a "sensible" B object.

You probably just want to create a constructor in B which takes an A and copies all the A data into the new B.


If you're not scared of commons-beanutils you can use PropertyUtils

import org.apache.commons.beanutils.PropertyUtils;
class B extends A {
B(final A a) {
try {
        PropertyUtils.copyProperties(this, a);
    }
    catch (Exception e) {
    }
}
}


There is a (relatively) trivial solution!

Implement a constructor in class B that takes an instance of class A and copies the fields.

One of the reasons there's no generic solution in the language itself is because of the problem of deep copying.

For example, if the source object contains further Objects, as opposed to plain types, what would the generic copy operator do? Just copy the reference (giving a shallow copy), or make real copies?

What then if one of those objects is a Collection? Should it also copy every element of the collection, too?

The only logical conclusion would be to perform a shallow copy, but then you haven't really got a copy at all.


Perhaps you could do this:

class A {
    int x;
    int y;

    A(A a) {
        this.x = a.x;
        this.y = a.y;
    }
}

class B extends A {
    int z;

    B(A a) {
        super(a);
        z = 0;
    }
}

You're still listing every field, but only once per class.


I am shocked too. :)

You really cannot do this: objB = objA;. Because Renault and BMW are cars but not all cars are BMW.

Thank about A as Car, B as BMW.

Now you say:

Car car = new Renault();
BMV bmv = car;  // you cannot do this. This is exactly your case. 


...not because this is what people should do but more because I felt like a challenge, here is some test code which does a simple copy of the objects (using setter and getter methods):

import java.lang.reflect.Method;
import org.junit.Test;

public class ObjectUtils {
    @Test
    public void test() {
        A a = new A();
        B b = new B();
        a.setX(1);
        a.setY(2);
        this.copyProperties(a, b);
    }
    private void copyProperties(Object obja, Object objb) {
        Method m[] = obja.getClass().getDeclaredMethods();
        for(int i=0;i<m.length;i++) {
            try {
                String name = m[i].getName();
                if(name.startsWith("get") || name.startsWith("is")) {
                    Class rtype = m[i].getReturnType();
                    String setter = name.replaceFirst("^(get|is)","set");
                    Class s = objb.getClass();
                    Method method = s.getMethod(setter,rtype);
                    Object[] args = new Object[1];
                    args[0] = m[i].invoke(obja);
                    method.invoke(objb,args[0]);
                }
            } catch(Exception e) {
                e.printStackTrace();
            }
        }
    }
    class A {
        int x;
        int y;
        /**
         * @return the x
         */
        public int getX() {
            return x;
        }
        /**
         * @param x the x to set
         */
        public void setX(int x) {
            this.x = x;
        }
        /**
         * @return the y
         */
        public int getY() {
            return y;
        }
        /**
         * @param y the y to set
         */
        public void setY(int y) {
            this.y = y;
        }       
    }
    class B extends A {
        int z;
        /**
         * @return the z
         */
        public int getZ() {
            return z;
        }
        /**
         * @param z the z to set
         */
        public void setZ(int z) {
            this.z = z;
        }
    }
}


If you do not need full functionality of A, there is also an option to create class B, holding internal copy of A instance and implementing some minimal subset of methods via C interface by proxying them to instance.

class A implements IC {
  int x;
  int y;

  public C() {
  ...
  }
}

class B implements IC {
  private A _a;

  public B(A a) {
    _a = a;
  }

  public C() {
  _a.C();
  }
}


Assuming that your class A has a very neat and clean setter and getter method naming convention like setXXX(Object xxx) and corrresponding getXXX() which returns the same thing (Object xxx ) as a param passed to setXXX()

I have written a utility method using reflection

public static B createSubclassInstance(A a) throws SecurityException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        Method[] aMethods = Class.forName("package.A").getDeclaredMethods();
        B b = new B(); 
        for (Method aMethod : aMethods) {
            String aMethodName = aMethod.getName();
            Class param = aMethod.getReturnType();
            if (methodName.startsWith("get")){
                String setterMethodName = methodName.replaceFirst("get", "set");
                Method bMethod = Class.forName("package.B").getMethod(setterMethodName);
                Object retValA = aMethod.invoke(a,null);
                bMethod.invoke(b,retValA);
            }

        }
        return b;
    }


If you change your method to create B objects, you can just do what you want using:

objB = (B) objA;
objB.z = someZ;

This can even be inlined, but you need parentheses:

((B) objA).z = someZ;

If not, you have to go the long way using constructors:

objB = new B(objA);
objB.z = someZ;

In this case I would recommend to copy the fields of the superclass in the superclass. Else, if you add a field to that class later, you may forget to change the copying more easily.

class A {
    int x;
    int y;
    public A(A objA) {
        x = objA.x;
        y = objA.y;
    }
}

class B extends A {
    int z;
    public B(A objA) {
        super(objA);
    }
}

I prefer merging the additional members of B into A.

You can do this if your classes A and B share the same package or if the variables in your A class are declared as protected. Then you can just access the fields of the superclass.

class A {
  protected int x;
  protected int y;
}

class B extends A {
  int z;

  void merge(A a){
    super.x = a.x; 
    y = a.y;        // you do not *need* to use the super keyword, but it is a good hint to
                    // yourself if you read your program later and might wonder ‘where is
                    // that y declared?’
  }
}

Useage, of course, is:

objB = new B();
objB.merge(objA);
objB.z = someZ;


I think best way is to use a factory method to create B objects from A objects.

class BFactory
{
    public static B createB(A a)
    {
     B b = new B();
     copy(a,b);

     return b;
    }

    private static <X,Y> void copy(X src,Y dest) throws Exception
    {
        List<Field> aFields = getAllFields(src.getClass());
        List<Field> bFields = getAllFields(dest.getClass());

        for (Field aField : aFields) {
            aField.setAccessible(true);
            for (Field bField : bFields) {
                bField.setAccessible(true);
                if (aField.getName().equals(bField.getName()))
                {
                    bField.set(dest, aField.get(src));
                }
            }
        }
    }

    private static List<Field> getAllFields(Class type)
    {
        ArrayList<Field> allFields = new ArrayList<Field>();
        while (type != Object.class)
        {
            Collections.addAll(allFields, type.getDeclaredFields());
            type = type.getSuperclass();
        }
        return allFields;
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜