Valid Java, inclutivejava
The generic type used since Java 1.5 is quite intuitive... "I don't need to check the type or make a strong conversion if the type is written in angle brackets ".
Indeed, from the perspective of API users, what is the significance of generics?
Discover errors as soon as possible after they are made, ideally at compile time.
Generics provide this capability.
For example, a set that can only be added to a String is guaranteed by watching this restriction without declaring type parameters.
Then add an Integer instance to the set, and use strong rotation when getting elements from the set, resulting in ClassCastException.
When such an error occurs at run time, compiler is helpless, while when the type parameter is declared, compile-time error occurs.
Compared with raw type, generics have obvious advantages, that is, security and presentation.
Is it better to have a generic type than raw type?
What if the type parameter is an Object?
What is the difference between this usage and raw type?
If you only use the code description, it can be said that raw type does not necessarily support which type, and Collection <object> supports any type.
Correct, but it makes no sense.
Generic rules are called subtyping rules. For example, List is a subtype of List, but not a subtype of List <object>.
The following code describes this situation:
// Uses raw type (List) - fails at runtime!public static void main(String[] args) { List<String> strings = new ArrayList<String>(); unsafeAdd(strings, new Integer(42)); String s = strings.get(0); // Compiler-generated cast}private static void unsafeAdd(List list, Object o) { list.add(o);}
Only errors can be found during the running as a result of the above situation. The following method is that compilation fails:
Test.java:5: unsafeAdd(List<Object>,Object) cannot be applied to (List<String>,Integer) unsafeAdd(strings, new Integer(42)); ^
To cope with this situation, Java providesUnbounded wildcard type.
That is, for uncertain type parameters '? .
For example, Set <?> It can be understood as a collection of Certain types.
Raw type is rarely used for encoding, but there are two exceptions, and they are related to generic erasure.
- Must use raw types in class literals.
- It is illegal to use the instanceof operator on parameterized type.
In this case, why does Java retain the raw type usage?
At the time of the release of Java 1.5, Java was about to usher in the First Decade, and there was already a lot of code.
It is vital for old code to interact with new features, that isMigration compatibility.
Now let's talk about how to use generics.
For raw type, if raw type is used in some ides, a warning is displayed and @ SuppressWarnings is prompted.
For example, in eclipse:
@ SuppressWarnings what is the role?
The author reminds us that:Eliminate these warnings as much as possible.
The prompt "@ SuppressWarnings" is to convey a message that the security of type conversion cannot be checked at runtime.
The programmer's elimination of these warnings is a message that ClassCastException does not occur during the runtime.
The method of declaring type parameters is preferred when warnings are eliminated. If warnings cannot be eliminated for some reason and you need to prove that the code is correct, @ SuppressWarnings is used.
As shown in, @ SuppressWarnings can be used in variables and methods. We give priority to smaller granularity.
For @ SuppressWarnings, do not ignore and do not blindly.
Next, let's talk about subtyping. I always think that this feature makes generics sometimes troublesome.
Covariant and invaritant.
For example, if the array is covariant and Sub is a subclass of Super, Sub [] is a subclass of Super.
On the contrary, the generic type is invariant, and the List is not a subclass of the List.
Given this difference, it is difficult to mix arrays and generics.
For example, the following statements are invalid:
new List<E>[]new List<String>[]new E[]
The following code illustrates why generic arrays are invalid:
// Why generic array creation is illegal - won't compile!List<String>[] stringLists = new List<String>[1]; // (1)List<Integer> intList = Arrays.asList(42); // (2)Object[] objects = stringLists; // (3)objects[0] = intList; // (4)String s = stringLists[0].get(0); // (5)
FirstHypothesisThe first line is valid.
The second row is legal. The third row is also legal because the first row is legal and the array is changed collaboratively.
Given that generics are implemented by erasure, that is, the List runtime type is List. Correspondingly, the List [] runtime type is List [], and the fourth row is legal.
The fifth line is a conflict. What about the String type? Why is ClassCastException returned when the type parameter is declared?
In this case, the first line will generate a compile-time error, and the generics will become better.
For example, the following code:
interface Function<T> { T apply(T arg1, T arg2);}static Object reduce(List list, Function f, Object initVal) { Object[] snapshot; snapshot = list.toArray(); Object result = initVal; for (Object e : snapshot) result = f.apply(result, e); return result;}
Now I want to change reduce to a generic method, so I changed it to the following form:
static <E> E reduce(List<E> list, Function<E> f, E initVal) { E[] snapshot = (E[])list.toArray(); E result = initVal; for (E e : snapshot) result = f.apply(result, e); return result;}
Obviously, the result is that the compilation fails.
The result is that there is no problem except the prompt @ SuppressWarnings on list. toArray, and it can run properly.
Do not ignore @ SuppressWarnings! Prompt me to add @ SuppressWarnings to tell me that the security of type conversion cannot be checked at runtime.
Should I add @ SuppressWarnings? How can we ensure it if it is added?
In fact, the solution is very simple, that is, do not mix arrays and generics, that is:
static <E> E reduce(List<E> list, Function<E> f, E initVal) { List<E> snapshot; synchronized (list) { snapshot = new ArrayList<E>(list); } E result = initVal; for (E e : snapshot) result = f.apply(result, e); return result;}
This is good. It can be used with generics.
But in another way, as a provider rather than a user, will the problem still be so easy?
For example, describe a stack:
// Public class Stack cannot be compiled by <E> {private E [] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack () {elements = new E [DEFAULT_INITIAL_CAPACITY];} public void push (E e) {ensureCapacity (); elements [size ++] = e;} public E pop () {if (size = 0) throw new EmptyStackException (); E result = elements [-- size]; elements [size] = null; // Eliminate obsolete reference return result ;} // check method omitted}
Obviously, arrays are specific, new E [DEFAULTINITIALCAPACITY] indicates Cannot create a generic array of E.
So I changed it to (E []) new Object [DEFAULTINITIALCAPACITY].
In this way, you can pass it, and then you will be prompted to add @ SuppressWarnings...
I cannot ignore it, but I can prove that no ClassCastException occurs.
That is, elements is private, and there is no way to directly access it. The push and pop types are secure.
Alternatively, I can change the elements type to Object [] and convert the element type to E in pop.
Rather than making the user turn, it is better for the provider to provide a secure generic type.
However, not all situations are as smooth as the above example.
For example, I added one in the Stack:
public void pushAll(Iterable<E> src) { for (E e : src) push(e);}
Then I passed the Iterable into the Stack. Since the generics are not covariant, a compile-time error is generated.
However, it seems appropriate to put a bunch of integers in a bunch of numbers.
So we have the bounded wildcard type, and the key is this bounded.
A '? 'Is wildcard, and the restriction is bounded wildcard, that is:
public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e);}
Correspondingly, we provide a popAll method to add pop elements to the specified set.
For example, the elements in the Stack must be added to the Collection <object>, that is:
public void popAll(Collection<? super E> dst) { while (!isEmpty()) dst.add(pop());}
For the use of generic wildcard, the author points out: PECS, stands for producer-extends, consumer-super.
That is, the type parameter indicates that a producer uses <? Extends T>, the consumer uses <? Super T>.
For another example, we want to merge the elements of two sets. Because this is a production behavior, the statement is:
public static <E> Set<E> union(Set<? extends E> s1,Set<? extends E> s2);
Can I use wildcards in the return type?
We recommend that you do not use it in the return type as this will complicate the call.
Here is a more complex example. For example, I want to find the largest element in a List of parameter types.
The initial statement is:
public static <T extends Comparable<T>> T max(List<T> list)
The returned result is obtained from the list parameter, so the parameter declaration is changed to List <? Extends T> list.
How can I handle <T extends Comparable>.
Take ScheduledFuture and Delayed in java. util. concurrent as an example.
(Ps: interface ScheduledFuture extends Delayed and interface Delayed extends Comparable .)
That is, the type T itself does not implement Comparable, but its parent class implements Comparable, so the declaration is:
public static <T extends Comparable<? super T> > T max(List<? extends T> list)
There is also an interesting example. Let's look at the code first:
public static void swap(List<?> list, int i, int j) { list.set(i, list.set(j, list.get(i)));}
This Code cannot be compiled because elements other than null cannot be added to the List. <?> .
Of course, if you declare a type parameter directly, there is no problem, but now we assume that we only use wildcards, and we cannot use raw type.
Since we know that the type parameter can be solved, we can do this:
public static void swap(List<?> list, int i, int j) { swapHelper(list, i, j);}private static <E> void swapHelper(List<E> list, int i, int j) { list.set(i, list.set(j, list.get(i)));}