简体   繁体   中英

How do I create a class out of a superclass without downcasting?

I'm using spring-data-neo4j and I have two node entities, Person and Owner extends Person

When I save person, it gets the label :Person , and when I save an owner, it gets the labels :Owner and :Person . Excellent. Exactly what I wanted.

Sometimes we have people that become owners, so I'd like to convert that person to an Owner and add the missing data (such as properties).

Obviously downcasting doesn't work. There is a way I've heard of where you can get a subclass to call its superclass with parameters. I'd like to avoid the adapter pattern as there will be hundreds of inherited fields from Person .

How do I "construct" an Owner from a Person ?

/*** MAIN CLASS ***/
public class Application {

    public static void main (String[] args) {

        Person p = personRepository.findByEmail ("joesoap@example.com");
        Owner o = new Owner(p); // Trying to construct a person from an owner
        o.addProperty (...);
        ownerRespository.save(o);   

    }

}

/*** PERSON ***/
@NodeEntity
public class Person {

    public Person(Person person) {
        this = person; // Obviously this won't work, but I can't think of the solution...
    }

    String fullName;
    String email;
}

/*** OWNER ***/
@NodeEntity
public class Owner extends Person {

    public Owner(Person person) {
        super (person);
    }

    public List<Property> properties;
}

Java classes were never meant to handle cases where one wants to make an object of one type into another type. Obviously treating a subclass as its superclass, or treating a class as an object which implemented an interface, were intended, but Java just has no good way of converting an object of one class into an object of another class.

You could convert the object to XML and then read the superclass fields into the subclass; kind of a brute-force-leave-the-XML-in-memory approach.

But I think what you have is a problem where Owner should not be represented by a subclass, for the specific reason that you sometimes want to convert from one class to the other. I think your Person class should have a field of a type containing owner information, null if the person is not also an owner.

This is what you do when you need to clone an object. Unfortunately the only way is to set each property manually:

class Test {

    private String prop1;

    private String prop2;

    public Test() {}

    public Test(Test test) {
        setProp1(test.getProp1());
        setProp2(test.getProp2());
    }

    public String getProp1() {
        return prop1;
    }

    public void setProp1(String prop1) {
        this.prop1 = prop1;
    }

    public String getProp2() {
        return prop2;
    }

    public void setProp2(String prop2) {
        this.prop2 = prop2;
    }


}

So in your case you call the cloning constructor, then setting the extra parameters in Owner

Downcasting is only allowed if there is a chance that it will succeed at runtime. In your case you would want to do as @arcy stated: add the extra parameters and initialize them as null if the person is not also an owner.

As mentioned in some other answers, creating a subclass instance from an instance of its superclass has its drawbacks, and has no direct support in Java. The resulting subclass instance is typically incomplete since it is only a clone of the superclass fields, and would then need additional field set-calls to populate subclass-specific fields. This might not be an issue in your application. Solving this through composition as suggested by @arcy is another decent option.

If you must create a subtype instance from a supertype instance, then copy-via-serialization is the best option. The key is to choose a flexible and high-performing serialization mechanism, and something better than native Java serialization. A library like Kryo is one decent option.

Using Kryo you could create a utility class to help register the serializers for the classes involved, then perform the clone-to-subclass-instance:

public class KryoDowncastCloneUtil {

    private static final KryoDowncastCloneUtil instance = new KryoDowncastCloneUtil();

    public static KryoDowncastCloneUtil instance() {
        return instance;
    }

    private final Kryo kryo = new Kryo();

    public <A, B extends A> void register(Class<A> superClass, Class<B> subClass) {
        final Serializer<A> superClassSerializer = new FieldSerializer<>(kryo,
                superClass);
        // the superClass serializer is registered for both the superClass and its subClass
        kryo.register(superClass, superClassSerializer);
        kryo.register(subClass, superClassSerializer);
    }

    public <A, B extends A> B copyAndDowncast(A superClassInstance, Class<B> subClass) {

        byte[] buffer = null;

        try (final ByteArrayOutputStream stream = new ByteArrayOutputStream();
                final Output output = new Output(stream)) {
            kryo.writeObject(output, superClassInstance);
            output.flush();
            buffer = stream.toByteArray();
        } catch (IOException e) {
            // these are only from auto-close, swallow
        } // auto-close stream, output

        final B subClassInstanceClonedFromSuperClassInstance = kryo.readObject(new Input(
                new ByteArrayInputStream(buffer)), subClass);

        return subClassInstanceClonedFromSuperClassInstance;
    }

}

The key here is that the same serializer, the one for the super-class A , is registered for serializing both the super-class and its sub-class B . This allows an instance of subclass B to be instantiated from the field data serialized from A - its own fields will be left null . You can then add some syntactic sugar to actual classes if desired:

public class Owner extends Person {

    static {
        KryoDowncastCloneUtil.instance().register(Person.class, Owner.class);
    }

    // ...

    public static Owner fromPerson(Person person) {
        return KryoDowncastCloneUtil.instance().copyAndDowncast(person, Owner.class);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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