Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

ekis/effective-java-3rd-edition

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
13 Commits
 
 
 
 
 
 

Repository files navigation

Effective Java - 3rd Edition Notes

Chapter Index

  • 02 - Creating and Destroying Objects
  • 03 - Methods Common to All Objects
  • 04 - Classes and Interfaces
  • 05 - Generics
  • 06 - Enums and Annotations
  • 07 - Lambdas and Streams
  • 08 - Methods
  • 09 - General Programming
  • 10 - Exceptions
  • 11 - Concurrency
  • 12 - Serialization

Chapter 02 - Creating and Destroying Objects

Item 1 - Consider static factory methods instead of constructors

  • traditional vs. flexible way of object instantiation
  • example of static factory method:
public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}
Advantages and disadvantages:
  • (PRO) static factories have names, unlike constructors
  • (PRO) static factories are not required to create a new object on each invocation
  • such classes are called instance controlled
    • enable singleton (Item 2) and non-instantiability (Item 3) guarantees
    • allows immutable value class to guarantee no two instances exist
    • forms the basis of Flyweight Pattern
    • Enum types provide this guarantee
  • (PRO) static methods can return an object of any subtype of their return type, unlike constructors
  • this can lead to compact APIs
  • lends itself to interface-based frameworks (Item 20)
  • companion classes mostly obviated in Java 8
    • default methods
    • still some limitations which are dealt with in Java 9+
  • (PRO) static factories allow the class of the returned object to vary from call to call as function of input params
  • example: EnumSet
    • backed by a long - RegularEnumSet
    • backed by a long[] - JumboEnumSet
  • (PRO) static factories do not require the class of returned object to exist when the class containing the method is written
  • form the basis of service provider frameworks
  • example: JDBC
  • variants:
    • dependency-injection frameworks (i.e. Spring, Guice)
    • Bridge Pattern
  • (CON) classes without public/protected constructors cannot be subclassed
  • a blessing in disguise
    • encourages composition over inheritance
  • (CON) hard to find in documentation
  • current JavaDoc limitations
Some common names for static factory methods
  • from()
  • of()
  • valueOf()
  • instance() or getInstance()
  • getType()
  • newType()
  • type()
Conclusion

Avoid the reflex to provide a public constructor and consider static methods/factories.

Item 2 - Consider a builder when faced with many constructor params

  • static factories and constructors share a limitation: they scale poorly with the increase of (optional) params
  • traditional ways of dealing with this:
    • telescoping constructor
      • also scales poorly: hard to write with many params and even harder to read
    • JavaBeans pattern
      • allows inconsistency
      • precludes immutability (Item 17)
  • a better way: Builder pattern
    • typically a static member class (Item 24)
    • the client code is easy to write and, more importantly, easy to read
    • easy to incorporate validity checks
      • check on object fields after copying params from the builder (Item 50)
      • failing check will throw an IllegalArgumentException (Item 72) with exception details (Item 75)
    • well suited to class hierarchies
      • generic builder with recursive type parameters (Item 30) can construct any subclass
      • abstract self() simulates self-type which, combined with covariant return typing, obviates need for casting
    • flexible
  • disadvantages:
    • cost of creating a builder
    • more verbose than a telescoping constructor
  • conclusion:
    • almost always start with a builder in the first place
      • especially so if we have more than a handful params
      • client code much easier to read and write than telescoping constructors
      • builder are much safer than JavaBeans

Item 3 - Enforce the singleton property when a private constructor or enum type

  • singleton is a class that is instantiated exactly once
  • making a class a singleton can make it difficult to test
  • three common ways of implementing it:
    • with public final field
    • with static factory
    • with a single-element enum (preferred)
  • conclusion:
    • if a singleton is indeed warranted, create it as a single-element enum
      • well-defended against reflection
      • solves serialization problems

Item 4 - Enforce non-instantiability with a private constructor

  • occasionally, we want to write a class that is simply a grouping of static methods and static fields
  • such utility classes have acquired a bad reputation due to their abuse in avoiding thinking in terms of objects but they have valid uses:
    • group related methods on primitive values or arrays
      • examples: java.util.Math or java.util.Arrays
    • group static methods, including factories (Item 1)
      • default methods are also available (providing we own the interface)
    • group methods on a final class, since we can't put them in a subclass
  • make such classes non-instantiable
    • provide an explanatory comment
    • throw an AssertionError from constructor instead of empty one, to guard against accidental constructions from within the class
    • as a side-effect, this class is now effectively final
      • the class cannot be subclassed as there are no available constructors
      • still, it is good to document this a make the class itself final

Item 5 - Prefer dependency injection to hard-wiring resources

  • do not use static utility methods or singletons to handle creations of class' resources
    • these yield inflexible and untestable classes
  • favour dependency injection by supplying the required resource parametrically
    • we inject (pass) the dependency (resource) into the class that requires it
    • testable
    • flexible (esp. with Factory Method pattern)
      • Supplier<T> is perfectly suited for representing factories
      • methods that take this interface should typically constrain the factory's type parameter with a bounded wildcard type (Item 31)
        • the client should be able to pass in a factory that requires any subtype of a specified type
  • the manual dependency injection can be automatised with frameworks

Item 6 - Avoid creating unnecessary objects

  • creating unnecessary objects can be avoided by using static factory methods (Item 1)
  • some object creations are much more expensive than others
    • it may be advisable to cache such objects
    • example: regex matching
  • immutable objects can trivially be reused
  • other examples are Adapters (a.k.a. views)
    • adapter is an object delegating to a backing object, providing an alternative interface
    • has not state beyond the backing object thus provides a view of it
    • example: keySet() in Map interface
  • autoboxing can often subtly create unnecessary objects
  • almost never create own object pools
    • JVM gc will almost always outperform such pools
    • exception to this: very expensive objects
      • example: database connection objects
  • counterpoint to this is defencive copying (Item 50)

Item 7 - Eliminate obsolete object references

  • when relying on automatic gc, be wary of leaks
    • example: a resizing-array stack that doesn't null out its references on pop()
  • common sources of leaks
    • classes that manage their own memory
    • caches
      • can be mitigated with WeakHashMap
      • sophisticated caches might need to use java.lang.ref directly
    • listeners and other callbacks
      • APIs that register callbacks but don't deregister them explicitly
      • can be mitigated with WeakHashMap
  • conclusion: it very desirable to learn to anticipate such problems before they occur as they can be very costly to fix

Item 8 - Avoid finalizers and cleaners

  • finalizers and cleaners are used to reclaim non-memory resources
    • example: input/output streams, files, etc.
    • they are not analogues of C++ destructors
  • finalizers are unpredictable, dangerous and generally unnecessary
  • cleaners are less dangerous than finalizers but still slow, unpredicatble and generally unnecessary
  • disadvantages:
    • spec makes no guarantee when they'll be executed
      • their execution is a function of GC algorithm, thus JVM implementation
      • never do anything time-critical in a finalizer or cleaner
      • providing a finalizer may arbitrarily delay reclamation of class' instances
      • cleaners are a bit better but still run under the control of GC so still the same applies
      • any existing methods that claim to trigger finalization are decade-old traps
        • examples: System.runFinalizerOnExit or Runtim.runFinalizerOnExit
    • uncaught exception thrown during finalization is ignored and finalization of that object terminates
      • object is potentially left in corrupted state
      • normally, uncaught exception terminates the executing thread and dumps stacktrace but not if it occurs in finalizer
      • cleaners do not suffer from this problem
    • there is a severe performance GC penalty for using finalizers and cleaners
      • finalizers: ~50x slower reclamation
      • cleaners: ~5x slower reclamation, equal to finalizers if they're used to clean all instance of the class
    • finalizers open up classes to finalizer attacks
      • if an exception is thrown during finalization, the finalizer leaves the class unreclaimed
      • attackers can exploit this and run code that shouldnt've existed
      • to protect non-final classes against it - write a final finalize() that does nothing
  • instead of using finalizers or classes simply use AutoCloseable
    • require clients to invoke close() whenever instance is not required
  • legitimate uses:
    • act as a safety net for closeables
      • think long and hard before doing so
    • reclaim native peer objects
  • conclusion: don't use cleaners, or in releases prior to Java 9, finalizers - except as a safety net or to terminate non-critical native resources. Even then, beware the indeterminacy and performance consequences

Item 9 - Prefer try-with-resources to try-finally

  • Java libraries include many resources that must be closed manually by invoking close()
    • examples: InputStream, OutputStream, java.sql.Connection, etc.
  • closing objects is often overlooked by clients
  • many use finalizers as safety net, with dire performance consequences (Item 8)
  • historically, try-finally was the best way to guarantee a resource would be closed properly, even when facing exception or return
    • while it doesn't look bad for a single resource, it doesn't scale well with the increase of resources required to be closed
      • nested try-finally blocks stick out like a sore thumb
    • it's tricky to get it right, even in JDK
    • nested finally block complicate debugging
  • try-with-resources suffers none of this issues
    • example:
        // try-with-resources on multiple resources - short and sweet
        static void copy(String src, String dst) throws IOException {
        try (InputStream in = new FileInputStream(src);
             OutputStream out = new FileOutputStream(dst)) {
             byte[] buf = new byte[BUFFER_SIZE];
             int n;
             while ((n = in.read(buf)) >= 0)
             out.write(buf, 0, n);
             }
        } catch (IOException e) {
             // do something here
        }
    • shorter, more readable than try-finally
    • provides far better diagnostics
      • no exceptions are suppressed
  • conclusion: always use try-with-resources when working with resources that must be closed

Chapter 03 - Methods Common to All Objects

Item 10 - Obey the general contract when overriding equals()

  • overriding the equals() seems simple but there are many pitfalls and consequences can be dire
  • easiest way to avoid problems is not override the method at all
    • then, we fall back to object identity only - each object is equal only to itself
  • do not override the method if:
    • each instance of the class is inherently unique
      • example: java.util.thread.Thread
    • there is no need for the class to provide 'logical equality' test
      • example: java.util.regex.Pattern
    • superclass is already overriding equals() and superclass' behaviour is appropriate for the subclass
      • example: most root classes from java.util.collection and subclasses of java.util.Map inherit equals() behaviour from their abstract implementations
    • the class is (package-)private and we're certain equals() will never be invoked
      • the risk-averse may throw an exception in the subclass if equals() is called
    • the class is instance controlled (Item 1)
      • example: enum types
  • do override the method if a class has a notion of logical equality that differs from mere object identity
    • this is generally the case for value classes
    • examples: java.util.Integer or java.util.String
  • overriding the equals() implies adhering to its general contract of equivalence relation:
    • (reflexivity) x.equals(x) == true, for x != null
      • hard to violate unintentionally
    • (symmetry) x.equals(y) == y.equals(x), for x, y != null
      • example: compare ordinary and case insensitive strings
    • (transitivity) x.equals(y) == y.equals(z) == x.equals(z), for x, y, z != null
      • examples: subclass adds a new value component; java.util.Date and java.util.Timestamp
      • easier to violate when using inheritance
      • once this property is violated, subsequent fixes are likely to violate other properties
      • fundamental problem of equivalence relations in OO languages => there is no way to extend an instantiable class and add a value component whilst preserving the relation!
      • workaround: favour composition over inheritance
    • (consistency) x.equals(y) == x.equals(y), for x, y != null
      • example: 'java.util.URL'
      • do not write equals() that depend on unreliable resources
    • (non-nullity) x.equals(null) == false, for x != null
      • hard to violate unintenionally, unless an exception is thrown instead of returning false
  • violation of this contract means it is uncertain how other objects will behave when confronted with our object
  • conclusions:
    • rely on IDEs to generate the equals()
    • if manual tuning is really necessary, pay attention to equivalence relation violation (write tests!) and performance

Item 11 - Always override hashCode() when overriding equals()

  • violating this rule will violate the general contract for hashCode()
    • it prevents proper functioning of hash-based collections
  • general hashCode() contract:
    • (consistency) x.hashCode() == x.hashCode(), for x != null
    • (equality) x.equals(y) => (x.hashCode() == y.hashCode()), for x, y != null
  • worst possible hash-code implementation is returning the same number
    • degrades performance horrifically
  • conclusions:
    • it is mandatory to override hashCode() each time equals() is overridden
      • failure to do so precludes correct functioning of the program (fallback on Object)
    • obey the general hashCode() contract
    • rely on the Object.hashCode(...) to compute the hash-code unless performance is very important

Item 12 - Always override toString()

  • makes systems using the class easier to debug
    • disadvantage: once specified, it's for-life
  • clearly document intentions
  • don't override toString() on
    • static utility class (Item 4)
    • enum types (Item 34)
  • rely on IDE to create a good toString() implementation

Item 13 - Override clone() judiciously

  • conclusions:
    • do not use clone()
    • use static factory to copy objects whenever possible

Item 14 - Consider implementing Comparable

  • isn't inherited from Object type
  • located in functional interface Comparable
  • similar to equals() except it permits order comparisons in addition to simple equality comparisons
  • implementing Comparable, class indicates its instances follow natural ordering
  • it also makes it easy to apply various searching, sorting and extreme values computations on such a class
    • example: java.util.String
    • class automatically interoperates with a variety of generic algorithms and collection implementations
    • small effort and code footprint to leverage existing (vast) capabilites
  • general contract of compareTo() is very similar to equals:
    • (reflexivity) (x.compareTo(y) == 0) => (sgn(x.compareTo(z) == sgn(y.compareTo(z)), for x, y, z != null
    • (symmetry) sgn(x.compareTo(y)) == -sgn(y.compareTo(x)), for x, y != null
      • helps imposing total order
    • (transitivity) (x.compareTo(y) && y.compareTo(z)) > 0 => (x.compareTo(z)) > 0, for x, y, z != null
      • helps imposing total order
    • (optional consistency with equals) (x.compareTo(y) == 0) <=> (x.equals(y)), for x, y != null
      • if violated, still will yield valid comparisons but will generally not guarantee obeying the general contract for collections and maps
      • examples: java.util.BigDecimal in HashSet and TreeSet
  • to compare object ref fields, invoke compareTo() recursively
  • do not use relational operators in compareTo()
    • use compare() on boxed primitive types
    • alternately, use comparator fluent construction methods in Comparator interface
  • do not use hashCode arithmetics-based comparisons
    • though somewhat more performant, they are fraught with danger from integer overflow and FP arithmetic artifacts
    • use the same techniques as in the bullet point above
  • generally, same limitations and workarounds as for the equals() apply here
    • exceptions may be thrown, however, without violating the contract
  • conclusions:
    • whenever a value class has sensible ordering, Comparable should be implemented
      • easy sorting, searching and usage in comparison-based collections
    • do not use relational comparison operators to determine the result of comparing two elements
      • rely on static compare() in the boxed primitive types
      • alternately, use comparator construction methods in Comparator interface

Chapter 04 - Classes and Interfaces

Item 15 - Minimise the accesibility of classes and members

  • well-designed components:
    • hide their internal data and other implementation details from other components
    • thus, they cleanly separate their API from its implementation
    • components are then oblivios to each other's workings
    • this is encapsulation (information hiding) -> a fundamental tenet of software design
  • importance of encapsulation:
    • decouples the components comprising a system
    • this allows for development/optimisation/usage/reasoning/modification in isolation
      • speeds up development (parallelisation)
      • reduces maintenance costs (easier reasoning/debugging/replacement)
      • makes for effective performance tuning (isolation -> does not influence correctness of others)
      • increases reuse (decoupled components usually provide use in contexts beyond original design)
      • de-risks development (individual components may prove successful even if the system does not)
  • Java encapsulation facilities:
    • access control mechanism (private, protected, public keywords + default access)
  • encapsulation rule of thumb: make each class/member as inaccessible as possible
    • after carefully designing a class' public API, the reflex should be to make all other members private
    • change design if you find yourself opening up the API too frequently
      • there may be a better decomposition with higher decoupling
    • some fields may leak into the exported API if the class implements Serializable interface
  • huge increase in visibility when going from default to protected
    • becomes a part of exported API (has to be supported forever)
    • should be relatively rare
  • overridden methods cannot have restrictive access level in the subclass than it is in the superclass
    • fields a compile-time error
    • a consequence of Liskov substition principle
    • special rule -> if a class implements an interface, all implemented methods must be public
  • opening up a class to facilitate testing is acceptable (to a degree)
    • the less restrictive access must not be any higher than default access
    • it is fine to make the code "worse" in order to cover it with tests (M. Feathers, "Working Effectively With Legacy Code")
  • instance fields of public classes should rarely be public
    • gives up :
      • the ability to enforce invariants on the field
      • the flexibility to change data structure
    • not thread-safe
    • same applies for static fields, except if they are also final
      • exception: it's always wrong to expose a mutable data structure as public static final (e.g. Arrays, Lists...)
      • for such instances make an accessor which defencively copies the data structure and make the field private
  • Java 9 modules:
    • module is a group of packages, much like a pakcage is group of classes
    • it may explicitly export some of its packages (via export declarations in its module declaration module-info.java)
    • public/protected members of unexported packages in a module are inaccessible outside the module
      • within, they work as before
    • using the module system allows us to share classes among packages within a module without making them visible to the entire world
    • the need for this kind of sharing is relatively rar and can often be eliminated by rearranging classes within a package
    • it is unclear if it will achieve widespread use outside of the JDK itself (where it's strictly enforced)
  • summary:
    • reduce accebility of program elements as much as possible
    • do not expose mutable types as public static final fields

Item 16 - Favour accessors over public fields in public classes

  • occasionaly, degenerate classes are rolled out such as this one:
class Point {
    public double x;
    public double y
}
  • if confined to package-private or private access modifier, they can be very useful in reducing visual clutter and overhead
    • making them more accessible would be dangerous and is not advised
      • there are examples in JDK that violate this rule (java.awt.Point, java.awt.Dimension)

Item 17 - Minimise mutability

  • immutable classes are classes whose instances cannot be modified
    • all of the data in the object is fixed for the lifetime of the object
    • e.g. java.lang.String, the boxed primitive classes, BigInteger and BigDecimal
  • many reasons to use immutable classes -> easier to design, implement and use than mutable classes
  • to make a class immutable, follow these 5 rules:
    • don't provide mutators
    • ensure that the class cannot be extended
    • make all fields final
    • make all fields private
    • ensure exclusive access to any mutable components
  • immutable classes are easier to realise using functional, rather than imperative approach
  • immutable objects are simple
    • has always exactly one state - the state in which it was created
    • they are easier to use reliably
  • immutable object are inherently thread-safe (they require no synchronisation)
    • thus can be shared freely, promoting reuse
    • no defencive copies necessary
    • their internals can be shared freely
  • immutable objects make great building blocks for other objects
    • they also make great map keys or set elements
  • immutable object provide atomicity for free
  • the one disadvantage -> they require a separate object for each distinct value
    • the problem is exacerbated if the object is a part of multistep transformation
    • one way to solve this issue is by providing mutable 'companion classes'
  • no method may produce an externally visible change in the object's state
    • consider using lazy initialisation technique
  • summary:
    • classes should be immutable unless there is a very good reason to make them mutable
    • if a class cannot be made immutable, limit its mutability as much as possible
      • declare every field private final unless there is a good reason to do so otherwise
  • constructors should create fully initialised objects with all their invariants established
    • e.g. CountDownLatch

Item 18 - Favour composition over inheritance

  • this chapter applies to implementation inheritance, not interface inheritance
  • inheritance is a powerful way to achieve code reuse
  • can lead to fragile software if employed inappropriately
  • safe to use:
    • within a package (where sub- and super-class are under control of the same programmers)
    • if a class specifically is designed and documented for inheritance
  • inheritance violates encapsulation
    • subclass depends on implementation details of the superclass
    • both must evolve in tandem
    • e.g. HashSet extension
      • depending on superclass' method to implement your own can lead to fragility - as the superclass evolves, your code may break without any changes
    • related cause of fragility is that their superclass can acquire new methods in subsequent releases
      • e.g. Hashtable and Vector classes had to had their security holes fixed before being retrofitted to participate in Collections framework
  • solution to these issues is the application of composition
    • existing class becomes a component of the new one
    • new class is free to forward calls to the old class where appropriate (forwarding methods)
    • resulting classes are then rock solid:
      • they don't depend on the implementation details of the existing class
    • the forwarder-wrapper pattern is also known as the Decorator pattern
      • this combination of composition and forwarding is also informally defined as delegation
    • disadvantages of wrapper classes are few:
      • not suited for use in callback frameworks (callbacks elude the wrapper - SELF problem)
      • theoretical performance and memory impacts
      • tedious to write forwarding classes
  • inheritance is only appropriate for classes that pass the "is-a" test
    • answer truthfully to question "If B extended A, is B really an A?"
    • a number of obvious violations in JDK
      • Stack extends Vector
      • Properties extends Hashtable
  • does the class contemplated to extends has any flaws in its API?
  • summary:
    • inheritance is powerful but problematic as it violates encapsulation
    • appropriate only when a genuine subtype relationship exists between the sub- and super-class
    • inheritance may lead to fragility
      • use composition and forwarding instead to avoid this fragility
      • wrapper classes are more robust and powerful than subclasses

Item 19 - Design and document for inheritance or else prohibit it

  • what does it mean to design/document for inheritance?
    • the class must document its self-use of overridable methods
      • this is one special case where it's ok to document implementation detail (unfortunate side-effect of inheritance violating encapsulation)
    • the class may have to provide hooks into its internal workings
      • careful choosing of protected methods
      • enables programmers to write efficient subclasses without undue pain
    • the only way to test a class designed for inheritance is to write subclasses
      • test before releasing the class
    • constructors must not invoke overridable methods (directly or indirectly)
      • safe to invoke private/final/static methods, none of which are overridable
    • Cloneable or Serializable interfaces are especially difficult to design for inheritance
      • use neither in such classes
  • designing a class for inheritance requires great effort and places substantial limitations on the class
    • occasionaly, it is clearly the right thing to do (e.g. abstract classes, interfaces or skeletal implementations)
    • the opposite also holds (e.g. immutable classes)
  • best approach is to prohibit subclassing in classes that are not designed and documented for safe subclassing
    • prohibit inheritance on classes implementing an interface that captures its essence
    • for other classes, ensure at least that the class never invokes any of its overridable methods and document this
  • summary:
    • designing a class for inheritance is hard
    • document all self-use patterns
    • export one or more protected methods
    • strongly consider prohibiting inheritance altogether

Item 20 - Prefer interfaces to abstract classes

  • two mechanisms to define a type that permits multiple implementations: interfaces and abstract classes
  • existing classes can easily be retrofitted to implement a new interface
    • they cannot, in general, be retrofitted with a new abstract class without doing collateral damage to the type hierarchy
  • interfaces are ideal for defining mixins
    • a mixin is a type thtat a class implements additionally to its 'primary type'
      • e.g. Comparable
    • abstract classes cannot be used to define mixins
  • interfaces allow for the construction of non-hierarchical type frameworks
    • they can prevent combinatorial explosion produced by abstract classes
  • interfaces enable safe, powerful functionality enhancements
    • wrapper class idiom

About

Effective Java 3rd Edition Notes

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Morty Proxy This is a proxified and sanitized view of the page, visit original site.