简体   繁体   中英

Unchecked cast from Object, generic class and wildcards

Here is my code:

public class ArrayTaskList<E> {
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ArrayTaskList<E> other = (ArrayTaskList<E>) obj;
        if (!Arrays.equals(db, other.db))
            return false;
        return true;
    }
}

And compiler says:

Type safety: Unchecked cast from object to arraytasklist

I understand, that it's jst an warning, but if i try this code, there are no warnings:

ArrayTaskList<?> other = (ArrayTaskList<?>) obj;

Is it more convenient solution?

The difference is that the raw type object is not typesafe while the unbounded wildcard gives you type safety.

For example with a raw type you can have code like this:

List list = new ArrayList<String>();
list.add(42); // integer
list.add(true); // boolean
list.add(whateverYouWant); // whatever you want

while this code:

List<?> list2 = new ArrayList<String>();
list2.add(42);
list2.add(true);

will cause a compiler error.

When execution reaches that line, you know that obj is an instance of ArrayTaskList . However, you don't know whether it is an ArrayTaskList<Integer> or ArrayTaskList<String> etc.

Thus, the cast generates a warning (you could try to cast a ArrayTaskList<Integer> to ArrayTaskList<String> ).

However, you don't need the type information here, so using ArrayTaskList<?> would indeed be the better solution here.

EDIT :

I had some misconception here, since even using type's boundary would cause the warning. As @svz pointed out, ArrayTaskList<?> won't add any assumptions and just enables type checking.

The compiler trusts you that the cast to ArrayTaskList is ok, while the warning is generated because you make the assumption of obj also having the type E . The compiler can't check that and thus issues the warning. With <?> or <? extends XYZ> <? extends XYZ> the compiler will ignore the type but will raise errors if any method that might fail is invoked.

Consider the following example:

Your class is ArrayTaskList<E extends Number> and thus you'd cast to ArrayTaskList<Number> or ArrayTaskList<E> (where E could be Long for example).

In that case the compiler doesn't know whether E is of type Number , Long etc. and thus warns you, since obj could be a ArrayTaskList<Double> and casting that to ArrayTaskList<Number> would allow you to add Longs to a list of Doubles (ouch).

Hence the compiler warns you of that cast.

Casting to ArrayTaskList<?> would tell the compiler to ignore the type but raise errors if you called other.add(...) , preventing you from accidential inconstencies.

Edit 2:

I still have some misconception here (I'll think more about this), but so far, here's a way to cast without a warning and still use any upper bound that E might provide:

public boolean equals(Object obj) {
  ...      
  return equals_((ArrayTaskList<?>)obj);
}

protected boolean equals_(ArrayTaskList<? extends Number> other)
{      
  if (!Arrays.equals(db, other.db))
        return false;
  return true;
}

You could also pass in the ArrayTaskList constructor an inscance of Class<E> clazz and then the cast can be done clazz.cast(...) :

public class ArrayTaskList<E> {
    Class<ArrayTaskList<E>> clazz;

    public ArrayTaskList(Class<ArrayTaskList<E>> c) {
        clazz = c;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ArrayTaskList<E> other = clazz.cast(obj);
        if (!Arrays.equals(db, other.db))
            return false;
        return true;
    }
}

You could use the following implementation instead:

@Override
public boolean equals(Object obj) {
    return obj instanceof ArrayTaskList && obj.hashCode() == hashCode();
}

@Override
public int hashCode() {
    return Arrays.hashCode(db);
}

That way, there's no unchecked cast problem anymore ;)

But please note that due to type erasure,

new ArrayTaskList<String>().equals(new ArrayTaskList<Integer>())

will return true if both have the same db array, even if one used String while the other one Integer as class parameter.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM