Bounded type parameters: cannot access subtype methods when overriding

196 Views Asked by At

I'm new to using bounded types in Java, and I'm not sure if the following is a programming error because of a bad use of inheritance or a javac bug


I need to define two different type of objects: things which have to be managed and managers of that things. That's why I created an abstract class to model the common behavior of those things

public abstract class AbstractThing {
    // Common method implemented
    public void hello() {
        System.out.println("HI, I'm AbstractThing");
    }
} 

and an interface to define the methods that those things' manager must implement

public interface AbstractManager {
    // Operation that a things' manager must implement
    public <T extends AbstractThing> void greet(T t);
}

So suppose I create two concrete things classes, one of them just inherits the abstract one:

public class Thing extends AbstractThing {
    // Constructor
    public Thing() {}
}

but the other one implements an own method:

public class AnotherThing extends AbstractThing {    
    // Constructor
    public AnotherThing() {}
    
    // Extra method which this class implements
    public void goodbye() {
        System.out.println("BYE, I'm AnotherThing");
    }
}

But when I define a manager as follows:

public class Manager implements AbstractManager {
    
    // Constructor method
    public Manager() {}

    // Implementation of the interface's method fails
    @Override
    public <AnotherThing extends AbstractThing>
    void greet(AnotherThing t) {
        // I can use this method, which AnotherThing inherits from AbstractThing
        t.hello();
        // But I can't use this one defined by AnotherThing
        t.goodbye();
    }

}

I get the error:

AnotherManager.java:15: error: cannot find symbol
        t.goodbye();
         ^
  symbol:   method goodbye()
  location: variable t of type AnotherThing
  where AnotherThing is a type-variable:
    AnotherThing extends AbstractThing declared in method <AnotherThing>greet(AnotherThing)
1 error

And I don't understand why, because it's recognizing the class as AnotherThing, but it's dealing it as AbstractThing. I've tried to cast the object as the subclass, but it doesn't work

I've also checked that it only happens when I try to access the subtype methods, because the following manager compile and works perfectly:

public class Manager implements AbstractManager {

    // Constructor method
    public Manager() {}
 
    // Implementation of the method defined into the interface
    @Override
    public <Thing extends AbstractThing>
    void greet(Thing t) {
        t.hello();
    }

    // I can overload the method "greet" without overriding the interface 
    // and it works for AnotherThing
    public void greet(AnotherThing t) {
        t.hello();
        t.goodbye();
    }

}

Any idea about what's happening there?

2

There are 2 best solutions below

2
shikida On BEST ANSWER

Here's why

enter image description here

It's not difficult to figure out if you do something like this

public class Manager implements AbstractManager {
    
    // Constructor method
    public Manager() {}

    // Implementation of the interface's method fails
    @Override
    public <T extends AbstractThing>
    void greet(T t) {
        // I can use this method, which T inherits from AbstractThing
        t.hello();
        // But I can't use this one because T inherits from AbstractThing which does not know this method
        t.goodbye();
    }

}

In other words, the word before the "extends" is not supposed to be a class name, but a generic identifier (a variable name).

In this case, since T (or wharever you call it) is AbstractThing, it does not know what goodbye() is.

Now, why your last example works?

Because you're saying that greet receives a object of type AnotherThing, not a generic type such as T.

3
shmosel On

AnotherThing in <AnotherThing extends AbstractThing> has nothing to do with the class AnotherThing. It's the name a type parameter, same as if it were called T. The reason it acts like AbstractThing is because that's all we know it is. Your method doesn't know how it's parameterized; all it knows is that it's getting a subclass of AbstractThing. This can be useful for constructs where we need to capture the type, but we don't care what subtype it is, e.g.:

<T extends AbstractThing> void copy(List<T> from, List<T> to) {
    for (T t : from) {
        t.hello();
        to.add(t);
    }
}

But if you want a specialized subclass of AbstractManager to manage AnotherThing, the type parameter should be on the class, not the method:

public interface AbstractManager<T extends AbstractThing> {
    // Operation that a things' manager must implement
    public void greet(T t);
}

public class AnotherManager implements AbstractManager<AnotherThing> {
    @Override
    public void greet(AnotherThing t) {
        t.hello();
        t.goodbye();
    }
}