Effective Java Third edition--32. Rationally combine generics and variable parameters

Source: Internet
Author: User
Tags addall variadic

Tips
"Effective Java, third Edition" an English version has been published, the second edition of this book presumably many people have read, known as one of the four major Java books, but the second edition of 2009 published, to now nearly 8 years, but with Java 6, 7, 8, and even 9 of the release, the Java language has undergone profound changes.
In the first time here translated into Chinese version. For everyone to learn to share.

32. Reasonable combination of generics and variable parameters

In Java 5, variable-parameter methods (entry 53) and generics are added to the platform, so you might want them to interact normally; Sadly, they did not. The purpose of a mutable parameter is to allow the client to pass a variable number of arguments to a method, but this is a fragile abstraction (leaky abstraction): When you invoke a mutable parameter method, an array is created to hold the variable arguments; the array that should be the implementation details is visible. Therefore, when a mutable parameter has a generic or parameterized type, it causes a compiler warning to be confused.

Recalling entry 28, the type of non-materialized (non-reifiable) is a type whose runtime represents less information than its compile-time representation, and that almost all generics and parameterized types are non-materialized. If a method declares a type whose mutable parameters are non-materialized, the compiler generates a warning on that declaration. The compiler also generates a warning in the call if the method is called on a mutable parameter parameter that infers the type is not deterministic. The warning looks like this:

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

Heap pollution occurs when a variable of a parameterized type references an object that does not belong to that type (heap pollution) [jls,4.12.2]. It causes the compiler to automatically generate a cast failure that violates the basic guarantee of the generic type system.

For example, consider the following method, which is a less obvious variant of the Code fragment on page 127th:

// 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 visible cast, but throws a classcastexception exception when one or more arguments are called. Its last line has an invisible conversion generated by the compiler. This conversion fails, indicating that type safety has been compromised, and that it is not safe to keep the value in a generic variable parameter array parameter .

This example raises an interesting question: why is it legal to declare a method with a generic variable parameter, when it is illegal to explicitly create a generic array? In other words, why does the previously shown method generate only one warning, and the Code fragment on page 127 generates an error? The answer is that a method with variable parameter parameters of a generic or parameterized type can be useful in practice, so the language designer chooses to endure this inconsistency. In fact, the Java Class Library exports several of these methods, including, Arrays.asList(T... a) Collections.addAll(Collection<? super T> c, T... elements) EnumSet.of(E first, E... rest) . Unlike the dangerous methods shown earlier, these class library methods are type-safe.

In Java 7, SafeVarargs annotations have been added to the platform to allow the authors of methods with generic mutable parameters to automatically suppress client warnings. Essentially, SafeVarargs annotations form the author's commitment to type-safe methods . In exchange for this commitment, the compiler agrees not to warn the user to invoke methods that may be unsafe.

Unless it is actually safe, be careful not to @SafeVarargs annotate a method with annotations. So what needs to be done to ensure this? Recall that invoking a method creates a generic array to accommodate the mutable parameters. It is safe if the method does not store anything in the array (it overrides the parameter) and does not allow an array of references to be escaped (which causes untrusted code to access the arrays). In other words, if the variable parameter array is used only to pass a variable number of arguments from the caller to the method-after all, this is the purpose of the mutable parameter-then the method is safe.

It is important to note that you can violate type safety even if you do not store anything in a mutable parameter array. Consider the following generic mutable parameter method, which returns an array containing the parameters. At first glance, it may look like a handy gadget:

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

This method simply returns an array of its mutable arguments. The method may not look dangerous, but it is! The type of the array is determined by the compile-time type of the arguments passed to the method, and the compiler may not have enough information to make the correct judgments. Because this method returns an array of its mutable parameters, it can propagate heap pollution onto the call stack.

For specific instructions, consider the following generic method, which accepts three types T of parameters and returns an array of two parameters, randomly selected:

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}

This method is not inherently dangerous, and does not produce a warning except for calling methods with generic mutable parameters toArray .

When this method is compiled, the compiler generates code to create an T toArray array of mutable parameters that are passed to two instances. This code assigns an array of Object [] type, which is the most specific type that is guaranteed to hold these instances, regardless of the type of object that is passed to the call location pickTwo . The toArray method simply returns the array and returns pickTwo pickTwo it to the caller, so it pickTwo always returns an Object [] array of type.

Now consider the pickTw method of this test main :

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

There is no problem with this approach, so it compiles without any warning. However, when you run it, a ClassCastException exception is thrown, although it does not contain a visible transform. What you don't see is that the compiler has generated a hidden cast to the pickTwo type returned by the value so that String [] it can be stored in the property. The conversion failed because it Object [] is not String [] a subtype. This failure is quite disturbing because it removes two levels from the method that actually caused the heap pollution ( toArray ), and the variable parameter array was not modified after the actual parameters were stored in it.

This example is meant to make it unsafe to give another method access to a generic variable parameter array , except for two exceptions: it is safe to pass an array to another variable parameter method, which is @SafeVarargs correctly labeled, The method of passing an array to a non-mutable parameter is safe, and the method only computes some methods of the contents of the array.

Here is a typical example of the safe use of generic mutable parameters. This method takes any number of lists as parameters, and returns a single list that contains all the input list elements sequentially. Because this method @SafeVarargs is used for labeling, no warning is generated on the declaration or its call station location:

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

The rules for deciding when to use SafeVarargs annotations are simple: use them on each method @SafeVarargs and use mutable parameters of generic or parameterized types so that users do not worry about unnecessary and confusing compiler warnings. This means that you should not write dangerous or toArray non-secure variable parameter methods. Whenever the compiler warns you that you may be exposed to heap pollution from a generic variable parameter in a method you control, check that the method is safe. As a reminder, the generic Variadic method is safe in the following situations:
1. It doesn't store anything in a mutable parameter array

2. It does not make arrays (or clones) visible to untrusted code. If any of these prohibitions are violated, please fix them.

Note that SafeVarargs annotations are only valid for methods that cannot be overridden, because it is not possible to guarantee that every possible overriding method is safe. In Java 8, annotations are only valid on static methods and final instance methods; In Java 9, it also becomes legal in private instance methods.

An SafeVarargs alternative to using annotations is to take the recommendation of entry 28 and List replace the variable parameters with parameters (this is a disguised array). Here's flatten how this approach looks when applied to our methods. Note that only the parameter declaration is changed:

// List as a typesafe alternative to a generic varargs parameterstatic <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;}

This method can then be used in conjunction with a static factory method List.of to allow a variable number of parameters. Note that this method relies on List.of declarative usage @SafeVarargs annotations:
Audience = Flatten (List.of (Friends, Romans, countrymen));

The advantage of this approach is that the compiler can prove that this method is type-safe. SafeVarargsThere is no need to use annotations to justify their security or to be afraid of making mistakes when determining security. The main drawback is that the client code is a bit verbose and may run slower.

This technique can also be used in situations where it is not possible to write a safe variable-parameter method, as in the 147th page toArray method. Its list of simulations is a List.of method, so we don't even have to write it; The Java class Library author has done the work for us. The pickTwo method then becomes this:

static <T> List<T> pickTwo(T a, T b, T c) {    switch(rnd.nextInt(3)) {      case 0: return List.of(a, b);      case 1: return List.of(a, c);      case 2: return List.of(b, c);    }    throw new AssertionError();}

mainSquare into this:

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

The generated code is type-safe because it uses only generics, not arrays.

In summary, mutable parameters and generics do not interact well because the variadic mechanism is a fragile abstraction built on top of the array, and the array has a different type rule than the generic. Although generic mutable parameters are not type-safe, they are legal. If you choose to write using a generic (or parameterized) variable parameter, first make sure that the method is type-safe, and then @SafeVarargs label it with annotations to avoid unpleasant use.

Effective Java Third edition--32. Rationally combine generics and variable parameters

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.