1

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.

Oleksandr Riznyk
  • 758
  • 1
  • 8
  • 19

4 Answers4

3

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();
  • Wow! Great Solution! :) Although you do have to make entries in the Map for each child class. – A_C Oct 24 '18 at 09:34
0

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;
    }
A_C
  • 905
  • 6
  • 18
  • Hi, thanks, but question contains word "properly" so if I will have a hundred children than with your solution I will have a very long method. I am looking for a good solution – Oleksandr Riznyk Oct 23 '18 at 13:45
  • Can you subClass the Parent like I asked in the previous comment? ChildA --> SubParent --> Parent and then use SubParent reference in your application instead of Parent reference. – A_C Oct 23 '18 at 13:47
  • sorry, this classes are provided from external jar so I do not have access to them – Oleksandr Riznyk Oct 23 '18 at 13:48
  • Do you mean all the classes i.e. all Children and Parent are from external jar? – A_C Oct 23 '18 at 13:49
  • Yes. I can just use them in any way but not change – Oleksandr Riznyk Oct 23 '18 at 13:50
0

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.

kishore
  • 604
  • 3
  • 7
  • 13
0

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
    } 
}
LuCio
  • 5,055
  • 2
  • 18
  • 34