2

I have the following method:

public static <T> T[] toArray(Iterable<? extends T> iterable) {
  return (T[]) StreamSupport.stream(iterable.spliterator(), false).toArray();
}

My IDE tells me that there is an unchecked cast. In general, I understand the error message, but I cannot understand why the cast should be checked, because iterable has the same type I use for casting. Can someone please explain this to me?

lsiem
  • 45
  • 5
  • 1
    Hey! Why are you doing ` extends T>` instead of just ``? – akuzminykh Apr 16 '20 at 12:34
  • Doesn't it make the whole thing even more future-proof? – lsiem Apr 16 '20 at 12:49
  • No, why would it be "future-proof"? Do you know what `? extends T` does? What happens if you call this method with for `Animal[] as = toArray(some Iterable)` or `Dog[] as = toArray(some Iterable)` or `Dog[] as = toArray(some Iterable)`? Which of them work, why and is that desired? – luk2302 Apr 16 '20 at 12:54
  • 1
    There is no "future proof" here. What this does is, it allows someone to do `Animal[] animals = toArray(dogs)`, where `dogs` is, for example a `List`. Which can make sense, However, it is not needed because arrays are covariant. So if you leave it as `T` you get back an `Dog[]`, which can be assigned to an `Animal[]` as well because it is covariant. So the wildcard `? extends T` is unecessary in this case. – Zabuzard Apr 16 '20 at 12:59
  • I know what you're getting at. I've been looking for other implementations and guava uses it too: [link](https://guava.dev/releases/snapshot/api/docs/src-html/com/google/common/collect/Iterables.html#line.279). What is the difference there? – lsiem Apr 16 '20 at 13:02
  • There is nothing wrong with it, especially if you want to have more control over the `T`, it is just not really needed. – Zabuzard Apr 16 '20 at 13:04

1 Answers1

5

but I cannot understand why the cast should be checked

A checked cast is something that results in a checkcast bytecode instruction. For example:

String s = (String) someObject;

In the case of generics, a checkcast instruction can't be inserted, because there is no known type at that point in the code: the type checked by a checkcast is statically written into the bytecode; but your method has to work for all types. As such, no checkcast can be added here.

A warning is the compiler's way of saying "I can't be sure, but something looks a bit fishy here". And, indeed, there is something fishy, but the problem just doesn't occur in this code.

Let's say you call this method something like so:

Integer[] ints = toArray(iterableOfInts);

There would actually be a checked cast inserted at the call site (inserted by the compiler):

Integer[] ints = (Integer[]) toArray(iterableOfInts);

This will fail at runtime because Object[] can't be cast to Integer[]. But the failure occurs here, rather than in the toArray method.

The correct approach to fix this would be to provide something to create the T[]:

public static <T> T[] toArray(Iterable<? extends T> iterable, IntFunction<T[]> arraySupplier) {
  return StreamSupport.stream(iterable.spliterator(), false).toArray(arraySupplier);
}

Iterable<? extends T> is an appropriate parameter type, because it lets you create an array of a supertype:

Integer[] integers = toArray(iterableOfInts, Integer[]::new);
Object[] objects = toArray(iterableOfInts, Object[]::new);
Andy Turner
  • 122,430
  • 10
  • 138
  • 216