optimusfrenk
optimusfrenk

Reputation: 1321

How to bound the class type of the subclass

I have this class:

public abstract class Addressable {

  abstract <T extends "this" Addressable> void hardEquals(T t);

}

The method hardEquals(T t) is not bounded as I want. What I want is bound T to be same class of this. In other words when I extend the class Addressable, with the concrete class MyAddressable, I want that the method hardEquals() has the signature:

void hardEquals(MyAddressable t);

For completeness:

public class MyAddressable extends Addressable {

  void hardEquals(MyAddressable t);

}
  1. Is it possible to achieve what I want?
  2. If the answer is no, is it so stupid my class constraint?

Upvotes: 10

Views: 910

Answers (4)

Holger
Holger

Reputation: 298409

It’s not possible in general. The problem becomes clear when think a bit longer about your scenario. You have that class Addressable and its method hardEquals which demands that its parameter has the type equal to this. Then you have the subclass

public class MyAddressable extends Addressable {
  void hardEquals(MyAddressable t);
}

that seems to work as intended. Now imagine a subclass of the subclass:

public class GrandChild extends MyAddressable {
  void hardEquals(MyAddressable t);
}

Here, you have a class which must accept MyAddressable as hardEquals’ parameter as it is not allowed to override a method by narrowing the parameter types, i.e. not accepting things that the superclass accepted. After all, you can always have a variable of type MyAddressable that actually refers to an instance of GrandChild. It would be impossible to restrict the parameters to hardEquals to match the actual runtime type of the object.

So the desired “parameter type must match this type” rule conflicts with the “subclass methods must accept all parameters the superclass did” rule.


Note that this is often confused with an actual limitation of the type system, that the same thing doesn’t work for return types. Since return types are allowed to be narrowed for subclasses, the desire to guaranty to return the this type could be reasonable in some scenarios, e.g.:

class Base {
    Base/*actually this type*/ clone() { … }
}
class SubClass extends Base {
    SubClass/*actually this type*/ clone() { … }
}
class GrandChild extends SubClass {
    GrandChild/*actually this type*/ clone() { … }
}

works, but there is no formal way to specify the guaranty that the this type is returned so it’s up to the programmer’s discipline to add the correct override to every subclass.

But, as said, for parameter types this doesn’t work in general as you can’t narrow the type of a parameter in a subclass.

Upvotes: 3

OldCurmudgeon
OldCurmudgeon

Reputation: 65851

If you are looking for a hardEquals that can only be used with objects with exactly the same type then something like this should work:

public abstract class Addressable<T extends Addressable<T>> {

    abstract boolean hardEquals(T t);

}

class X extends Addressable<X> {

    @Override
    boolean hardEquals(X t) {
        return true;
    }

}

class Y extends Addressable<Y> {

    @Override
    boolean hardEquals(Y t) {
        return true;
    }

}

public void test() {
    X x = new X();
    if ( x.hardEquals(x)) {
        System.out.println("Ok");
    }
    Y y = new Y();
    // Fails!
    if ( x.hardEquals(y)) {
        System.out.println("Not OK");
    }
}

As @Holger rightly points out - this does not help when extending the sub-classes.

Upvotes: 1

sp00m
sp00m

Reputation: 48837

I'm not aware of a clean solution for your problem.

You could think about something like this:

public abstract class Addressable<T extends Addressable<T>> {

  abstract void hardEquals(T other);

}

public class MyAddressable extends Addressable<MyAddressable> {

  @Override
  void hardEquals(MyAddressable other) {
    // ...
  }

}

But this could fail in some cases, e.g.:

public class FakeAddressable extends Addressable<MyAddressable> {

  @Override
  void hardEquals(MyAddressable aT) {
    // ...
  }

}

Upvotes: 1

Mureinik
Mureinik

Reputation: 311853

You could borrow a trick from Comparable, and specify the generic type in the class declaration itself:

public abstract class Addressable<T> {
    abstract <T> void hardEquals(T t);
}

This results in slightly clunky class definitions, but it should fulfill your requirement

public class HomeAddress extends Addressable<HomeAddress> {
    void hardEquals(HomeAddress t) {
        // implementation
    }
}

Upvotes: 1

Related Questions