简体   繁体   中英

Java Polymorphism : How can I avoid type casting input parameters?

Assume we have a Parent interface with compare() function.

public interface Parent {
    public int compare(Parent otherParent);
}

Suppose that children Child1, Child2, Child3 implement this interface Parent

public class Child1 implements Parent {
    @Override
    public int compare(Parent other) {
        Child1 otherChild = (Child1)other;
    }
}

Also, I am using generics <T extends Parent> everywhere else in the code. So I need to compare two objects of type T from other parts of code.

I understand this is a bad design as I am typecasting the Parent object in compare() function and I don't even know whether the input is of type Child1.

How can I avoid this type casting while using generics?

Why not this?

interface Parent<T extends Parent<?>> {
    int compare(T type);
}

class Child1 implements Parent<Child1>{

    @Override
    public int compare(Child1 type) {
        return 0;
    }
}

Edit: To insure correct usage you can use

interface Parent<T extends Parent<T>>{ /* ... */ }   //instead of wildcard

But to be honest that "loop" doesn't look pretty, and since Generics in Java don't work at RunTime ( more information ), they're essentially syntactic-sugar for that same cast you called "bad design" so I don't think your current approach is bad.

The answer depends on how you treat your inheritance hierarchy:

  • If derived classes must compare to instances of their own class only, and are allowed to throw an error when presented with object of a different class, use cast, but protect it with checking instanceof
  • If derived classes must be compared across type boundaries, a more complex strategy is required to avoid casting: you could use a visitor-like pattern to implement double dispatch required to perform the comparison.

Here is a sample implementation using an abstract class:

// Use this class as the base class of your Child classes
class AbstractChild implements Parent {
    @Override
    public int compare(Parent otherObj) {
        if (!()) {
            throw new IllegalStateException("Unexpected implementation of Child");
        }
        AbstractChild other = (AbstractChild)otherObj;
        return doCompare(other);
    }
    protected abstract int doCompare(AbstractChild other);
    protected abstract int accept(Child1 c1);
    protected abstract int accept(Child2 c2);
    protected abstract int accept(Child3 c3);
}
class Child1 extends AbstractChild {
    @Override
    protected int doCompare(AbstractChild other) {
         return other.accept(this);
    }
    @Override
    protected int accept(Child1 c1) {
        // Compare c1 instance to this object
    }
    @Override
    protected int accept(Child2 c2) {
        // Compare c2 instance to this object
    }
    @Override
    protected int accept(Child3 c3) {
        // Compare c3 instance to this object
    }
}
class Child2 extends AbstractChild {
    @Override
    protected int doCompare(AbstractChild other) {
         return other.accept(this);
    }
    @Override
    protected int accept(Child1 c1) {
        // Compare c1 instance to this object
    }
    @Override
    protected int accept(Child2 c2) {
        // Compare c2 instance to this object
    }
    @Override
    protected int accept(Child3 c3) {
        // Compare c3 instance to this object
    }
}
class Child3 extends AbstractChild {
    @Override
    protected int doCompare(AbstractChild other) {
         return other.accept(this);
    }
    @Override
    protected int accept(Child1 c1) {
        // Compare c1 instance to this object
    }
    @Override
    protected int accept(Child2 c2) {
        // Compare c2 instance to this object
    }
    @Override
    protected int accept(Child3 c3) {
        // Compare c3 instance to this object
    }
}

The only cast in this approach is at the abstract class level. The implementations of the actual comparison logic are contained in accept methods, where the comparison is done between two objects of known type.

You can't. Java Interfaces are exactly what the name implies – an interface dictating the types accepted; so there needs to be exactly the same method signature at compile time as defined in the interface.

Yes, casting this back to a Child type feels like bad design – but it's really the bad design that Java dictates, so that's not your fault.

You can't avoid it in the general case. However, if the number of child classes is fixed, you can apply something akin to the visitor pattern to avoid casting at the expense of writing more code:

interface Parent {
   int compare(Parent p);
   protected int compareWith(Child1 c);
   protected int compareWith(Child2 c);
   protected int compareWith(Child3 c);
}

class Child1 implements Parent {
    @Override int compare(Parent p) {
        return p.compareWith(this);
    }
    @Override int compareWith(Child1 c) {
       //specific code to child1
    }
    @Override int compareWith(Child2 c) {
       //specific code to child2
    }
    @Override int compareWith(Child3 c) {
       //specific code to child3
    }
}
// and so on for the other children

This does avoid casting, but I'm not sure the extra effort is worth it in the case you presented here.

The pattern that you described there is exactly the pattern of Comparable . In fact, you should consider omitting your Parent interface, and replace it with Comparable instead.

The Comparable interface and its uses also show how this can be solved: The type can be given as a parameter, to make sure that only the matching type can be passed to the compare method:

interface Parent<T> {
    int compare(T that);
}

class Child1 implements Parent<Child1> {
    @Override
    public int compare(Child1 that) {
        ...
    }
}

For all other cases, you would at least have to think about what should happen when different Child -classes are compared to each other:

Child1 c1 = new Child1();
Child2 c2 = new Child2();

// Should this be possible? What should happen here? 
// How should different child classes be compared?
c1.compare(c2); 

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