Dynamic reference casting depending on actual object type

蓝咒 提交于 2019-12-23 03:06:44

问题


I have classes hierarchy such as

ChildA extends Parent
ChildB extends Parent
ChildC extends Parent

then in my application, I get in a method any of this child by Parent reference.

The problem is all of these children have the same methods but their parent doesn't

So, ChildA, ChildB, and ChildC all have getSomeValue() getter but Parent doesn't.

Now I need to parse the value from any of this child, but Parent reference doesn't provide me with API so I need to cast Parent to specific children type.

Below is the snippet representing what I am trying to do:

private void processChildren(Parent parent) {
    ChildA childA = null;
    ChildB childB = null;
    ChildC childC = null;

    if (parent instanceof ChildA) {
        childA = parent;
    }

    if (parent instanceof ChildB) {
        childB = parent;
    }

    if (parent instanceof ChildC) {
        childC = parent;
    }

    String someValue;

    if (Objects.nonNull(childA)) {
        someValue = childA.getSomeValue();
    } // and the same checks and extracts for each of childs and for many methods

}

As you can see in order to extract just one value I need to create 3 references, then check them in order to cast to specific type and then check what the type actually was created in order to call the method.

The question is how to properly cast the reference to the specific child reference in runtime? I guess it is possible to write using reflection, although I was not able to solve it even with reflection.

Also, even if it possible - is it ok to do that?

FYI: I am working on a legacy application so I can't change the previously written code so I can't add this API in Parent class. Also, the classes are provided from an external jar.


回答1:


As a possible solution you can create a map parametrized by specific child Class as a key, and supplier as a value. Each supplier is responsible for casting and dealing with particular methods.

Map<Class<? extends Parent>, Supplier> getValueMap = new HashMap<>();

getValueMap.put(ChildA.class, () -> { return ((ChildA) parent).getValue(); });
getValueMap.put(ChildB.class, () -> { return ((ChildB) parent).getValue(); });
getValueMap.put(ChildC.class, () -> { return ((ChildC) parent).getValue(); });

getValueMap.get(parent.getClass()).get();



回答2:


The question is how to properly cast the reference that definitely is the one of this types to the specific reference?

Use instanceof operator to check the exact instance type of the Parent reference.

    if (parent instanceof ChildA) {
        ChildA childA = (ChildA) parent;
    } else if (parent instanceof ChildB) {
        ChildB childB = (ChildB) parent;
    } else if (parent instanceof ChildC) {
        ChildC childAC = (ChildC) parent;
    }



回答3:


If you are sure that all the children have the getSomeValue method, you could call them as

parent.getClass().getMethod("getSomeValue").invoke(parent);

However, this is not a good practice. The class design violates Liskov's substitution principle here.




回答4:


My approach is to do as I would extend Parent by the missing interface. To do so I'm defining an interface which declares the missing methods:

public interface HasSomeValue {

    String getSomeValue();

}

As it's impossible to let Parent itself implement this interface I define a wrapper for Parent:

private static class ParentWithSomeValue implements HasSomeValue {

    private Parent parent;

    public ParentWithSomeValue(Parent parent) {
        this.parent = parent;
    }

    @Override
    public String getSomeValue() {
        try {
            Method method = parent.getClass().getMethod("getSomeValue");
            return (String) method.invoke(parent);
        } catch (Exception e) {
            return null;
        }
    }
}

Moreover ParentWithSomeValue delegates to other methods which Parent already has. Then ParentWithSomeValue provides the full interface of Parent and can replace it.

getSomeValue is implemented using reflection. If parent has the called method then the call is delegated to parent using reflections. Otherwise a default value is returned. In my example null. Another option would be to throw an exception if parent has to have the called method.

Using this approach you can also extend the implemented interface or add more interfaces and implement the delegation on a per method basis.

Applying this to your code:

private void processChildren(Parent parent) {
    if (Objects.nonNull(parent)) {
        ParentWithSomeValue parentWithSomeValue = new ParentWithSomeValue(parent);
        String someValue = parentWithSomeValue.getSomeValue();
        // go on
    } 
}


来源:https://stackoverflow.com/questions/52950426/dynamic-reference-casting-depending-on-actual-object-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!