Effective Java Review (90 Part Series)
1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
… 86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances
Today we take on a bit of a complicated topic but one that, when done correctly, can really make your code much more flexible in its usage. Previously we have talked about how parameterized types are invariant. This means that for two types, TypeA
and TypeB
, Collection<TypeA>
can not be either a subtype or super type of Collection<TypeB>
. Making this more concrete, List<String>
is not a subtype of List<Object>.
This is because any object can go into List<Object>
but not any object can go into List<String>
. While this may follow logically, sometimes we want/need more flexibility and that’s what the chapter we are reviewing today covers.
Let’s consider a previous example we looked at.
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
}
Enter fullscreen mode Exit fullscreen mode
Above is the currently exposed API. Let’s say we want to add a new method to add many items to the stack at once. We may consider writing it as such:
public void pushAll(Iterable<E> newItems) {
for (E e : newItems) {
push(e);
}
}
Enter fullscreen mode Exit fullscreen mode
This will compile and work fine but we do lose some flexibility that we may want. Let’s consider an example where this may lead to frustration:
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ...;
numberStack.pushAll(integers);
Enter fullscreen mode Exit fullscreen mode
While the above feels like it should work (Integer
being a subtype of Number
) it actually won’t compile because of the invariants. This is where our bounded wildcard types come in to save the day. If we simply change the function signature to the following the above code will work.
public void pushAll(Iterable<? extends E> newItems) {
for (E e : newItems) {
push(e);
}
}
Enter fullscreen mode Exit fullscreen mode
So what does the above signature, ? extends E
, mean? It basically means it can take any subtype of E
(as well as E
itself which can feel a little strange with the extends
keyword). This indeed fits exactly into what we were after.
Now let’s consider a similar but different case. Let’s create the cousin to the pushAll
function and create a popAll
function. How this one will work is we will give the function a collection and it will pop
all the contents of the Stack
into it. Personally I’m not a huge fan of using parameters as output from a function but it serves to illustrate this point well. So our initial implementation may be something like:
public void popAll(Collection<E> destination) {
while(!isEmpty()) {
destination.add(pop());
}
}
Enter fullscreen mode Exit fullscreen mode
Once again this will compile with no issues and is usable but isn’t as usable as it could be. Also again we get in trouble with a very similar example that we ran into with the pushAll
function.
Collection<Number> numberCollection = ...;
Stack<Integer> integerStack = ...;
integerStack.popAll(numberCollection);
Enter fullscreen mode Exit fullscreen mode
This feels like it should work just fine but it doesn’t because again we get bit by the invariants. The difference with this one is rather than looking for a subtype of E we are wanting a collection passed in that is type of E or a supertype of E. Java provides a way to do this like the following:
public void popAll(Collection<? super E> destination) {
while(!isEmpty()) {
destination.add(pop());
}
}
Enter fullscreen mode Exit fullscreen mode
With this new signature the client code with compile and execute fine as well as the Stack code.
The rule of thumb that we are invited to follow is as follows. If an input parameter is something that we will be reading values from or writing to we should consider the use of wildcard types.
In the above two examples we used extends
in one and super
in another. You may ask, when should I use which one. This is indeed a great question. The mnemonic that the book suggests is PECS - producer-extends, consumer-super
. Honestly this mnemonic doesn’t really work for me as the producer/consumer
terminology is a bit weird for me in this case. The way that I think of it is GEPS - get-extends, put-super
. As in, if I’m getting values from it, I should use extends
and if I’m putting values into it I should use super
. Use whatever mnemonic works for you or come up with your own! So let’s apply this to our examples above. In pushAll
we were getting values out of the provided parameter to put somewhere else, so we used extends
. In popAll
we were putting values into the provided parameter, therefore super
. This can take some practice to get the hang of thinking about. Let’s go through some examples from previous examples in our generic section of the book.
First up, the Chooser
function originally written as public Chooser(Collection<T> choices);
. This takes in a selection of choices to be chosen from. Stop reading and thinking about which keyword should we use? Did you say extends
? Great! Since we will be getting values from the choices
collection (or in other words, if it’s a producer) we use the extends
keyword.
Next, public static <E> Set<E> union(Set<E> s1, Set<E> s2);
which takes two sets in and provides a set that is the union of both the sets together. What keywords would we use for our parameters here? Did you say extends
again? Perfect! Again we are grabbing values out of the provided Sets and therefore we want to use extends
so the new function would look like public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);
But wait, what do we do with the return type, it still has no wildcards, is that OK? Indeed it is, in fact, it’s preferred to be that way. Return types shouldn’t use wildcards as it forces the use of wildcards on the receiver of those values. As a rule of thumb, if the user of your function needs to think about the wildcards you are using, you are likely doing it wrong. With the above changes we can now write code like:
Set<Integer> integerSet = Set.of(1,3,5);
Set<Double> doubleSet = Set.of(2.0,4.0,6.0);
Set<Number> numberSet = union(integerSet, doubleSet);
Enter fullscreen mode Exit fullscreen mode
The above code is great and quite clean in that the compiler is capable of inferring the type that we want. While there were great steps in improving the type inference of Java in Java 8 and beyond we still do run into cases where the compiler is still unable to infer the types for us. In these cases we will get fairly hairy error messages but, thankfully, there is a way to help the compiler along via an explicit type argument. Let’s see what our above union
call would need to look like prior to Java 8.
Set<Number> numberSet = Union.<Number>union(integerSet, doubleSet);
Enter fullscreen mode Exit fullscreen mode
Definitely not the prettiest code I have ever seen but at least it’s possible and is something you can put in the back of your mind if you ever do run into this type of issue.
Ok, let’s return from that detour to our practice game of Super or Extends
. Let’s consider the max
function from the previous chapter. public static <T extends Comparable<T>> T max(List<T> list)
. It’s a workout even to type that thing but let’s think about how we would write this when in search of greater flexibility. Consider each type parameter individually. What we end up with is public static <T extends Comparable<? super T>> T max(List<? extends T> list);
That has to be up there with one of the more complicated type signatures I have ever written. Let’s consider the two parts. With our Comparable
part we are providing a value to something there so in that area we are going to want to use super
whereas in the list
parameter we are grabbing items from that List
and thus we want to use extends
. Rule of thumb is that Comparables
always are consumers and thus will always use super
. This added complexity doesn’t come without it’s benefits though. We are now able to find the max of types such as ScheduledFuture
which doesn’t implement Comparable<ScheduledFuture>
but instead implements Comparable<Delayed>
the super type of ScheduledFuture
.
There is one last thing to consider. When to use wildcards, and when to use type parameters. Consider the following two function signatures that implement a swap
function:
public static <E> void swap(List<E> list, int from, int to);
public static void swap(List<?> list, int from, int to);
Enter fullscreen mode Exit fullscreen mode
So which one should be preferred? Effective Java says a rule to follow is, if a type parameter only shows up in a method signature once that we should go with the wildcard option (the second option). This also does seem like the simpler type signature. However, before we jump to that solution let’s consider how the implementation would work.
public static void swap(List<?> list, int from, int to) {
list.set(from, list.set(to, list.get(from));
}
Enter fullscreen mode Exit fullscreen mode
This actually won’t compile because the only thing allowed to be put into a wildcard collection is null
. There is a way around this though, with a helper function that can capture the type. So if we change it to the following it will work.
public static void swap(List<?> list, int from, int to) {
swapHelper(list, from, to);
}
private static <E> void swapHelper(List<E> list, int from, int to){
list.set(from, list.set(to, list.get(from));
}
Enter fullscreen mode Exit fullscreen mode
Because the swapHelper
function can capture the type of E
and verify that the operation is safe to do this will work. However, take a look at it again, didn’t we just write the first option? We did indeed, so in this case I would definitely vote in favor of going with the type parameter option rather than the wildcard option here.
This has been a fairly heavy chapter. It will likely take more practice and exposure to these wildcard parameters to really get your mind around them, I know it did for me and I continue to get confused about them at times. As you use these in the wild you will likely find that you use the extends
option a lot more than you use the super
option. This is just the nature of the fact that we usually pass parameters in to read from more than we pass them in to put values into. As we use these capabilities of bounded wildcard parameters in our code we can indeed make our code much more flexible and this is very much something that I think is worth the effort.
Effective Java Review (90 Part Series)
1 Effective Java Tuesday! Let’s Consider Static Factory Methods
2 Effective Java Tuesday! The Builder Pattern!
… 86 more parts…
3 Effective Java Tuesday! Singletons!
4 Effective Java Tuesday! Utility Classes!
5 Effective Java Tuesday! Prefer Dependency Injection!
6 Effective Java Tuesday! Avoid Creating Unnecessary Objects!
7 Effective Java Tuesday! Don’t Leak Object References!
8 Effective Java Tuesday! Avoid Finalizers and Cleaners!
9 Effective Java Tuesday! Prefer try-with-resources
10 Effective Java Tuesday! Obey the `equals` contract
11 Effective Java Tuesday! Obey the `hashCode` contract
12 Effective Java Tuesday! Override `toString`
13 Effective Java Tuesday! Override `clone` judiciously
14 Effective Java Tuesday! Consider Implementing `Comparable`
15 Effective Java Tuesday! Minimize the Accessibility of Classes and Member
16 Effective Java Tuesday! In Public Classes, Use Accessors, Not Public Fields
17 Effective Java Tuesday! Minimize Mutability
18 Effective Java Tuesday! Favor Composition Over Inheritance
19 Effective Java Tuesday! Design and Document Classes for Inheritance or Else Prohibit It.
20 Effective Java Tuesday! Prefer Interfaces to Abstract Classes
21 Effective Java! Design Interfaces for Posterity
22 Effective Java! Use Interfaces Only to Define Types
23 Effective Java! Prefer Class Hierarchies to Tagged Classes
24 Effective Java! Favor Static Members Classes over Non-Static
25 Effective Java! Limit Source Files to a Single Top-Level Class
26 Effective Java! Don’t Use Raw Types
27 Effective Java! Eliminate Unchecked Warnings
28 Effective Java! Prefer Lists to Array
29 Effective Java! Favor Generic Types
30 Effective Java! Favor Generic Methods
31 Effective Java! Use Bounded Wildcards to Increase API Flexibility
32 Effective Java! Combine Generics and Varargs Judiciously
33 Effective Java! Consider Typesafe Heterogenous Containers
34 Effective Java! Use Enums Instead of int Constants
35 Effective Java! Use Instance Fields Instead of Ordinals
36 Effective Java! Use EnumSet Instead of Bit Fields
37 Effective Java! Use EnumMap instead of Ordinal Indexing
38 Effective Java! Emulate Extensible Enums With Interfaces.
39 Effective Java! Prefer Annotations to Naming Patterns
40 Effective Java! Consistently Use the Override Annotation
41 Effective Java! Use Marker Interfaces to Define Types
42 Effective Java! Prefer Lambdas to Anonymous Classes
43 Effective Java! Prefer Method References to Lambdas
44 Effective Java! Favor the Use of Standard Functional Interfaces
45 Effective Java! Use Stream Judiciously
46 Effective Java! Prefer Side-Effect-Free Functions in Streams
47 Effective Java! Prefer Collection To Stream as a Return Type
48 Effective Java! Use Caution When Making Streams Parallel
49 Effective Java! Check Parameters for Validity
50 Effective Java! Make Defensive Copies When Necessary
51 Effective Java! Design Method Signatures Carefully
52 Effective Java! Use Overloading Judiciously
53 Effective Java! Use Varargs Judiciously
54 Effective Java! Return Empty Collections or Arrays, Not Nulls
55 Effective Java! Return Optionals Judiciously
56 Effective Java: Write Doc Comments For All Exposed APIs
57 Effective Java: Minimize The Scope of Local Variables
58 Effective Java: Prefer for-each loops to traditional for loops
59 Effective Java: Know and Use the Libraries
60 Effective Java: Avoid Float and Double If Exact Answers Are Required
61 Effective Java: Prefer Primitive Types to Boxed Types
62 Effective Java: Avoid Strings When Other Types Are More Appropriate
63 Effective Java: Beware the Performance of String Concatenation
64 Effective Java: Refer to Objects By Their Interfaces
65 Effective Java: Prefer Interfaces To Reflection
66 Effective Java: Use Native Methods Judiciously
67 Effective Java: Optimize Judiciously
68 Effective Java: Adhere to Generally Accepted Naming Conventions
69 Effective Java: Use Exceptions for Only Exceptional Circumstances
70 Effective Java: Use Checked Exceptions for Recoverable Conditions
71 Effective Java: Avoid Unnecessary Use of Checked Exceptions
72 Effective Java: Favor The Use of Standard Exceptions
73 Effective Java: Throw Exceptions Appropriate To The Abstraction
74 Effective Java: Document All Exceptions Thrown By Each Method
75 Effective Java: Include Failure-Capture Information in Detail Messages
76 Effective Java: Strive for Failure Atomicity
77 Effective Java: Don’t Ignore Exceptions
78 Effective Java: Synchronize Access to Shared Mutable Data
79 Effective Java: Avoid Excessive Synchronization
80 Effective Java: Prefer Executors, Tasks, and Streams to Threads
81 Effective Java: Prefer Concurrency Utilities Over wait and notify
82 Effective Java: Document Thread Safety
83 Effective Java: Use Lazy Initialization Judiciously
84 Effective Java: Don’t Depend on the Thread Scheduler
85 Effective Java: Prefer Alternatives To Java Serialization
86 Effective Java: Implement Serializable With Great Caution
87 Effective Java: Consider Using a Custom Serialized Form
88 Effective Java: Write readObject Methods Defensively
89 Effective Java: For Instance Control, Prefer Enum types to readResolve
90 Effective Java: Consider Serialization Proxies Instead of Serialized Instances
原文链接:Effective Java! Use Bounded Wildcards to Increase API Flexibility
暂无评论内容