views:

167

answers:

7
+4  Q: 

generics question

I get a warning about unchecked casts on the "return (T) value;" line. Is there a better way to do this, or should I just suppress the warning?

class SomeClass<T>
{
    /* ... other methods ... */

    private Set<T> aSet;
    public T filter(Object value)
    {
        if (this.aSet.contains(value))
            return (T) value;
        else
            return null;
    }
}

edit: I'm stuck with public T filter(Object value) as a signature.

+4  A: 

What about using the generic type argument as the argument type to filter.

class SomeClass
{
    /* ... other methods ... */

    private Set<T> aSet;
    public T filter(T value)
    {
        if (this.aSet.contains(value))
            return (T) value;
        else
            return null;
    }
}

Ok, since you're stuck with the object signature, you don't have any other chance but do disable/ignore the warning. Java Generics are no "real generics" in the sense that the underlying type system supports them. In fact, they're just a compiler thing since they're based on Type Erasure. Performance penalities and possibly unsafe casting is the price for maintaing binary compatibility with older versions of the JVM.

You can contrast that with the .NET CLR that has real generics, I've written a blog post comparing the two approaches recently to which you can also refer if any of what I said above left you confused.

Johannes Rudolph
You forgot the space between T and value and you shouldn't need the cast to (T) in the return statement.
Thomas Owens
yes and I just wanted to edit that and they were... just there ;-)
Johannes Rudolph
This is a possibility in general but not in my case: I am using it with a class that predates generics so it just gives me an Object.
Jason S
Jason S: If your class predates generics, how do you have a Set<T> and a method that returns a type T? Also, perhaps you should consider refactoring your class to support generics to ensure safer code (it shouldn't break any code that fails to use generics, either, but will make future code safer).
Thomas Owens
*my* class doesn't predate generics, but I'm stuck with implementing a pre-existing interface; some of the methods take an Object as input so I was trying to write a helper function that would handle the casting nastiness.
Jason S
+1  A: 

Is this what you mean?

public class SomeClass<T>
{
    private Set<T> aSet;

    public T filter(Object value)
    {
        return (aSet.contains(value) ? (T) value : null);
    }
}
Coronatus
yes, but why would that be better? (I'm reading this on another computer where I don't have easy access to a Java IDE)
Jason S
Because it complies, and the ternary `if` is nicer :)
Coronatus
this doesn't solve the problem, you're just changing the syntax.
teehoo
@teehoo - The problem was the the original didn't compile. The code I posted *does* compile because of the additional <T> in the class declaration.
Coronatus
Could you explain why is this valid? Sorry if it's a elementary question.
Michael Foukarakis
@Michael Foukarakis - `T` would be defined when the object is created, so `T` is always known. That's the only difference.
Coronatus
Indeed, it makes sense now. Thanks.
Michael Foukarakis
+3  A: 

In this case you could suppress the warning and leave a comment why it is save:

   if (this.aSet.contains(value)) {
        // this following cast from Object to T is save, because ...
        @SuppressWarnings("unchecked") T result = (T) value;
        return result;
   } else
        return null;

There's an alternative but that would require, that the class or the method 'knows' it's parametized type. Then we can cast without a warning (but should comment too). I show a solution where we introduce / change a constructor to store the 'generics' information as a field:

public class SomeClass<T> {

    private Set<T> aSet;

    private Class<T> genericType;
    public SomeClass(Class<T> genericType) {
        this.genericType = genericType;
    }

    public T filter(Object value)
    {
        if (this.aSet.contains(value))
            // this following cast from Object to T is save, because ...
            return genericType.cast(value);
        else
            return null;
    }
}
Andreas_D
Do not forget `genericType.cast(...)` may throw `ClassCastException`! Of course, so can the normal cast syntax, but the `Class` casting can only throw an exception at runtime.
Brian S
@Brian - that's the reason for the comment in the previous line. But at least, the ClassCastException thrown by `cast` is pretty verbose.
Andreas_D
+2  A: 

It's possible :)

public T filter(Object value)
{
    if (this.aSet.contains(value)) {
        Set<T> tmp = new TreeSet<T>(aSet);
        tmp.retainAll(Collections.singleton(value));
        return tmp.iterator().next();
    }
    return null;
}

but it's obviously uglier than doing a cast.

aioobe
+1 for the interestingness.
Jason S
A: 

The problem with doing it that way is that it's a ClassCastException waiting to happen. If you have an object that is not of class T, but is equals with one of your elements, when you try to cast it to T it will fail. What you need is to get the actual value that is contained in the set, which you know is of type T. Unfortunately Set doesn't have a get method, so you need to temporarily convert it to a collection that does have that like a List.

You could try doing it like this:

private Set<T> aSet;

public T filter(Object value) {
    List<T> list = new ArrayList<T>(aSet);
    int index = list.indexOf(value);
    if (index == -1) {
        return null;
    }
    T setValue = list.get(index);
    return setValue;
}

This might have a slower performance, but it offers a bit more type safety.

Andrei Fierbinteanu
+2  A: 

Perhaps you could replace:

private Set<T> aSet;

with

private final Map<T,T> aSet;

The Set is likely implemented as a Map anyway.

Tom Hawtin - tackline
Wow, I like it!
sixtyfootersdude
+2  A: 

Elaborating on Tom Hawtin's answer, you have the option of using a Map<T,T> instead, which gets around the casting issue. If you're using a HashSet<T> for aSet's implementation, HashSet uses a HashMap<T,HashSet<T>> behind the scenes anyway (it uses references to itself as the values - not sure if there's a reason for this other than choice - with the set's "values" as keys) and performs the bulk of its operations just by reinterpreting the return values of the Map functions.

Consequently, you could do this, if you wanted to (and I don't see an immediate reason why it would be any less performant than a HashSet):

class SomeClass<T>
{
    /* ... other methods ... */

    private Map<T,T> aSet;

    public T filter(Object value)
    {
        // Will return the properly-typed object if it's in
        // the "set" otherwise will return null
        return aSet.get(value);
    }
}
Tim Stone
Thanks for the extra explanation Tim
sixtyfootersdude