views:

244

answers:

5

So, I have some code that looks approximately like (truncated for brevity - ignore things like the public member variables):

  public class GenericThingy<T> {
    private T mValue;
    public final T[] mCandidates;

    public GenericThingy(T[] pCandidates, T pInitValue) {
      mCandidates = pCandidates;
      mValue = pInitValue;
    }

    public void setValue(T pNewValue) {
      mValue = pNewValue;
    }
  }

  public class GenericThingyWidget {

    private final GenericThingy<?> mThingy;
    private final JComboBox mBox;

    public GenericThingyWidget (GenericThingy<?> pThingy) {
      mThingy = pThingy;
      mBox = new JComboBox(pThingy.mCandidates);
      //do stuff here that makes the box show up
    }

    //this gets called by an external event
    public void applySelectedValue () {
      mThingy.setValue(mBox.getSelectedItem());
    }
  }
}

My problem is that the mThingy.setValue(mBox.getSelectedItem()); call generates the following error:

The method setValue(capture#4-of ?) in the type Generics.GenericThingy<capture#4-of ?> is not applicable for the arguments (Object)

I can get around this by removing the <?> from the declaration of mThingy and pThingy in GenericThingyWidget - which gives me a "GenericThingy is a raw type. References to GenericThingy should be parameterized" warning.

I also tried replacing the setValue call with

mThingy.setValue(mThingy.mCandidates[mBox.getSelectedIndex()]);

which I genuinely expected to work, but that produced a very similar error:

The method setValue(capture#4-of ?) in the type Generics.GenericThingy<capture#4-of ?> is not applicable for the arguments (capture#5-of ?)

Is there any way to do this without generating "raw type" warnings ("unchecked cast" warnings I'm OK with) and without making GenericThingyWidget into a generic type? I'd think I could cast the return of mBox.getSelectedItem() to something, but I can't figure out what that would be.

As a bonus question, why does the replacement call to mThingy.setValue not work?

+1  A: 

Update

OK, try this:

  public class GenericThingy<T> {
    private Class<T> mClazz;
    private T mValue;
    public final T[] mCandidates;

    public GenericThingy(Class<T> clazz, T[] pCandidates, T pInitValue) {
      mClazz = clazz;
      mCandidates = pCandidates;
      mValue = pInitValue;
    }

    public void setValue(Object newValue) throws ClassCastException {
      mValue = mClazz.cast(newValue);
    }
  }
Daniel Pryden
Those mean the same thing.
Yishai
@Yishai: Yes, but in previous versions of Java there were some issues with using just a bare <?>, IIRC. Any any case, I've changed my answer now to something different.
Daniel Pryden
Hmmm... looks like a workaround for removing the annoying warning, but does not really follow a strict achitecture.
subtenante
This is not a case where the annoying Class instance is necessary.
erickson
+2  A: 

You lack information in GenericThingyWidget. The ? you put means : any class extending object. Which means any, not some particular one but I don't know which one. Java can't relate one ? to another, they can not be related one to the other in a class hierarchy tree. So

mThingy.setValue(mThingy.mCandidates[mBox.getSelectedIndex()]);

this tries to put an object of any class in the setValue, which is waiting for any other class, but the ? can not tell Java these two any should be the same class.

Without parameterizing GenericThingyWidget, I don't see any way to work around it.

What I would do : parameterize GenericThingyWidget, and create a Factory static parameterized method :

public static <T> GenericThingyWidget<T> make(T someObject){
    ...
}
subtenante
"... without making GenericThingyWidget into a generic type"
erickson
Sorry I can't make any wish come true... Other people have given possibilities to avoid the warnings. My solution is clean, I don't pretend it's best for OP, just another track that he may not have thought of.
subtenante
A: 

What you need to to is parameterize GenericThingyWidget like so:

public class GenericThingyWidget<T> {

private final GenericThingy<? super T> mThingy;
private final JComboBox mBox;

public GenericThingyWidget (GenericThingy<? super T> pThingy) {
  mThingy = pThingy;
  mBox = new JComboBox(pThingy.mCandidates);
  //do stuff here that makes the box show up
}

//this gets called by an external event
public void applySelectedValue () {
  mThingy.setValue((T) mBox.getSelectedItem());
 }
}
}

Technically, you don't need the ? super T for your example, and would be fine with just a T, and perhaps it would be better in real code if you ever want to get from the GenericThingy instead of just inserting into it.

Yishai
"... without making GenericThingyWidget into a generic type"
erickson
Thanks, I missed that - read the question to quickly.
Yishai
+2  A: 
erickson
Thanks for the good answer, and the useful link! It still seems odd to me that the compiler can't figure out that two separate wildcards from the same object should have the same capture-of, but at least that explains why it doesn't work.
Sbodd
A: 

As KLE said, You can just de-parameterize GenericThingy (replace all the T's with objects). In fact, I think you have to unless you plan to pass the class of T to the constructor of GenericThingyWidget, and then dynamically cast from your mbox.getSelectedItem(), since as far as I can tell, getSelectedItem() only returns Object.

Carl