Classes and Interfaces - Effective Java
- Item 15: Minimize the accessibility of classes and members
- Item 16: In public classes, use accessor methods, not public fields
- Item 17: Minimize mutability
- Item 18: Favour composition over inheritance
- Item 19: Design and document for inheritance or else prohibit it
- Item 20: Prefer interfaces to abstract classes
- Item 21: Design interfaces for posterity
- Item 22: Use interfaces only to define types
- Item 23: Prefer class hierarchies to tagged classes
- Item 24: Favour static member classes over nonstatic
- Item 25: Limit source files to a single top-level class
Item 15: Minimize the accessibility of classes and members
A well-designed component hides the API and the implementation. It communicates only via the API which is called encapsulation.
Advantages of encapsulation:
- decouples the components
- faster parallel development, as isolated
- easy on maintenance
- enable effective performance tuning by finding inefficient parts
- increases and promotes reuse
- decreases risk in large systems, even if it fails we have independent reusable parts
Java provides access control mechanism for it. One rule of thumb for efficient encapsulation: make each class or member as inaccessible as possible, i.e using the lowest privilege accessor.
For top-level classes (non-nested) there are two modes:
public: part of implementation, has to be present and maintained for the lifetime (backward compatibility)
- package-private : part of API, gives flexibility can be changed or removed
If a top-level class/interface is used by only one client class, it can be a
private static nested class/interface of the client class.
private and package-private members are part of a class’s implementation and do not normally impact its exported API. These fields can, however, “leak” into the exported API if the class implements
For member of
public classes a huge increase in accessibility is when package-private is changed to
protected. It is part of exported API, must be supported forever and shows implementation commitment detail.
Instance fields of
public classes should rarely be
public mutable fields are not generally thread-safe. Even if it is
final we loose the flexibility of modification. Similar arguments hold for
static fields as well except that constants can be exposed via
public static final, but they should be either primitive or reference to immutable objects. Non-zero length field is mutable and should not be
public or returned by an accessor. You can make the array field
private and return an immutable
List or a copy of the array field.
Item 16: In public classes, use accessor methods, not public fields
publicclasses should not expose mutable fields, immutable may be exposed (questionable)
- If a class is package-private or is a
privatenested class, there is nothing inherently wrong with exposing its data fields to make code cleaner
Item 17: Minimize mutability
- easier to design, implement and use
- less prone to error and more secure
Five rules to make a class immutable:
- Don't provide methods that modify an object's state (mutators)
- Ensure class can't be extended (
private/ package-private constructor or
- Make all fields
final(also thread-safe), some helper or non-core fields may be non-final like caching
- Make all fields
public finalfor constants of type primitive or immutable)
- Ensure exclusive access to any mutable components
Functional approach -> return result after applying the function to the operand, without modifying. Procedural approach -> like functional approach but modifies the operand.
Immutable objects advantages:
- inherently thread-safe
- no synchronization required
- shared freely
- no need of defensive copies
- no need of
clone()or copy constructor (
Stringhas copy constructor from early days, can't remove backward compatibility)
- great building blocks for other objects as robust
Immutable objects disadvantages:
- a separate object for each state / distinct value
In multiple-step operations costs add up. To minimize this we can provide primitive for the common operations. Nested companion classes can also be used. The companion class can also be
If a class implements
Serializable and one or more fields refer to mutable then we must provide an explicit
readResolve() method, or use the
ObjectInputStream.readUnshared() even if default is fine, because an attacker can create a mutable instance. (More on this in Item 90).
Item 18: Favour composition over inheritance
inheritance -> class extending another class
Unlike method invocation, inheritance violates encapsulation. Subclass depends on the implementation detail of the superclass and may break if the superclass is changed like in subsequent releases. The subclass is not isolated.
Use composition instead ->
private field referencing the instance of the required class (superclass from above).
This can be achieved with two classes:
- Reusable forwarding classes -> stores the reference and contains only the forwarding methods.
- The client class (wrapper class) which extends the forwarding class and overrides the required methods and becomes isolated
Wrapper class design is also known as Decorator pattern.
Delegation = composition + wrapper object passes itself to the internal wrapped object
Wrapper classes can't be used for callback frameworks as the wrapped object doesn't know about the wrapper also known as SELF problem. It is also tedious to write forwarding methods.
Item 19: Design and document for inheritance or else prohibit it
The class must document its self-use of overridable methods. For each
protected method, the documentation must show which overridable (non-
protected) methods it invokes. Use
@implSpec for it. It does show how besides the usual what, but that's the cost of using inheritance. If you need a common functionality that is in the overridable method then extract it in
private helper method and reuse it.
The overridable subclasses must be tested by subclassing multiple times, not by the class implementor.
The constructor must not invoke overridable methods. It is because in subclass the super constructor is called first, and if the overridable method of a subclass is called from the superclass, then at that time the subclass object is not created and may lead to failure. The constructor may invoke
Special care has to be taken if a class implements
Cloneable as they also behave in a much way similar to the constructor.
If a class implements an interface capturing its essence like
Map then the inheritance can be done.
Item 20: Prefer interfaces to abstract classes
- Since a class can only extend one class, existing classes can easily be retrofitted by implementing a new interface.
- Ideal for defining mixins. Mixin classes denotes the additional responsibility a class has to perform apart from primary type.
- Allow for the construction of non-hierarchical type frameworks. Type hierarchies are good for some cases but in other it is a nightmare. If a hierarchical structure is used and multiple conditions has to be supported it might lead to combinatorial explosion of classes.
Note from author/blogger of this post: The fourth point the book suggests Interfaces enable safe, powerful functionality enhancements. I, personally, don't think that this is an advantage of interfaces over the abstract classes. As the forwarding class can work in same way with an abstract class as with an interface on the lines of Item 18. This needs to be decided upon. Readers may leave the valuable feedback on it.
We can use default methods for implementation assistance. But they should be described using
@implSpec for the purpose of overriding while inheritance if required. There is a shortcoming of default methods in interface is that they can't be used for methods of
toString(). It is because of class wins rule.
Interfaces can not have instance fields, non-
public static fields except
The class wins rule can be overcome by a mix of interface and abstract classes. We can provide an abstract skeletal implementation class. The responsibilities shared are:
- Interface : define the type, some default methods
- skeleton implementation : implement remaining non-primitive interface method atop primitive interface methods.
Extending the skeletal implementation takes less work for implementing the interface. It is Template Method pattern. The convention of skeletal class name is AbstractInterface, like
Interface with default methods can also suffice without skeletal class. Skeletal class is a great use case for overriding Object methods in which default interface methods fail. Skeletal classes are incidentally an example of Adapter Pattern in many cases. Try to give good documentation in skeletal class for the client.
Item 21: Design interfaces for posterity
Default methods were introduced primarily for lambdas, and injected to implementations without their consent. It is not always possible to write a default method that maintains all invariants of every conceivable implementation.
In the presence of default methods, existing implementations of an interface may compile without error or warning but fail at runtime. Default methods should be avoided unless absolutely necessary. They are however useful in implementing the interface like in Item 20.
Default methods were not designed to support removing methods from interfaces or changing the signatures of existing methods.
Item 22: Use interfaces only to define types
constant interface is used somewhere to combine constants. It is implemented to use constants. It is a poor use case as it leaks into the implementation and commitment for future use ex
java.io.ObjectStreamConstants. It should be avoided.
Instead non-instantiable class should be used. The client may prefer
static import for simplicity of use.
Item 23: Prefer class hierarchies to tagged classes
Some times class instances come in different flavours. They have a field like tag and works according to its value. Its implementations is stuffed with switch cases to determine the action to perform.
They have many shortcomings:
- cluttered boilerplate code
- reduced readability
- high memory footprint
- adding a new flavour is different and highly coupled implementation
- constructor must set tag and other fields correctly else program fails at runtime
- data type has no clue about the flavour
Hierarchical classes can be used to show the natural relationships, final fields etc.
Item 24: Favor static member classes over nonstatic
Nested class : a class defined inside another class. Their main purpose in their lifetime is to serve the enclosing class.
Types of nested class:
- static member class
- nonstatic member class
- anonymous class
- local class
All (except 1.) are called inner classes.
Inner class usability diagram
Nested classes and some significances:
- common use case - public helper class
- implicitly associated with enclosing instance
- access enclosing class instance inside instance method of non-static member class using qualified this construct
- impossible to create an instance without an enclosing instance
- increases time and storage cost in instance construction
- can function as an adapter
- not a member of enclosing class
- simultaneously declared and instantiated
staticcontext: access to enclosing instance
staticcontext: access to only constant fields (
finalprimitive or type
Stringthat is initialized with a constant expression [JLS, 4.12.4])
- can't use
- least frequently used
- used anywhere like a local variable
- have access to enclosing instance iff in non-
- can not contain
Item 25: Limit source files to a single top-level class
Don't put multiple top-level classes or interfaces in a single source file.