views:

632

answers:

2

I have a base class that has an abstract getType() method. I want subclasses to be able to implement this method and provide the actual class to use.

In code, something like the following:

public abstract class A {
    public static interface Tile;

    protected abstract Class<Tile> getTileClass();
}

public class B extends A {
    public static class MyTile implements A.Tile { }

    @Override
    protected abstract Class<A.Tile> getTileClass() {
        MyTile t = new MyTile();  // WORKS 
        return MyTile;            // ERROR HERE
    }
}

The problem here is that I get "MyTile cannot be resolved" in the line marked. So I'm trying to return this instead:

return new MyTile().getClass()

but now Eclipse tells me:

Type mismatch: cannot convert from Class<capture#1-of ? extends B.MyTile> to Class<A.Tile>

which I'm not even sure if there's maybe a bug in Eclipse here top (capture#1?).

Next, I'm giving up on interfaces and trying to use an abstract base Tile class. With some help from Eclipse, I end up with the following code that seems to compile:

public abstract class A {
    public static abstract class Tile;

    protected abstract Class<? extends Tile> getTileClass();
}

public class B extends A {
    public static class MyTile exends A.Tile { }

    @Override
    protected abstract Class<? extends A.Tile> getTileClass() {
        return new MyTile().getClass();  // WORKS
        return MyTile;                   // "Cannot be resolved"
    }
}

So I basically seem to have three questions:

1) Is it possible to get this to work with A.Tile being an interface?

2) When using a base class, is Class<? extends X> really the correct way to go?

3) And how can I return my nested B.MyTile class reference from inside the method? Having to do new MyTile().getClass() can't be right, can it?

+5  A: 

Generics and covariant type overriding do not work very well together. You have to explicitly declare getTileClass() as returning a class that can be a subclass of A.Tile.

You also can access the class object of MyTile without instanciating it, with MyTile.class.

Try this instead:

public abstract class A {
    public static interface Tile;

    protected abstract Class<? extends Tile> getTileClass();
}

public class B extends A {
    public static class MyTile implements A.Tile { }

    @Override
    protected Class<MyTile> getTileClass() {
        return MyTile.class;
    }
}

Even better would be to make A generic. You still have to use extends in the class type definition, but you can be a bit more specific:

public abstract class A<T extends A.Tile> {
    public static interface Tile;

    protected abstract Class<? extends T> getTileClass();
}

public class B extends A {
    public static class MyTile implements A.Tile { }

    @Override
    protected Class<MyTile> getTileClass() {
        return MyTile.class;
    }
}
Varkhan
You mean `protected abstract Class<T> getTileClass();` `public class B extends A<MyTile>`.
Tom Hawtin - tackline
Carlos Heuberger
@Carlos yes, that was an overeager c/p
Varkhan
Good answer. IS this an issue just because of the covariance or in general ?
Uri
@Tom no I do mean <? extends T>. Just try to have a class B<U extends T> extends A<T>, then a class C extends B<V> that defines getTileClass(), and you will see why...
Varkhan
@Uri Covariance is supposed to work on all types, even generics. However, the compiler only knows how to apply covariance on the erased type, not on the *type arg*. In general, adding a <? extends T> is a good thing... you never know when someone will try to extend your class...
Varkhan
A: 
public abstract class A {
    public static interface Tile {};

    // I return something that is-a Tile
    protected abstract Tile getTileClass();
}

public class B extends A {
    public static class MyTile implements A.Tile { }

    @Override
    protected abstract Tile getTileClass() {
        return new MyTile();
    }
}

No need for generics, you just want to say that you return a Tile or Tile subclass.

Incidentally, a public static interface in a class is a "code smell"; either make it protected, so only subclasses of A can implement Tile, or put it in its own top-level interface. Putting in a but allowing anyone to implement it sends a mixed message.

tpdi