5

Either I'm too stupid to use google, or nobody else encountered this problem so far.

I'm trying to compile the following code:

public interface MyClass {
  public class Util {
    private static MyClass _this;
    public static <T extends MyClass> T getInstance(Class<T> clazz) {
      if(_this == null) {
        try {
          _this = clazz.newInstance();
        } catch(Exception e) {
          e.printStackTrace();
        }
      }
      return _this;
    }
  }
}

Howerer, in the line "return _this;" I get the error "Type mismatch: cannot convert from MyClass to T" Why is this? T extends MyClass, so where is the problem? If I change the line to "return (T)_this;", i just get a warning about the unchecked cast, but I don't like warnings ;-) Is there a way to achieve what i want without an error or warning?

Kim Stebel
  • 41,826
  • 12
  • 125
  • 142
MarioP
  • 3,752
  • 1
  • 23
  • 32

4 Answers4

6

Imagine you have two implementations of MyClass, Foo and Bar. As a field of type MyClass, _this could be a Foo or a Bar.

Now, since your getInstance method returns <T extends MyClass>, it's legal to call it any of these ways:

MyClass myClass = Util.getInstance(MyClass.class);

This doesn't work if it's the first call, because MyClass is an interface and can't be instantiated with newInstance().

Foo foo = Util.getInstance(Foo.class);
Bar bar = Util.getInstance(Bar.class);

Now, what would happen if _this was an instance of Foo and you called Util.getInstance(Bar.class)? That's why you aren't allowed to do this.

ColinD
  • 108,630
  • 30
  • 201
  • 202
  • 1
    wow, looks like i made a huge error in reasoning there. considering this, it wouldnt have worked the way i want anyway. thanks for the explanation. – MarioP May 31 '11 at 20:34
  • +1: Good explanation. I have one nitpick; You say "Imagine you have two **subclasses** of `MyClass`". Since `MyClass` is actually an interface, not a class, can you change the word "subclasses" to "implementations"? And then in the next sentence, you go on to say that "`_this` could be a `MyClass`, a `Foo` or a `Bar`. But in fact, it's **always** a `MyClass`. In addition to being a `MyClass`, `_this` could be a `Foo` or a `Bar`. – Asaph Jun 01 '11 at 00:02
  • 1
    @Asaph: Good point. My explanation treated `MyClass` as if it were a concrete class. It being an interface also raises another issue which I've edited my answer to mention. – ColinD Jun 01 '11 at 01:36
  • In your defense, the interface was called MyClass. – Asaph Jun 01 '11 at 03:14
4

That's because the variable _this is of type MyClass, not type T. Even though it happens to contain an instance of T, the compiler doesn't have a way of knowing that.

Tyler
  • 21,762
  • 11
  • 61
  • 90
2

I just verified that this makes the compiler happy and still constrains types in the manner that you want:

public interface MyClass {
  public class Util {
    private static MyClass _this;
      public static MyClass getInstance(Class<? extends MyClass> clazz) {
        if(_this == null) {
          try {
            _this = clazz.newInstance();
          } catch(Exception e) {
            e.printStackTrace();
          }
        }
        return _this;
    }
  }
}

Edit:

Thinking about the client code, this actually just exposes a bug in the design of this factory. Imagine this:

MyClass foo = MyClass.getInstance(Foo.class); // sets _this to a Foo and returns it

MyClass bar = MyClass.getInstance(Bar.class); // _this is already set to a Foo and
                                              // we return a Foo when we probably
                                              // are expecting a Bar!
Asaph
  • 159,146
  • 25
  • 197
  • 199
  • But then the method explicitly returns an instance of a `MyClass` and the client would have to do the cast on the outside instead, right? – aioobe May 31 '11 at 20:19
  • @aioobe: There'd be a `ClassCastException` waiting to happen if they did cast. – ColinD May 31 '11 at 20:20
  • My assumption is that the client code assigns the return value to something with type `MyClass`. – Asaph May 31 '11 at 20:24
  • I don't get it. If you say `getInstance` returns a `MyClass` then you'd have to cast the result to the desired type on the *outside*. Why wouldn't you want to say that it returns a `T` and do the cast on the inside? – aioobe May 31 '11 at 20:34
  • @aioobe: If you only ever call `MyClass` methods then there is no need to cast on the client side. Without seeing some client side code, it's impossible for us to know the use case. Actually, looking at it further, I see that `MyClass` is an empty interface... Hmm... I think the whole example is somewhat broken anyway (see my latest edit). – Asaph May 31 '11 at 20:38
  • @aioobe: Would it help if I pointed out the fact that MyClass is actually an interface? Due to the confusing name, you may have thought it was a class. Doesn't not casting seem like a good idea? If you called a method that returned a List would you cast it into an ArrayList? Probably not, right? – Asaph Jun 01 '11 at 13:15
  • Ah, but what if I want to do `Foo foo = MyClass.getInstance(Foo.class);`? – aioobe Jun 01 '11 at 13:21
1

The "Type Mismatch"...

...is due to the following:

  • T represents a subclass of MyClass.
  • getInstance is declared to return an object of type T
  • It returns an object of type MyClass.

It's like declaring a method to return a Double while it returns some Number.

The solution...

... is to change the return statement to

return (T) _this;

(and add @SuppressWarnings("unchecked") if you want to get rid of the warning).

But there's a problem...

As ColinD points out: Suppose you have

class MyClassImpl1 implements MyClass {
}

class MyClassImpl2 implements MyClass {
}

and do the following:

MyClassImpl1 o1 = MyClass.Util.getInstance(MyClassImpl1.class);
// _this now holds a value of type MyClassImpl1...

// ... which causes this line to throw a ClassCastException.
MyClassImpl2 o2 = MyClass.Util.getInstance(MyClassImpl2.class);
Community
  • 1
  • 1
aioobe
  • 413,195
  • 112
  • 811
  • 826