4

For a class I was assigned to write code to read objects of the class Vehicle using ObjectInputStream (in). The objects are stored in an ArrayList called orders.

SSCE:

// Read all orders
Object obj = in.readObject();
orders = (ArrayList<Vehicle>) obj;

However, the compiler complains:

MacBook:Homework Brienna$ javac Orders.java -Xlint:unchecked
Orders.java:152: warning: [unchecked] unchecked cast
                    orders = (ArrayList<Vehicle>) in.readObject();
                                                                ^
  required: ArrayList<Vehicle>
  found:    Object
1 warning

I always try to improve my code instead of ignoring or suppressing warnings. In this case, I have come up with a solution, but I'm trying to understand why it works, and if there is a better solution.

This update stops the warning:

// Read all orders, casting each order individually
Object obj = in.readObject();
ArrayList ar = (ArrayList) obj;
for (Object x : ar) {
    orders.add((Vehicle) x);
}

Based on what I understand from what I've been reading, it works because (ArrayList<Vehicle>) obj may throw an exception if not all the elements are Vehicle. I am confused -- non-Vehicle objects can be added to the ArrayList even if its type parameter has been specified as Vehicle? Also, is there a better solution, e.g. using instanceof?

brienna
  • 1,415
  • 1
  • 18
  • 45
  • ...why are you doing this? Why do you not know the type from the result of `readObject`? Why are you using raw types? (This is kind of the world you're in when dealing with raw types, by the way; you can't really shake that compiler warning.) – Makoto Dec 30 '17 at 01:00
  • I'm doing this for an assignment. The expected type from `readObject` is `ArrayList` but this throws a compiler warning. The solution I gave above works. And my professor's explanation for why it works didn't make sense to me, so I am hoping to find a clearer explanation. – brienna Dec 30 '17 at 01:03
  • Hang on. So the method signature is `public ArrayList readObject()`? Why are you trying to save *that* result to an `Object`? – Makoto Dec 30 '17 at 01:04
  • It's the ObjectInputStream `in`, which requires casting from Object to your object during serialization. – brienna Dec 30 '17 at 01:05

2 Answers2

6

You were close. It is always safe to cast to ArrayList<?>:

Object obj = in.readObject();
ArrayList<?> ar = (ArrayList<?>) obj;

orders.clear();
for (Object x : ar) {
    orders.add((Vehicle) x);
}

You might even want to be extra safe and cast to something more generalized, like Iterable:

Object obj = in.readObject();
Iterable<?> ar = (Iterable<?>) obj;

orders = new ArrayList<>();
for (Object x : ar) {
    orders.add((Vehicle) x);
}

If you have control over the objects which were originally serialized, there is a way to avoid the loop entirely: Use an array instead of a Collection. Array types are always a safe cast (if they don’t have a generic type themselves):

Object obj = in.readObject();
orders = new ArrayList<>(Arrays.asList((Vehicle[]) obj));
VGR
  • 40,506
  • 4
  • 48
  • 63
  • Why does this actually make the warning disappear? Isn't the cast `(Vehicle) x` also unchecked and can fail? – Manuel Apr 01 '19 at 13:56
  • 1
    @Manuel Because generic types are not *reified.* Generic types are a mechanism of the compiler, and are enforced by the compiler only. At runtime, there is no such thing as `ArrayList`, so if you cast an object to that, the runtime has no ability to check that it actually contains Vehicle instances. You are telling the compiler to assume it contains Vehicles with no way to know that such an assumption is safe. – VGR Apr 01 '19 at 14:15
1

first code excerpt your cast with generic (ArrayList) second code excerpt your cast without generic. cast is a runtime check - java compiler does type erasure, and at runtime there's actually no difference between a List<Vehicle> and List<Car>

serdroid
  • 166
  • 2
  • 7