-
Notifications
You must be signed in to change notification settings - Fork 67
/
chapter22-variance.jsh
137 lines (111 loc) · 5.3 KB
/
chapter22-variance.jsh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// To starts, run jshell --enable-preview which is a program able to interpret Java syntax
// then cut and paste the following lines to see how it works
// To exit jshell type /exit
// # Generics Variance
// Let say we have a list of String and a list of Integer and
// we want to write a method being able to print all the values
// from those lists, we may write something like this
void printAll(List<Object> list) {
list.forEach(System.out::println);
}
List<Integer> list = List.of(42, 777);
printAll(list);
// This line doesn't compile because generics in Java are invariant,
// you can only call `printAll()` with a list of Object and not a list of String.
// You may be confused because both for a List<Object> and a List<String>,
// you can get any items and see it as an Object.
// But List<Object> also means that you can set() any cell with an Object,
// something which is clearly not possible if it's a List<String>.
// That's why generics in Java are invariant.
// ## `? extends`
// The way to solve this problem is to say to the compiler, i want a list of Object
// and I pinky swear that i will not call set() with an Object.
// The type system not trusting humans, we invent a notation
// `List<? extends Object>` for that
void printAll(List<? extends Object> list) {
list.forEach(System.out::println);
}
List<Integer> list = List.of(42, 777);
printAll(list);
// There is no class List<? extends Object> at runtime, it's just a type that can be
// a list of anything (`?` means any types) that is a subtype of Object at runtime.
// Note that `?` is not a type, the type is `? extends Something` and it only exist
// in between `<` and `>`.
// If a method as a parameter typed Optional<? extends CharSequence>, you can call
// that method with anything which is a optional of a subtype of CharSequence.
// Inside the method, you can not call any method of Optional<E> that takes
// an E because, it may be stored inside the Optional.
// Note that this is an approximation, this code does not compile even if orElse()
// doesn't change the content of the Optional because the compiler has no way
// to know that implementation of orElse().
/*
void printOptional(Optional<? extends Object> list) {
System.out.println(list.orElse(new Object()));
}
*/
// There is an exception, you call the method that takes an E if the value is null
// because you can store null in any Optional. So the following code compiles
void printOptional(Optional<? extends Object> list) {
System.out.println(list.orElse(null));
}
Optional<String> optional = Optional.of("foo");
printOptional(optional);
// ### `?`
// `?` is a short syntax for `? extends Object`.
void printOptional(Optional<?> list) {
System.out.println(list.orElse(null));
}
Optional<String> optional = Optional.of("foo");
printOptional(optional);
// ## `? super`
// Sometimes you want to do the opposite, store something in a list
// by example, you want to write a method addOne that add an element
// into any list that can store a String, but it you declare it like
// this, you can not call it with a List<Object>
void addOne(String s, List<String> list) {
list.add(s);
}
addOne("foo", new ArrayList<Object>());
// Again, we have a notation for that, List<? super String>, it means
// a list of the supertype of a String.
void addOne(String s, List<? super String> list) {
list.add(s);
}
addOne("foo", new ArrayList<Object>());
// In that case, it means that if you try to call a method a method
// that return a E, the compiler will not be able to type it correctly
// because it can be any supertype of String
// Again, there is an exception because you can always store anything
// as Object so the following code compiles
void foo(List<? super String> list) {
// I can call any method that takes an E with a String
// an i can also write because any object is an Object in Java
Object o = list.get(0);
}
// ## Where to put some `? extends`/`? super`
// For any public methods that takes a generics as parameter, you should ask yourself
// if you can use `? extends` or `? super`.
// You can note that this is similar to using List as parameter instead of using
// ArrayList. Using a type less precise allow the user code to call with
// generics with different type arguments.
// Apart in case of overriding, never, never use a `? extends`/`? super` as
// a return type. Otherwise, every developers that use your method will have to
// introduce some `? extends`/`? super` in it's own code.
// ### PECS: Produce Extends Consumer Super
// The rule PECS is a mnemonic to know when to use `? extends`/`? super`.
// From the point of view of a generics class Foo<E>
// - if the class acts as a producer of E (calling only methods that return an E)
// you want to use `Foo<? extends E>`
// - if the class acts as a consumer of E (calling only methods that takes an E)
// you want to use `Foo<? super E>`
// - if the class acts as a producer and a consumer, use Foo<E>.
// ## Relation with the variable of type
// Instead of `printAll(List<? extends Object>)`, one can write
<T extends Object> void printAll(List<T> list) {
list.forEach(System.out::println);
}
List<String> list = List.of("hello");
printAll(list);
// But in Java, we prefer to not introduce a type variable if it's not necessary
// You can also notice that it doesn't work with `? super` because <T super Whatever>
// is not a valid syntax.