views:

75

answers:

1

Before I get chided for not doing my homework, I've been unable to find any clues on the multitude of questions on Java generics and dynamic casting.

The type Scalar is defined as follows:

public class Scalar <T extends Number> {

  public final String name;

  T value;

  ... 

  public T getValue() {
    return value;
  }

  public void setValue(T val) {
    this.value = val;      
  }

}

I would like to have a method that looks like this:

public void evilSetter(V val) {
  this.value = (T) val;
}

Sure, this is generally discouraged. The reason I want such a method is because I have a collection of Scalars whose values I'd like to change later. However, once they go in the collection, their generic type parameters are no longer accessible. So even if I want make an assignment that's perfectly valid at runtime, there's no way of knowing that it'll be valid at compile time, with or without generics.

Map<String, Scalar<? extends Number>> scalars = ...;

Scalar<? extends Number> scalar = scalars.get("someId");

// None of this can work
scalar.value = ...
scalar.setValue(...)

So how do I implement a checked cast and set method?

public <V extends Number> void castAndSet(V val) {
    // One possibility
    if (this.value.getClass().isAssignableFrom(val.getClass()) {
       // Some cast code here
    }
    // Another
    if (this.value.getClass().isInstanceOf(val) {
       // Some cast code here    
    }
    // What should the cast line be?
    // It can't be:
    this.value = this.value.getClass().cast(val);
    // Because this.value.getClass() is of type Class<?>, not Class<T>        

}

So I'm left with using

this.value = (T) val;

and catching a ClassCastException?

+1  A: 

You have:

this.value.getClass().isAssignableFrom(val.getClass())

This is probably going to be a problem unless you can be certain value will never be null.

You also have:

this.value = (T) val;

This will only cast to Number and not to T because under the hood T is just a Number due to type-erasure. Therefore if value is a Double and val is an Integer, no exception will be thrown.

If you actually want to perform a checked cast, you must have the correct Class<T> object. This means you should be passing Class<T> in the constructor of your object. (Unless you can be sure value is never null, in which case you can go with your first idea.) Once you have that object (stored in a field), you can perform the checked cast:

T value = valueClass.cast(val);
Kirk Woll
Thanks for the response! I can easily check for nulls. But assuming I do use my first solution, where do I go from there? The cast syntax in my original code snippet throws a compile error, and casting using (T) val still has a unchecked conversion warning, meaning that the isAssignable(...) test isn't really being used.
dgorur
@dgorur, the cast for your original code snippet wasn't specified. You just have "// Some cast code here". It *should* be: `value = value.getClass().cast(val);`, which does compile.
Kirk Woll
I put the cast code at the bottom of the code snippet, and the reason it wouldn't compile for me. Interesting that it did compile for you! Could it be javac version or something? Eclipse tells me: Type mismatch: cannot convert from capture#1-of ? extends Number to T. That's how I arrived at the reason I provided in the code snippet...
dgorur
@dgorur, What is the compiler error?
Kirk Woll
CastAway.java:16: incompatible types found : capture#556 of ? extends java.lang.Number required: T value = value.getClass().cast(val); ^1 error
dgorur
$ javac -versionjavac 1.6.0_20
dgorur
@dgorur, you're right, I had a mistake in my sample and forgot that `getClass()` returns `Class<?>` instead of `Class<T>`. You'll have to pass the `Class<T>` (valueClass) into the constructor of your class.
Kirk Woll
Thanks for your help!
dgorur