[JAVA] Item 32: Combine generics and varargs judiciously

32. Be careful when associating generics with variadic arguments

Methods and generics with variadic arguments were released simultaneously in Java 5, but they don't go together well. The variadic argument is leaky abstraction. https://euske.github.io/slides/sem20170627/index.html When you call a method with variadic arguments, an array is created to hold the variadic arguments, and this array is visible. (When abstracted, it seems to be called leaky abstraction, which means that the user should not be aware of it.) As a result, if you pass a generic or parameterized type as a variadic argument, the compiler gets confused. If you declare a method with a non-reifiable type as a variadic argument, the compiler raises a warning. The warning is also raised when a method is called that is passed a variadic argument with a type that is inferred to be non-reifiable. The warning is as follows.

warning: [unchecked] Possible heap pollution from
    parameterized vararg type List<String>

Heap pollution occurs when various parameterized types refer to a different type. Heap pollution causes the compiler to automatically cause cast failures, violating the type-safe foundations guaranteed by generic types.

As an example, consider the following method.

// Mixing generics and varargs can violate type safety!
static void dangerous(List<String>... stringLists) {
    List<Integer> intList = List.of(42);
    Object[] objects = stringLists;
    objects[0] = intList;             // Heap pollution
    String s = stringLists[0].get(0); // ClassCastException
}

This method does not have a clear cast, but I get a `ClassCastException```. An implicit cast by the compiler was performed on the last line, failing. This indicates that it is ** unsafe to store a value in a variadic argument of type **. The reason why a compile error does not occur when a method with a variadic argument of a generic type is declared is that variadic arguments of a generic type or a parameterized type are very convenient. As an example, ```Arrays.asList (T ... a) , Collections.addAll (Collection <? Super T> c, T ... elements) ```, ```EnumSet. There are methods in the Java standard library such as of (E first, E ... rest) `` , which are type safe. Prior to Java6, there was no way for method authors to deal with warnings that appear at method invocations with generic variadic arguments. This meant that the caller had to write `@ SuppressWarnings (" unchecked ")` each time. In Java7, the SafeVarargs annotation has been added. By giving this to a method with a generic type variadic argument, the caller will not get a warning. ** The `` `SafeVarargsannotation indicates that the method author promises that the method is type-safe. ** **safevarargs```In annotation, it's really important to be type-safe, but how should we be sure that it's type-safe? An array of generic type is generated when a method is called to hold a variable length array. In the processing of a method, it is safe (that is, only a simple transfer from the argument to the method) if it does not store in the array and does not allow references from untrusted code to the array. It can be said that it is safe). It should be noted that even if nothing is stored in a variadic array, it can threaten type safety. The following example seems to be okay at first glance, but it is dangerous.

// UNSAFE - Exposes a reference to its generic parameter array!
static <T> T[] toArray(T... args) {
    return args;
}

The type of this array depends on the compile-time type of the type passed to the method argument, but the compiler does not give enough information to make an accurate decision. This method returns an array of variadic arguments, so heap pollution occurs on the call stack. To think concretely, consider the following method.

static <T> T[] pickTwo(T a, T b, T c) {
    switch(ThreadLocalRandom.current().nextInt(3)) {
      case 0: return toArray(a, b);
      case 1: return toArray(a, c);
      case 2: return toArray(b, c);
    }
    throw new AssertionError(); // Can't get here
}

In compiling this method, the compiler produces code that produces a variadic array for passing two `T``` instances to the ``` toArray``` method. The code places an array of ```Object [] so that any object can be passed by the caller. The `` toArraymethod simply returns this array to the pickTwo method, and the `` `pickTwo method returns this array to the caller. So the pickTwo method always returns an array of type ʻObject []` ``. Consider calling pickTwo from the main code below.

public static void main(String[] args) {
    String[] attributes = pickTwo("Good", "Fast", "Cheap");
}

Regarding this code, neither a compile error nor a warning appears, but when I execute it, I get a `ClassCastException` even though I have not explicitly cast it. This happens when I'm trying to make it `String [] ``` because the return type of the pickTwo``` method is ```Object [] ``. .. This example reminds us that ** it is not safe to allow other methods to access a variadic array of generic types **. There are two exceptions to this:

Here's how to use a typical safe generic type variadic argument:

// Safe method with a generic varargs parameter
@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists) {
    List<T> result = new ArrayList<>();
    for (List<? extends T> list : lists)
        result.addAll(list);
    return result;
}

safevarargsDetermining whether to annotate is simple,For all methods with variadic arguments of generic or parameterized typessafevarargsShould be annotated.. This means thattoarrayDon't write unsafe variadic methods like methods. The safe variadic method satisfies:

  1. Nothing is stored in the variadic argument
  2. Don't make variadic arguments visible to untrusted code

Also, the `` `SafeVarargs``` annotation is valid only for methods that are not overridden.

safevarargsAs a correspondence other than using annotation, from item28, variable length argumentlistIt is conceivable to replace it with. ThenflattenThe method changes as follows.

// List as a typesafe alternative to a generic varargs parameter
static <T> List<T> flatten(List<List<? extends T>> lists) {
    List<T> result = new ArrayList<>();
    for (List<? extends T> list : lists)
        result.addAll(list);
    return result;
}

The nice thing about this is that it guarantees type-safeness and you don't have to annotate the `` `SafeVarargs``` yourself. The bad news is that the client-side code can be a bit verbose and a bit slower.

Recommended Posts

Item 32: Combine generics and varargs judiciously
Item 53: Use varargs judiciously
Item 67: Optimize judiciously
Item 55: Return optionals judiciously
Item 52: Use overloading judiciously
Item 45: Use streams judiciously
Item 83: Use lazy initialization judiciously
[Java] Generics classes and generics methods
Item 66: Use native methods judiciously
URLSession with URLSession and Combine normally
java Generics T and? Difference