Jason S
Jason S

Reputation: 189906

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.

Upvotes: 4

Views: 228

Answers (7)

Tim Stone
Tim Stone

Reputation: 19189

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);
    }
}

Upvotes: 2

Tom Hawtin - tackline
Tom Hawtin - tackline

Reputation: 147164

Perhaps you could replace:

private Set<T> aSet;

with

private final Map<T,T> aSet;

The Set is likely implemented as a Map anyway.

Upvotes: 2

Andrei Fierbinteanu
Andrei Fierbinteanu

Reputation: 7826

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.

Upvotes: 0

aioobe
aioobe

Reputation: 421340

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.

Upvotes: 2

Andreas Dolk
Andreas Dolk

Reputation: 114847

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;
    }
}

Upvotes: 3

Johannes Rudolph
Johannes Rudolph

Reputation: 35761

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.

Upvotes: 4

Amy B
Amy B

Reputation: 17977

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);
    }
}

Upvotes: 1

Related Questions