How to avoid "Type mismatch" in static generic factory method?
Asked Answered
K

4

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?

Kiernan answered 31/5, 2011 at 20:6 Comment(4)
What are you really trying to accomplish here?Platinum
naming a static var _this sounds like asking for troubleRayner
@MatrixFrog - sigh i wanted a factory in the interface so i dont have to write one for every implementing subclass. you can laugh now ;-)Kiernan
@matt b: Naming an interface MyClass is also asking for trouble!Leta
K
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.

Kershner answered 31/5, 2011 at 20:19 Comment(4)
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.Kiernan
+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.Leta
@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.Kershner
In your defense, the interface was called MyClass.Leta
P
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.

Platinum answered 31/5, 2011 at 20:10 Comment(0)
L
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!
Leta answered 31/5, 2011 at 20:18 Comment(7)
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?Syrian
@aioobe: There'd be a ClassCastException waiting to happen if they did cast.Kershner
My assumption is that the client code assigns the return value to something with type MyClass.Leta
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?Syrian
@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).Leta
@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?Leta
Ah, but what if I want to do Foo foo = MyClass.getInstance(Foo.class);?Syrian
S
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);
Syrian answered 31/5, 2011 at 20:16 Comment(3)
I don't think this is a good idea; the method signature lies about what it can do.Kershner
Hmm.. could you explain what you mean by that? (Forgive me if I'm being slow here.)Syrian
Sorry. I just read your answer for like the third time and I see what you mean... I'll update my answer.Syrian

© 2022 - 2024 — McMap. All rights reserved.