10

If you have a raw type in Java, you can safely assign/cast this to the same type with an unbounded wildcard. For example a List can be safely cast to a List<?>, which removes its raw nature and allows you to use it in a safe (type-checked) manner1.

On the other hand, Java doesn't let you cast from a List itself parameterized with a raw type, like List<Optional> to a list of the same type parameter with an unbounded wildcard, like List<Optional<?>>.

You can still do it by dropping all the way down a raw List and back up again (implicitly via the assignment):

List<Optional> rawOptionalList = null;
List<Optional<?>> wildcardOptionalList = (List)rawOptionalList;

Of course, this triggers warning about unchecked conversion (from List to List<Optional<?>>).

It seems to me though that this conversion is guaranteed safe: isn't List<Optional<?>> just as safe as List<Optional> in the same way that casting a raw Optional to an Optional<?> is safe?


1 ... but you'll never be able to add anything to this list, since nothing will match the capture of ? for the add(?) method. That's the price you pay for safety.

BeeOnRope
  • 51,419
  • 13
  • 149
  • 309
  • I think it's safe. Or at least it's all you can do. `Optional` is a raw type, you don't know what's in it. `Optional>` is the safest guess. But you could also use `Optional` if it's really any old type of `Optional` at all. – markspace Dec 05 '17 at 19:01
  • @markspace - perhaps `Optional` wasn't the best example since it is immutable and so only acts as a "producer" not a consumer, but in general it isn't safe to use a raw `Foo` as an `Foo`: consider for example `AtomicReference` and corresponding qualified type `AtomicReference`: if you have something that's really an `AtomicRefererce` and end up treating it as `AtomicReference` you could unsafely insert non-string objects into it. `AtomicReference>` is safe though: you can't set it with _anything_ (because nothing will match the capture of `?`). – BeeOnRope Dec 05 '17 at 19:10
  • Well, here the scenario you present in your OP is that you have a raw type, The same idea would use `AtomicReference` in your example in the comment. In that case, you have no idea that only strings have been used with that object, and the compiler was not enforcing any such rule. So `AtomicReference` would be "safe" in that it was just following the same rule, which is no rule. – markspace Dec 05 '17 at 19:13
  • @markspace - not really. You have a raw type, so yes you don't know anything about (why you have a raw type is besides the point). **Even in that state** a cast from `Optional` to `Optional>` is totally safe and doesn't incur a checked cast, because every possible `Optional` type is compatible with `Optional>`. A cast from `Optional` to `Optional` is **unsafe** and requires an unchecked cast (and may result in spurious cast failures later), because every possible `Optional` type is not compatible with `Optional`. – BeeOnRope Dec 05 '17 at 19:16
  • I think we just disagree on what is actually unsafe in this context. Before generics, all code was "unsafe" yet we worked with it. Raw types and just work the same way. If that's what your existing code does, it doesn't mean that it's really unsafe, just that the programmer is responsible for doing the right thing, not the compiler. – markspace Dec 05 '17 at 19:19
  • Maybe we are using the the word "unsafe" differently. I'm using "unsafe" in the specific sense of unsafe/unchecked type conversions with generics specifically. This concept was introduced when generics were introduced and occurs because they use type erase. Prior to generic all code was _safe_ in this respect: everything just used Object, and all casts were explicit. You could never get a cast-class exception in code that didn't a cast. Generics introduced unchecked conversions that mean you could have a `List` that doens't actually contain `Foo` objects at all! – BeeOnRope Dec 05 '17 at 19:23
  • So something like `List list = ...; Foo f = list.get(0);` could result in a class cast exception in "impossible" places like that assignment. Java tells you when you are doing an unsafe/unchecked cast though, and if you don't do that you won't have problems. Most uses of raw types are unsafe in that regard. However a cast from a raw type to an unbounded wildcard like `List` to `List>` is totally safe! You made the raw type safe, at the cost of not being able to insert into it. A cast from `List` to `List` is totally unsafe, and the compiler will tell you. Try it. @markspace – BeeOnRope Dec 05 '17 at 19:27
  • interesting question. For this particular example I think you are safe, in the sense that the only thing you can *get* from that List is an Object (of course after calling `Optional#get`). My guess is that when you cast - the compiler does not look at the assign type (in the sense that it can deduce the type you would get from that), but only at the cast itself. – Eugene Dec 06 '17 at 09:02

2 Answers2

2

Yes, it's safe. The generic check is just at compile time.

Lukas Bradley
  • 380
  • 2
  • 10
  • 2
    By this standard, casting `List` to `List` is safe. – Alexey Romanov Dec 05 '17 at 19:07
  • @LukasBradley it's *probably* safe only for this example, as `List.get(n).get()` can only return an `Object` in this case, but I doubt this is correct *in general* – Eugene Dec 06 '17 at 13:19
1

Type erasure in Java means that all parameterized types are erased at runtime. In your case, a List<Optional> is just a List at runtime and an Optional<MyClass> is just an Optional at runtime.

When you get a contained class from a List or an Optional, Java makes a cast to the parameterized type. So not only is your code safe, it is exactly what the Java compiler does.

Nate Vaughan
  • 2,537
  • 1
  • 22
  • 33
  • 1
    Sure, the types are _erased_ at runtime but the type system relies help from the compiler and the concept of checked casts to ensure that `List` and `List` each only actually contain `Integer` and `String` objects at runtime. If that's violated, you'll get class cast exceptions in mysterious places. So the fact that the type of `List` is erased doesn't really help here: I could also do `List l = (List)Arrays.asList(new String[]{"foo"});` which would definitely be unsafe! The compiler warns you though, just like it does for my case - is the warning valid in my case. – BeeOnRope Dec 05 '17 at 19:31