Java Skill — Effective Java (3rd Edition)
Use this skill to apply Effective Java (3rd edition) best practices while implementing or reviewing Java code.
Table of Contents
2. Creating and Destroying Objects
3. Methods Common to All Objects
4. Classes and Interfaces
5. Generics
6. Enums and Annotations
7. Lambdas and Streams
8. Methods
9. General Programming
10. Exceptions
11. Concurrency
12. Serialization
2. Creating and Destroying Objects
1. Consider static factory methods
Pros:
- Named, unlike constructors
- Not required to return a new object, which allows for instance controlling
- Can return an object of any subtype of their return type
- Class of returned object can vary from call to call as a function of the input parameters
- Class of returned object need not exist when the class containing the method is written, which is the basis of Service Provider Frameworks
Cons:
- No public/protected constructor means no subclassing
- Hard for programmers to find
Common names:
from()- type conversionof()- aggregationvalueOf()- more verboseinstance()orgetInstance()- returns instance, but not the same valuecreate()ornewInstance()- call returns new instancegetType()ornewType()- if in different classtype()- more concise
2. Consider a builder when faced with many constructor parameters
- Telescoping constructors pattern hard to read, doesn't scale well
- JavaBeans pattern allows inconsistency, mandates mutability
- Builder simulates named parameters from languages like Python
- Well-suited to class hierarchies
- Downside: have to create
Builderobjects
3. Enforce the singleton property with a private constructor or an enum type
- A singleton is a class that is instantiated exactly once
- Can make it difficult to test its clients
- Best way to implement is with a single element enum
4. Enforce noninstantiability with a private constructor
- E.g. a collection of methods (Math, Arrays)
- Attempting to enforce noninstantiability by making a class abstract does not work
- Solution: private constructor means no public method to create an instance
5. Prefer dependency injection to hardwiring resources
- Static utility classes and singletons are inappropriate for classes whose behavior is parameterized by an underlying resource (e.g. dictionary for spellcheck program)
- Dependency injection provides testability and flexibility
6. Avoid creating unnecessary objects
- Objects can always be reused if they're immutable
- Try static factory methods or static initializers (for mutable objects)
- Prefer primitives to boxed primitives and watch out for unintentional autoboxing
7. Eliminate obsolete object references
- Memory leaks ~ "unintentional object retentions"
- Solution: null out references once they're obsolete
- But: nulling out references should be the exception, not the norm
- So: do this when you're "managing memory manually", e.g. a custom data structure like a queue where old elements may remain but the garbage collector doesn't know what is in use and not
- Another source: caches & listeners and other callbacks
8. Avoid finalizers and cleaners
- Finalizers are unpredictable, often dangerous, and generally unnecessary
- Cleaners are less dangerous than finalizers, but are still unpredictable and generally unnecessary
- Never do anything time-critical in a finalizer or cleaner
- Never depend on a finalizer to update critical persistent state
- There is a severe performance penalty for using finalizers
- Instead, provide an explicit termination method (e.g.
file.close()) - Explicit termination methods are typically used in combination with the try-finally construct to ensure termination
- Two valid uses of finalizers and cleaners:
- Can be used as a fail-safe if explicit termination method is forgotten by programmer
- Finalizer should log warning if it finds that the resource has not been terminated
- Objects with native peers
- Remember
super.finalize() - Finalizer Guardian with public nonfinal class
- In sum, don't use either of these except as a safety net or to terminate noncritical native resources. Even then, beware indeterminacy and performance hits
9. Always use try-with-resources in preference to try-finally when working with resources that must be closed
- Code shorter and cleaner, and better exceptions provided to programmer
3. Methods Common to All Objects
10. Obey the general contract when overriding equals
- Don't override if:
- each instance is inherently unique
- no need for logical equality test
- superclass overrode equals and still applies
- certain that
equalswill never be invoked
- Once you've violated the
equalscontract, you don't how other objects will behave when confronted with yours - There is no way to extend an instantiable class and add a value component while preserving the
equalscontract - Workaround: favor composition over inheritance (e.g. give
ColorPointsa privatePointfield and a public view method:asPoint()) - But: can add a value component to a subclass of an abstract class without violating
equalscontract - Do NOT write an
equalsmethod that relies on unreliable resources (e.g. network access) - Recipe for a high-quality equals method:
- Use
==operator to check if argument is reference to this object - Use
instanceofto check if argument has correct type - Cast argument to correct type (since it's an
Objectto start) - For each "significant" field in the class, check if the field in the argument matches the corresponding one in the object. Compare fields most likely to differ first, or the ones that are less expensive to compare
- When done writing, check 1) symmetry, 2) transitive, 3) consistent
- Use
- Always override
hashcodewhen you overrideequals. Don't be too clever - Make sure the parameter is the
Objecttype
11. Always override hashcode when you override equals
- Equal objects must have equal hashcodes
- Do not be tempted to exclude significant fields from the
hashcodecomputation to improve performance - Don't provide a detailed specification of the value returned by
hashcode, so clients can't depend on it and you can change it
12. Always override toString
- Makes your class more pleasant to use and makes systems using the class easier to debug
- When practical,
toStringshould return all the interesting info contained in the object - Whether or not you decide to specify a format (and corresponding static factory for converting back) you should clearly document your intentions
- Provide programmatic access to the info contained in the value returned by
toString(e.g. accessors)
13. Override clone judiciously
Cloneableinterface means protectedclonemethod onObjectreturns field-by-field copy of the object- A class implementing
Cloneableis expected to provide a properly functioning, public clone method - By convention, object should be obtained by calling
super.clone(), not constructor - Immutable classes should never provide a
clonemethod (wasteful copying) - Must ensure that the new object does no harm to the original object and properly establishes invariants on the clone
- The
Cloneablearchitecture is incompatible with normal use of final fields referring to mutable objects - Try a
deepCopymethod for objects with complex mutable state - A clone method must never invoke an overridable method on the clone under construction
- Public
clonemethods should omit thethrowsclause - A better approach to object copying is to provide a "copy constructor" or "copy factory" because 1) they don't conflict with proper use of final fields and 2) don't throw unnecessary checked exceptions, etc
- New interfaces should not extend
Cloneable - Arrays are better with
clone, everything else is better with copy constructors or factories
14. Consider implementing Comparable
- Should generally agree with equals
- Use of
<and>incompareTomethods is verbose, error-prone, and not recommended - Start with the most significant fields
- Do not use difference-based comparators
4. Classes and Interfaces
15. Minimize the accessibility of classes and members
- Cleanly separate API from implementation (encapsulation)
- Make each class or member as inaccessible as possible
- Accessibility levels:
private- accessible only from top-level class where it's declaredpackage-private: accessible from any class in the package where it's declaredprotected: accessible from subclasses of the class and from any class in the package where it's declaredpublic: accessible from anywhere
- Instance fields of public classes should rarely be public; classes with public mutable fields are not generally thread-safe
- It is wrong for a class to have a
public static finalarray field or an accessor that returns such a field
16. In public classes, use accessor methods, not public fields
- If a class is accessible outside its package, provide accessor methods
- If a class is package-private or is a private nested class, there is nothing inherently wrong with exposing its data fields
17. Minimize mutability
- 5 rules to follow:
- Don't provide mutators
- Ensure the class can't be extended (make the class final)
- Make all fields final
- Make all fields private
- Ensure exclusive access to any mutable components
- Immutable objects are simple
- Immutable objects are inherently thread safe and require no synchronization
- Immutable objects can be shared freely: they never require defensive copies
- You should never provide a
clonemethod or a copy constructor for an immutable class - Not only can you share immutable objects, but they can share their internals
- Immutable objects make great building blocks for other objects (especially good for map keys or sets)
- Immutable objects provide failure atomicity for free
- The major disadvantage of immutable classes is that they require a separate object for each distinct value (which can be costly, especially if objects are large)
- Classes should be immutable unless there's a very good reason to make them mutable
- If a class cannot be made immutable, limit its mutability as much as possible
- Constructors should create fully initialized objects with all invariants established
18. Favor composition over inheritance
- Inheritance is powerful for code reuse, but used inappropriately can lead to fragile code
- Unlike method invocation, inheritance violates encapsulation
- Superclass implementation details can change, breaking subclass
- Superclass can acquire new methods in subsequent releases that subclasses don't know about
- Solution: give your class a private field that references an instance of the existing class instead of extending it (
composition)
- The resulting class will be rock-solid, with no dependencies on the implementation details of the existing class
- 'Forward' results of calling methods on the existing (private member) class
- Composition also known as the Decorator pattern
- Disadvantage of wrapper classes:
- Not suited for use in callback frameworks; callback elude wrapper (SELF problem)
- Tedious to write forwarding methods
- If you use inheritance where composition is preferable, you needlessly expose implementation details
19. Design and document for inheritance or else prohibit it
- The class must document its self-use of overridable methods
- A class may have to provide hooks into its internal workings in the form of judiciously chosen protected methods
- The only way to test a class designed for inheritance is to write subclasses (~3 sufficient)
- You must test your class by writing subclasses before you release it
- Constructors must not invoke overridable methods (leads to program failures)
- Neither
clonenorreadObjectmay invoke an overridable method directly or indirectly - The best solution is to prohibit subclassing in classes that are not designed and documented to be safely subclassed
20. Prefer interfaces to abstract classes
- Existing classes can easily be retrofitted to implement a new interface
- Interfaces are ideal for defining mixins
- mixin: type that a class can implement in addition to its "primary type" to declare that it provides some optional
behavior (e.g.
Comparable)
- mixin: type that a class can implement in addition to its "primary type" to declare that it provides some optional
behavior (e.g.
- Interfaces allow for the construction of nonhierarchical type frameworks
- Interfaces enable safe, powerful functionality enhancements via the wrapper class idiom
- Can provide a "skeletal implementation class" to go with an interface
- The interface defines the type, and the skeletal implementation class implements the remaining non-private interface methods atop the primitive interface methods (Template Method pattern)
- Called
AbstractInterfaceNameby convention
- Good documentation is absolutely essential in a skeletal implementation
21. Design interfaces for posterity
- Adding new methods to existing interfaces is fraught with risk
- It is not always possible to write a default method that maintains all invariants of every conceivable implementation (e.g. thread safety)
- In the presence of default methods, existing implementations of an interface may compile without errors or warnings but fail at runtime
- It is still of the utmost importance to design interfaces with great care; don't count on fixing flaws post-release
22. Use interfaces only to define types
- The constant interface (anti)pattern is a poor use of interfaces
- Misc: can use underscore in large numbers to make them more readable
23. Prefer class hierarchies to tagged classes
- Tagged classes are cluttered with boilerplate, readability is harmed
- Tagged classes have multiple implementations jumbled together into a single class
- Tagged classes are verbose, error-prone, and inefficient
- A tagged class is just a pallid imitation of a class hierarchy
24. Favor static member classes over nonstatic
- Static member class: an ordinary class that happens to be declared inside another class
- Common use: public helper class (e.g.
Operationenum with calculator class) - Common use of non-static version: define an Adapter that allows an instance of the outer class to be viewed as an instance of some unrelated class
- If you declare a member class that does not require access to an enclosing instance, always make it static
- Anonymous class: has no name, not a member of its enclosing class.
- They're permitted at any point an expression is legal
- Can't instantiate them except at point they're declared
- Can't use
instanceof - Can't implement multiple interfaces
- Must be kept short
25. Limit source files to a single top-level class
- Multiple top-level classes in same file means it's possible to provide multiple definitions
- Can change behavior based on the order files are passed to the compiler (!)
- Never put multiple top-level classes or interfaces in a single source file
5. Generics
26. Don't use raw types
Each generic type defines a set of parameterized types, which consist of the class or interface name followed by an angle-bracketed list of actual type parameters (
List<E>->List<String>)Raw types (
List) behave as if all generic type info were erased from the type declarationIf you use raw types, you lose all the safety and expressiveness benefits of generics
You lose type safety if you use a raw type like
List, but not if you use a parameterized type listList<Object>Instead of raw types, use unbounded wildcard types if you want to use a generic type but you don't know or care what the actual type parameter is; so for a
Set<E>, useSet<?>You can put any element (other than
null) into aCollection<?>; you also can't assume anything about the type of objects you get out. If you care about type, try generic methods or bounded wildcard typesYou must use raw types in class literals
Also illegal to use
instanceofoperator on parameterized types since generic type info is erased as runtimeThis is the preferred way to use the instanceof operator with generic types:
if (o instanceof Set) { Set<?> s = (Set<?> o); }Generic Terms:
| Term | Example | | ------| ------- | | Parameterized Type |
List<String>| | Actual Type Parameter |String| | Generic Type |List<E>| | Format Type Parameter |E| | Unbounded Wildcard Type |List<?>| | Raw Type |List| | Bounded Type Parameter |<E extends Number>| | Recursive Type Bound |<T extends Comparable<T>>| | Bounded Wildcard Type |List<? extends Number>| | Generic Method |static <E> List<E> asList(E[] a)| | Type Token |String.class|
27. Eliminate unchecked warnings
- Eliminate every unchecked warning that you can
- If you can't eliminate a warning, but you can prove that the code that provoked the warning is typesafe, then (and
only then) suppress the warning with a
@SuppressWarnings("Unchecked")annotation - Always use the
@SuppressWarningsannotation on the smallest scope possible - Every time you use a
@SuppressWarnings("Unchecked")annotation, add a comment saying why it is safe to do so
28. Prefer Lists to arrays
- Arrays are "deficient" since some type checks will fail at runtime instead of compile time
- Arrays are covariant whereas generics are invariant
- Arrays are reified, meaning they enforce their type at runtime, whereas generics use type erasure
- Generics and arrays don't mix well:
new List<E>[],new List<String>[],new E[]are all illegal - Generic arrays would not be typesafe
E,List<E>,List<String>are known as non-reifiable types meaning their runtime representations contain less info than their compile-time representations- Try
List<E>instead ofE[]
29. Favor generic types
- How to generify a class:
- Add 1+ type parameters to its declaration (
public class Stack<E>) - Replace Object with
E - Fix warnings and errors (e.g. with arrays, but beware of heap pollution)
- Add 1+ type parameters to its declaration (
- Generifying a class does not break it for existing clients
30. Favor generic methods
- Generic methods need a type parameter declared in angle brackets before the return type
- Generic singleton factory: a static factory method that deals out a single immutable object for each requested type parameterization
- Common use of recursive type bounds is in connection with the
Comparableinterface:public interface Comparable<T> { int compareTo(T o); }- Used to ensure mutual compatibility, i.e. each element in a collection can be compared to every other
public static <E extends Comparable<E>> E max(Collection<E> c);- Type reads as "any type E that can be compared to itself"
- Can generify methods without breaking existing clients
31. Use bounded wildcards to increase API flexibility
<? extends E>for subtypes<? super E>for supertypes- For maximum flexibility, use wildcard types on input parameters that represent producers or consumers
- Rule - PECS: Producer-extends, Consumer-super
- Do not use bounded wildcard types as return types!
- If the user of a class has to think about wildcard types, there is probably something wrong with its API
- Use
Comparable<? super T>in preference toComparable<T> - Use
Comparator<? super T>in preference toComparator<T> - If a type parameter appears only once in a method declaration, replace it with a wildcard
- Remember that all comparables and comparators are consumers
32. Combine generics and varargs judiciously
- Varargs are a "leaky abstraction"; array is visible
- Heap pollution occurs when a variable of a parameterized type refers to an object that is not of that type
- It is unsafe to store a value in a generic varargs array parameter
- The
@SafeVarargsannotation constitutes a promise by the author of a method that it's typesafe - Safe means the method doesn't store anything into the varargs array (overwrite) and doesn't allow an array reference to escape
- It is unsafe to give another method access to a generic varargs parameter array (with two exceptions)
- Use
@SafeVarargson every method with a varargs parameter of generic or parameterized type - Could also replace varargs parameter with
Listparameter
33. Consider typesafe heterogeneous containers
- E.g. database rows have arbitrary number of columns
- Parameterize the key instead of the container, then present key to access container
- When a class literal is passed among methods to communicate both compile-time and runtime type info, it's called a type token.
- Recall "Favorites" example with a
String, anint, and aclass. - Also possible to use a bounded type token using a bounded type parameter or bounded wildcard
6. Enums and Annotations
34. Use enums instead of int constants
intconstants have no type safety, little expressiveness- Brittle because if the actual values change, must be re-compiled by clients
- Enums are classes that export one instance for each enum constant via a public final static field
- To associate data with enum constants, declare instance fields and write a constructor that takes the data and stores it in the fields
- To associate different behaviors with each enum constant, declare an abstract method in the enum type, and override it with a concrete method for each constant in a "constant-specific class body".
- Use the "Strategy enum" pattern to force each new enum constant that's added to provide an implementation for the strategy (e.g. Overtime pay)
- Switches on enums are good for augmenting enum types with constant-specific behavior
- Use enums anytime you need a set of constants whose members are known at compile-time
- It is not necessary that the set of all constants in an enum type stay fixed for all time
35. Use instance fields instead of ordinals
- Never derive a value associated with an enum from its ordinal; store it in an instance field instead
36. Use EnumSet instead of bit fields
- Just because an enumerated type will be used in sets, there is no reason to represent it with bit fields
37. Use EnumMap instead of ordinal indexing
- Most serious problem with ordinal indexing: your responsibility to use correct
intvalue; ints do not provide the type safety of enums - Can use 3-parameter version of
Collectors.groupingByto specifyEnumMapimplementation if desired - It is rarely appropriate to use ordinals to index into arrays; use
EnumMapinstead
38. Emulate extensible enums with interfaces
- Would like to have extensible enums for opcodes
- Emulate this by defining an interface for the opcode type and an enum that is the standard implementation of the interface
- While you cannot write an extensible enum type, you can emulate it by writing an interface to accompany a basic enum type that implements the interface
39. Prefer annotations to naming patterns
- Naming patterns:
- Typographical errors lead to silent failures
- No way to ensure they're used on appropriate program elements
- No good way to associate parameter values with program elements
- Annotations solve these problems (
@AnnotationName) - There is simply no reason to use naming patterns when you can add annotations instead
- All programmers should use the predefined annotation types that Java provides
40. Consistently use the @Override annotation
- Use the
@Overrideannotation on every method declaration that you believe to override a superclass declaration - Not required to annotate methods that you believe to override abstract method declarations in concrete classes
41. Use marker interfaces to define types
- Marker interface: no method declarations, merely designates or "marks" a class that implements the interface as having
some property (e.g.
Serializable) - Marker interfaces define a type that is implemented by instances of the marked class; marker annotations do not
- Marker interfaces can be targeted more precisely
- The
Setinterface is arguable just a restricted marker interface - The chief advantage of marker annotations over marker interfaces is that they're part of the larger annotations facility
- How to decide between the two:
- If it's not a class or interface, choose the annotation
- If class or interface: "Might I want to write 1+ methods that accept objects only having this marking?"
- If so, use a marker interface
- If you find yourself writing a marker annotation type whose target is ElementType TYPE, take the time to figure out whether it really should be an annotation type or marker interface
7. Lambdas and Streams
42. Prefer lambdas to anonymous classes
- Interfaces with a single abstract method are now known as functional interfaces: the language now allows you to create instances using lambda expressions
- The compiler uses type inference to eliminate boilerplate
- Omit the types of all lambda parameters unless their presence makes your program clearer
- Unlike methods and classes, lambdas lack names and documentation; if a computation isn't self-explanatory, or exceeds a few lines, don't put it in a lambda
- For lambdas, one line is ideal, and 3 lines is a reasonable max
- Constant-specific class bodies:
- Use if enum type has constant-specific behavior that is 1) difficult to understand, 2) can't be implemented in a few lines, or 3) requires access to instance fields or methods
- Uses for anonymous classes:
- Create instance of abstract class
- Create instances of interfaces with multiple abstract methods
- Need access to function object from within its body
- You should rarely, if ever, serialize a lambda. Instead, use instance of private static nested class
- Don't use anonymous classes for function objects unless you have to create instances of types that aren't functional interfaces
43. Prefer method references to lambdas
- Multiset with lambda vs method reference:
- lambda:
map.merge(key, 1, (count, incr) -> count + incr); - method reference:
map.merge(key, 1, Integer::sum);
- lambda:
- Method reference reduces visual clutter
- Sometimes lambda parameters are useful documentation
- Consider them especially with large class names or if method lies within same class as lambda
- Method references vs lambdas:
| Method Reference Type | Example | Lambda Equivalent |
|---|---|---|
| Static | Integer::parseInt |
str -> Integer.parseInt(str) |
| Bound | Instant.now()::isAfter |
Instant then = Instant.now(); t -> then.isAfter(t); |
| Unbound | String::toLowerCase |
str -> str.toLowerCase() |
| Class constructor | TreeMap<K,V>::new |
() -> new TreeMap<K,V> |
| Array constructor | int[]::new |
len -> new int[len] |
- Where method references are shorter and clearer, use them; where they aren't, stick with lambdas
44. Favor the use of standard functional interfaces
- Now that Java has lambdas, you'll be writing more constructors and methods that take function objects as parameters
- If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface
- Basic functional interfaces:
Operator: function whose result and argument types are the same (UnaryOperatorandBinaryOperator)Predicate: function that takes an argument and returns a booleanFunction: argument and return types differSupplier: function that takes no arguments and returns (supplies) a valueConsumer: function that takes an argument but returns nothing
- 6 basic functional interfaces:
| Interface | Function Signature | Example |
|---|---|---|
UnaryOperator<T> |
T apply(T t) |
String::toLowerCase |
BinaryOperator<T> |
T apply(T t1, T t2) |
BigInteger::add |
Predicate<T> |
boolean test(T t) |
Collection::isEmpty |
Function<T, R> |
R apply(T t) |
Arrays::asList |
Supplier<T> |
T get() |
Instant::now |
Consumer<T> |
void accept(T t) |
System.out::println |
- Don't be tempted to use basic functional interfaces with boxes primitives instead of primitive functional interfaces
- Seriously consider writing a purpose-built functional interface if you need one that shares one of the following with
Comparator:- It will be commonly used and could benefit from a descriptive name
- It has a strong contract associated with it
- It would benefit from custom default methods
- Always annotate your functional interfaces with the
@FunctionalInterfaceannotation- Tells readers that the interface was designed to enable lambdas
- Keeps you honest because interface won't compile unless it has exactly one abstract method
- Prevents maintainers from accidentally adding abstract methods
45. Use streams judiciously
Stream: a finite or infinite sequence of elements- Stream pipeline: multistage computation on these elements
- Source stream -> 1+ intermediate operations -> 1 terminal operation
- Stream pipelines are evaluated lazily
- Default is to run sequentially
- Overusing streams makes programs hard to read and maintain
- In the absence of explicit types, careful naming of lambda parameters is essential to the readability of stream pipelines
- Using helper methods is even more important for readability in stream pipelines than in iterative code
- Refrain from using streams to process
charvalues - Refactor existing code to use streams and use them in new code only where it makes sense to do so
- It's hard to access corresponding elements from multiple stages of a pipeline simultaneously with streams; when applicable, try inverting the mapping when you need access to the earlier-stage value
- Name streams the plural noun describing the elements of the stream
- If you're not sure whether a task is better served by streams or iteration, try both and see which works better
46. Prefer side-effect-free functions in streams
- Streams paradigm: want result of each stage as close as possible to pure function of the result of the previous
stage
- Pure functions' results only depend on input, no other state
- A
foreachoperation that does anything more than present the result of the computation performed by the stream is a "bad smell" - The foreach operation should be used only to report the result of a stream computation, not to perform the computation
- Collector: an opaque object that encapsulates a reduction strategy (combining elements of the stream into a single object)
- It is customary and wise to statically import all members of
Collectorsbecause it makes stream pipelines more readable - groupingBy: returns collectors to produce maps that group elements into categories based on a classifier function
- downstream collector: produces a value from a stream containing all the elements in a category
- There is never a reason to say
collect(counting()) - minBy/maxBy: take a comparator and return the minimum or maximum element in the stream (determined by
Comparator) -joining: joins streams of character sequences (e.g. strings) - most important ones:
toList,toSet,toMap,groupingBy,joining
47. Prefer Collection to Stream as a return type
- Programmers cannot use for-each loops with streams because
Streamdoes not extendIterable - Can write an adapter to go from stream to iterable, and vice versa
- Collections provide both iteration and stream access. So,
Collectionor an appropriate subtype is generally the best return type for a public, sequence-returning method - Do not store a large sequence in memory just to return it as a collection
48. Use caution when making streams parallel
- Parallelizing a pipeline is unlikely to increase its performance if the source is from
Stream.iterateor the intermediate operationlimitis used - Do not parallelize stream pipelines indiscriminately
- Performance gains from parallelism are best on streams over
ArrayList,HashMap,HashSet, andConcurrentHashMapinstances; arrays;intranges; andlongranges- This is because all of these can be easily split into ranges by a
spliterator - All provide at least good locality of reference
- This is because all of these can be easily split into ranges by a
- Parallelizing a pipeline will do little if a lot of work is done in the terminator and that work is inherently
sequential
- The best candidates for parallelism are reductions and short-circuiting operations
- Not only can parallelizing a stream lead to poor performance, including liveness failures; it can lead to incorrect results and unpredictable behavior (safety failures)
- Under the right circumstances, it is possible to achieve near-linear speedup in the number of processor cores simply
by adding a
parallelcall to a stream pipeline
8. Methods
49. Check parameters for validity
- A method can fail quickly and cleanly with an appropriate exception if checked right away
- Failure to do so can result in a violation of failure atomicity
- The
Objects.requireNonNullmethod is flexible and convenient, so there's no reason to performnullchecks manually anymore - It's important to check validity of parameters not used by a method, but stored for later use (including constructors)
- Indiscriminate reliance on implicit validity checks can result in the loss of failure atomicity
50. Make defensive copies when needed
- Even in a safe language, you must program defensively, with the assumption that clients of your class will do their best to destroy its invariants
Dateis obsolete and should no longer be used in new code- It is essential to make defensive copies of the mutable parameters to the constructor
- Defensive copies are made before checking the validity of the parameters, and the validity check is performed on the
copies rather than the originals
- Protects the class against changes to the parameters from another thread, known as TOCTOU (Time Of Check - Time Of Use) attacks
- Do not use the
clonemethod to make a defensive copy of a parameter whose type is subclassable by untrusted parties - Return defensive copies of mutable internal fields
- Nonzero length arrays are always mutable; thus, make a defensive copy or return an immutable view
- Real lesson: use immutable objects as components when possible to avoid worrying about defensive copying
- If the cost of the copy would be prohibitive and the class trusts its clients not to modify the components inappropriately, then the defensive copy may be replaced by documentation outlining the client's responsibility not to modify the affected components
51. Design method signatures carefully
- Choose method names carefully
- Don't go overboard in providing convenience methods; when in doubt, leave it out
- Avoid long parameter lists: aim for 4 or fewer
- Long sequences of identically typed parameters are especially harmful
- Will compile and run with swapped parameters
- How to shorten:
- Break method into multiple methods
- Create helper classes to hold groups of parameters
- Adapt the builder pattern from object construction to method invocation
- For parameter types, favor interfaces over classes
- Prefer two-element enum types to boolean parameters, unless the meaning of the boolean is clear from the method name
52. Use overloading judiciously
- Recall that the choice of which overloading (method) to invoke is made at compile time
- Selection among overloaded methods is static, while selection among overridden methods is dynamic
- Avoid confusing uses of overloading
- A safe, conservative policy is never to export two overloading with the same number of parameters
- You can always give methods different names instead of overloading them
- Do NOT overload methods to take different functional interfaces in the same argument position
53. Use varargs judiciously
- Varargs methods accept 0+ parameters of the same type (
int... args) - To accept 1+, use one guaranteed parameter and one varargs parameter
- Exercise care when using varargs in performance-critical situations
54. Return empty collections or arrays, not nulls
- There is no reason to special-case the situation where nothing is returned
- Returning nulls is error-prone because the programmer writing the client may forget to write the special case code to
handle a
nullreturn value - Do not preallocate an empty array in hopes of improving performance
- Never return
nullin place of an empty array or collection
55. Return Optionals judiciously
- The
Optional<T>class represents an immutable container that can hold either a single non-nullTreference or nothing at all- If the
Optional<T>contains nothing, we call it empty - If the
Optional<T>contains a value of type T, we call it present
- If the
- A method that conceptually returns a
Tbut may be unable to do so under certain circumstances can instead be declared to return anOptional<T> - Never return a
nullvalue from anOptional-returning method: it defeats the entire purpose of the facility Optionalsa
…(truncated)