2

I am doing tasks from the Java SE 8 book by Cay S. Horstmann (Chapter 6 task 21)

Using the @SafeVarargs annotation, write a method that allows you to build arrays of generalized types, as in the following example:

List<String>[] result = Arrays.<List<String> > construct (10) ;
// Sets the result in a list of type List<String>[] with a length of 10

My intended solution was such

static <T> T[] construct(int size) {
    ArrayList<String> arr = new ArrayList<>();
    for (int i = 0; i < size; i++) arr.add(null);

    return (T[]) arr.toArray();
}

but it is incorrect since after erasing occurs

Exception in thread "main" java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.util.List; ([Ljava.lang.Object; and [Ljava.util.List; are in module java.base of loader 'bootstrap') at Main.main(Main.java:23)

5
  • 4
    I must be missing something here. As far as I know, it’s not possible to create an array without knowing the type of the array’s elements. Commented Aug 29, 2022 at 13:49
  • matt, according to the condition, it is not known in advance what the array needs to be created with. This is a List only in a specific example. Commented Aug 29, 2022 at 15:47
  • VRG, I suppose otherwise the exercises in the books would not have been checked, I asked for SO and checked again, this is not a question of "is it possible?" this is exactly the task and I do not know how to do it... Commented Aug 29, 2022 at 15:49
  • 1
    Is construct supposed to only have the size as parameter? Because in this case it's just straight up impossible. Commented Aug 29, 2022 at 16:55
  • @Alex In your example, your using ArrayList<String> explicitly, then trying to create a String[] even though you're providing List<String>. You seem to be missing a big part of the excercise, @SafeVarargs. Maybe using a Vararg and Arrays.copyOf Commented Aug 29, 2022 at 19:28

3 Answers 3

3

Don’t use this in production code. This is only for answering the book’s question as-is, for educational purpose. See the end of the answer for details.

The @SafeVarargs does already provide a hint towards the intended solution. Make the method a varargs method. Since a variable number of arguments also allows zero arguments, this change allows unchanged invocations:

public class Example
{
  @SafeVarargs
  static <T> T[] construct(int size, T... template) {
      return Arrays.copyOf(template, size);
  }

  public static void main(String args[])
  {
    List<String>[] result = Example.<List<String>>construct(10);
    System.out.println(result.getClass().getComponentType());
  }
}

Note that you don’t need explicit type parameters here. The caller can be simplified to

List<String>[] result = Example.construct(10);

which implies that we can even omit the declaring class when being in the same class or using import static …

List<String>[] result = construct(10);

But when the caller does provide arguments to the varargs parameter, they may show up in the resulting array. To enforce an array with only null elements, you could use

@SafeVarargs
static <T> T[] construct(int size, T... template) {
    return Arrays.copyOf(Arrays.copyOf(template, 0), size);
}

or

@SafeVarargs
static <T> T[] construct(int size, T... template) {
    T[] array = Arrays.copyOf(template, size);
    Arrays.fill(array, null);
    return array;
}

which can be tested with

List<String>[] result = construct(10, Collections.emptyList());
System.out.println(result.getClass().getComponentType());
System.out.println(Arrays.toString(result));

But as said, don’t use this code. The creation of generic arrays is not allowed for a reason. Consider

List<String>[] result = construct(10);
Object[] array = result;
array[0] = Arrays.asList(42);
List<String> stringList = result[0];

// this situation is called heap pollution

// at an entirely different place in you application, you might do
String s = stringList.get(0);
// without understanding what's going on

The reason why this code does not produce a warning, is that the @SafeVarargs prevents this and is used incorrectly here. For a varargs method to be safe, we must prevent any assignment to the array (this applies to copies of the array as well), but as soon as we return a generic array, we can’t guaranty this. As the user newacct correctly pointed out, there’s not even a guaranty that the array’s raw element type matches the intended type, e.g. if the caller also uses a type variable for T. If this has an impact on a method’s behavior, it must not use the @SafeVarargs annotation.

Sign up to request clarification or add additional context in comments.

5 Comments

It is wrong to annotate construct() with @SafeVarargs, because @SafeVarargs is only for methods which do not rely on the runtime class of the varargs array, and which instead only rely on the runtime types of the elements of the varargs array. But construct() does rely on the runtime class of the varargs array template (Arrays.copyOf uses the runtime component type of template to determine the runtime component type of the array to return).
You can see the problem if you have a method static <T> T[] foo(T obj) { return construct(10, obj); }. If you call this with List<String>[] result = foo(Collections.emptyList());, you will get a ClassCastException, even though there were no casts and no warnings.
@newacct I already demonstrated the problem at the end of the answer. But maybe it needs more emphasis.
That's actually a different problem. You are saying that an array's runtime checking of its element type doesn't work for parameterized types. This is true whenever you have an array of a parameterized type. However, I am pointing out that it is absolutely wrong to use @SafeVarargs there, because the array can be the wrong runtime type altogether.
@newacct it’s just a different symptom of the same problem. It wouldn’t make a difference if this method returned the original array without Arrays.copyOf, by the way. In general, every method returning a generic array is broken in this way and the only way to achieve this at all, are unchecked casts or varargs invocations. In either case, using annotations to suppress warnings, is inappropriate then.
1

It's a common mistake. arr.toArray() returns Object[] and then you try to cast it to List[] array. Unlike generics the type of arrays is not removed during runtime. That's why you get a class cast exception.

1 Comment

This is missing the answer part. You're explaining the error, but not how to solve it.
1

It's not possible to write such a method in Java that will work in all circumstances, and I can prove it. The mention of @SafeVarargs indicates that they want you to use varargs, and the varargs semantics does allow you to do some interesting things, but it will still not allow you to write such a method that works in all circumstances.

When you have a varargs method in Java like someMethod(int size, String... args), it receives a second argument of type String[], and when you call it with (0 or more) String arguments (and not a String[] argument directly), the compiler will implicitly create a new array like new String[] { ... } and pass that to the method under the hood.

The varargs parameter can also be of type variable type like someMethod(int size, T... args). If you call this method with 0 or more T arguments, the compiler will also try to implicitly create an array of the arguments. If the compiler knows (whether by inference or you explicitly specified) that for this call T is a reifiable type like String, then it will do new String[] { ... } with no problems.

However, when the compiler's (inferred or explicitly specified) T for this call is a parameterized type or a type variable, there is a problem because Java does now allow creating an array of a parameterized type (e.g. new List<String>[...]) or an array of a type variable type (e.g. new U[...]). What Java does is implicitly create an array with the component type being the erasure of what it inferred T to be. So if T for this call is List<String>, it will create new List[] { ... }, and if T for this call is U, then it will create an array of the upper bound of U; so if U is unbounded, this would be new Object[] { ... }. It will then produce a warning to indicate that this is unsafe.

The reason why this warning is necessary is that the runtime type of the varargs array object received in the method is not compatible with the parameter's declared type (this is called "heap pollution"). In the case where the expected type is an array of a parameterized type (e.g. List<String>[]) and it creates an array of the raw type (e.g. List[]), the difference is mostly not a big deal (although there are still some reasons why a warning is a good idea that I won't go into), and in fact people often use new List[...] as a workaround when needing to create an array of a parameterized type. However, in the case where the expected type is an array of a type variable type (e.g. U[]), creating an array of the upper bound type (e.g. Object[]) is potentially a big problem, because if this is returned to a scope which expects U to be a specific type (e.g. String), then it will expect U[] to be String[], and an array of runtime type Object[] will not be able to be put into that type since String[] and Object[] are actually separate classes at runtime, and Object[] is not a subclass of String[].

For most varargs methods that take T[], they just iterate through the varargs arguments, and they just care that the elements are of type T, and don't care that the runtime type of the varargs array object is T[]. In such cases, Java provides a @SafeVarargs annotation that can be put on the method to suppress this warning. However, in varargs methods that do care about the runtime type of the varargs array object, you should not use this annotation, because it suppresses the warning that warns of a real problem.

@Holger's answer, which is probably what the book's author was thinking of, takes advantage of varargs' implicit array creation. Even in the case of 0 variable arguments, the compiler still implicitly creates an array of the right type, and the array carries runtime information about its component type, which the method can try to use to create the array to return. So in the case of Arrays.<List<String>>construct(10), even though it doesn't look like you are passing any information in at runtime about the component type of the array, you actually are because the compiler implicitly passes a new List[] {}, and this carries the type List into the method at runtime. (Again, there is a difference between List and List<String>, but the difference is minor, and in most cases, you are not going to run into problems with this.) But this only works because the component type (List) is known at compile time, so the compiler can hard-code it at compile-time.

If you instead call this method where the component type is not known at compile time, i.e. is a type variable like Arrays.<U>construct(10), then the only thing that the compiler can send into it is new Object[], i.e. the only type information that can be passed into the method at runtime is the upper bound of the type variable (because that is known at compile time), but not the actual type that the type variable represents. The method can then only create an array with runtime type of Object[], which is incompatible with the right type of U[] (unless U happens to be Object). As mentioned above, the compiler usually generates a warning when this happens, but the book suggests you to use @SafeVarargs to suppress this warning, so you wouldn't get a warning. (And @SafeVarargs is not suitable for such a method, because it does use the runtime type of the varargs array object to create its new array.)

We can extend this further to create a proof by contradiction that the method that the question asks for is impossible in Java. We assume that there does exist a way to write the method, and assume that we have the working implementation of construct(), that is annotated with @SafeVarargs. Then we can use it to write this method:

public static <U> U[] foo(int size) {
    return construct(int);
}

This method takes a size, and returns an array of the desired component type with that size, without needing to be passed any information at runtime about what that component type is. This method does not use varargs, so there is no varargs shenanigans like implicit arguments under the hood. This is just a plain non-varargs method that just takes one parameter, the size. It compiles no warnings (since construct() is annotated with @SafeVarargs). It is very obvious that this method is impossible in Java. To create an array in Java, we need to provide the component type at runtime, and this method purports to magically be able to create an array of any type you want, without you telling it what type it is that you want at runtime. This is basically creation of an array of type variable type (i.e. new U[...]), and we know that this is not possible. Since this is a contradiction, that proves that the assumption that there exists a way to write the construct() method, is false.

If you limit the circumstances that the method has to work in to only those where the component type is a reifiable type or parameterized type, and not a type variable type, then @Holger's answer would qualify. But 1) that was not stipulated in the question from the book, and 2) such a restriction would go against the whole spirit of generics, since a generic method should work just as well no matter if it's called with T being a concrete type or type variable -- it shouldn't care at all.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.