Reputation: 33197
I have a List
that contains a certain superclass (like Vehicle), and I would like to write a method that returns the objects in that list that are instances of a certain subclass (like Car).
So far I have this, but it generates a typical "unchecked" operation compiler warning:
public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
List<T> result = new ArrayList<T>();
for (Vehicle vehicle : getVehicles()) {
if (type.isAssignableFrom(vehicle.getClass())) {
result.add(type.cast(vehicle)); // Compiler warning here
// Note, (T)vehicle generates an "Unchecked cast" warning (IDE can see this one)
}
}
return result;
}
Warning: Note: Test.java uses unchecked or unsafe operations.
I'm ok with any other method of accomplishing this (I couldn't find anything in Collections
, but it's possible some JDK method can do it), but ideally it would provide the following interface:
List<Car> cars = getVehiclesOfType(Car.class);
I would like to know why I was receiving a compiler warning on the original code, though.
Upvotes: 8
Views: 3740
Reputation: 47296
Multiple answers to this question claim that the warning is due to the fact that the compiler can't statically verify that the cast cannot fail. Although the compiler indeed cannot verify that, that's no reason to give a warning! If the compiler would flag warnings for all casts that can't be verified statically, that banishes all meaningful casts, because when a cast can be verified statically, you generally don't need to be doing the cast in the first place. In other words, the entire point of a cast operation is that it can fail at runtime, which is a perfectly fine situation that is handled with a ClassCastException
.
The "unchecked cast" warning on the other hand, happens when you do a cast for which there is insufficient type information to verify the cast at runtime. This can happen because of the erasure of type arguments at runtime. Suppose that you cast something of static type List<?>
to List<Vehicle>
. At runtime, objects of both of these types simply have the class ArrayList
or LinkedList
as their only runtime type information, without type arguments. So the compiler can't insert any code that will verify at runtime that the object is indeed a List
of Vehicle
. So the compiler does nothing, but raises an "unchecked cast" warning.
This is a useful warning, because you may run into a problem when you start using the result of the cast. Since the result has static type List<Vehicle>
, you may write code that treats elements from the list as a Vehicle
, without having to write a cast. But there is actually still a cast at runtime, and it will fail when the List
turns out to contain anything that is not a Vehicle
. So you may get a ClassCastException
at a point where you did not expect it.
The safe way to handle such a conversion from List<?>
to List<Vehicle>
, is to iterate over each of the elements, cast them to Vehicle
(something you can verify at runtime and which will raise a ClassCastException
at a well defined point), and add them to a new list. I wrote some generic code to do precisely that in this answer.
Upvotes: 0
Reputation: 12056
2 things:
I would like to know why I was receiving a compiler warning on the original code, though.
1st: Look at the code of Class: it does hide the cast for you. Normally casting to any arbitrary type (T) should be a warning but Class.cast actually checks and ignores the compiler warning(nonsense).
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException();
return (T) obj;
}
2nd
That being said: Generics warnings is the 1st thing to disable. Having mixture of old code and now just doesn't worth suppress warning, nor I'd care. I am just waiting to see how generics reduce ClassCastException and it's probably true in only one case: using add
instead addAll
(put/putAll)
Upvotes: 1
Reputation: 3505
Personally, I think you're taking the wrong approach. Generics in Java are a compile-time only feature. You know what kind of list you need at compile-time so just create a helper method that returns the right kind of list:
public static List<Car> getCars( List<Vehicle> vlist ){ /* code here */ }
Add the helper method to the class in question and then in your code just do:
List<Car> cars = Cars.getCars( getVehicles() );
No casting issues at all. A bit more code to write, but you could also create overloaded versions that returned only blue cars, only red cars, etc.
Upvotes: 0
Reputation: 4122
You are operating on Vehicle, which is a superclass and you try to downcast to a subclass of Vehicle. This is always an unsafe cast and will earn you a compiler warning.
So while you can cast from Car
to Vehicle
without warning (since Car extends Vehicle
), the compiler has no way of knowing that the variable typed as Vehicle
is actually a car.
By using a cast (either by using (Car)
or cast(..)
), you are telling the compiler that you know better.
The compiler hates people and still hands out a warning to you :)
Upvotes: 0
Reputation: 5277
The problem is that the compiler isn't smart enough to know that vehicle is of class "type". This is a runtime check and the compiler doesn't do that kind of analysis. There are lots of situations like this. For example I use if (true) return; to exit out of a function early during debugging all the time. If I use just return, the compiler realizes that there is unreachable code, but with the conditional, the compiler doesn't realize that it's impossible to get into that branch.
Consider if you replace your conditional with if (false) {. The code has no chance of throwing an exception but still contains an unsafe cast.
Basically the compiler is saying, "I can't confirm that this is safe so it's up to you to make sure you know what you are doing." Your code isn't broken, you just need to exercise caution.
Upvotes: 1
Reputation: 51093
You're getting a warning because there's no way for the compiler (or the IDE) to know that the cast is safe, without understanding the meaning of isAssignableFrom()
. But isAssignableFrom()
isn't a language feature, it's just a library method. As far as the compiler's concerned, it's the same as if you'd said
if (type.getName().contains("Elvis")) {
result.add(type.cast(vehicle));
}
However, you know what isAssignableFrom()
means, so you know it's safe. This is exactly the sort of situation @SuppressWarnings
is meant for.
Upvotes: 6
Reputation: 61414
You might be stuck with adding @SuppressWarning("unchecked")
to the method
Upvotes: 0
Reputation: 298838
How about this?
if (type.isInstance(vehicle)) {
result.add((T)(vehicle));
}
Does the compiler still complain like that?
But if I were you I'd use Guava, that will make your method a one-liner:
public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
return Lists.newArrayList(Iterables.filter(getVehicles(), type));
}
Upvotes: 3