Classes and Interfaces
Minimise the accessibility of classes and members
enforcing encapsulation
Speeds up the system development as components can be developed in parallel
Enables effective performance tuning. Components can be optimized separately without affecting the others
isolation -> does not influence correctness of others
reduces maintenance costs
easier reasoning/debugging/replacement
Increases software reuse as components aren’t tightly coupled
decoupled components usually provide use in contexts beyond original design
Decrease the risk in building large systems, improves resilience
individual components may prove successful even if the system does not
Make accessibility as low as possible. Work on a public API that you want to expose and try not to give access to implementation details.
About accessors for members (fields, methods, nested classes, and nested interfaces)
public
The member is accessible from anywhere.
package-private
The member is accessible from any class in the package where it is declared. Technically known as default access, this is the access level you get if no access modifier is specified (except for interface members, which are public by default).
protected
The member is accessible from subclasses of the class where it is declared (subject to a few restrictions) and from any class in the package where it is declared.
A protected member of an exported (i.e., public) class represents a public commitment to an implementation detail. The need for such protected members should be relatively rare.
private
The member is accessible only from the top-level class where it is declared.
Both 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 Serializable.
top level Classes and Interfaces can only be public or package private
If a top-level class or interface can be made package-private, it should be.
By making it package-private, you make it part of the implementation rather than the exported API, and you can modify it, replace it, or eliminate it in a subsequent release without fear of harming existing clients.
If a package-private top-level class or interface is used by only one class, consider making the top-level class a private static nested class of the sole class that uses it.
Instance fields of public classes should rarely be public.
If an instance field is nonfinal or is a reference to a mutable object, then by making it public, you give up the ability to:
limit the values that can be stored in the field
enforce invariants involving the field
take any action when the field is modified, so classes with public mutable fields are not generally thread-safe.
a nonzero-length array is always mutable, so it is wrong for a class to have a public static final array field, or an accessor that returns such a field.
Issues of IDEs generating getters for these fields
To get access to an array There are generally two ways around it:
Make the array private, and let the accessor return an immutable List (e.g., construct one using Collections.unmodifiableList(Arrays.asList(THE_ARRAY)).
Make the array private, and let the accessor return a copy of the array using clone().
Module system
Helps with issues with public accessor which you dont want to expose to the consumer
A module is a grouping of packages, like a package is a grouping of classes.
A module may explicitly export some of its packages via export declarations in its module declaration (which is by convention contained in a source file named module-info.java).
Public and protected members of unexported packages in a module are inaccessible outside the module; within the module, accessibility is unaffected by export declarations.
Public and protected members of public classes in unexported packages give rise to the two implicit access levels, which are intramodular analogues of the normal public and protected levels. The need for this kind of sharing is relatively rare and can often be eliminated by rearranging the classes within your packages.
Favour accessors over public fields in public classes
Public classes should never expose its fields. Doing this will prevent you to change its representation in the future.
Package private or private nested classes, can, on the contrary, expose their fields since it won't be part of the API.
Public classes should never expose mutable fields.
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
EG java.lang.String, the boxed primitive classes, BigInteger and BigDecimal
Why
easier to design, implement and use than mutable classes
are simple
has always exactly one state
the state in which it was created they are easier to use reliably
are inherently thread-safe (they require no synchronisation)
thus can be shared freely, promoting reuse
no defensive copies necessary
their internals can be shared freely
make great building blocks for other objects
they also make great map keys or set elements
provide atomicity for free
immutable classes are easier to realise using functional, rather than imperative approach
How
Don't provide methods that modify the object's state
no setter methods
Ensure that the class can't be extended
The simplest way to do this is to declare the class as final.
to make the constructor private and construct instances in factory methods
make default constructor (no args) private and throw AssertionError
Make all fields final
Exceptions may be made as long as there are no externally visible change in the object's state.
One example is to have a nonfinal field serve as cache for the result of the an expensive computation.
Make all fields private
Ensure exclusive access to any mutable components
If the class has any fields that refer to mutable objects, ensure that clients of the class cannot obtain references to these objects.
Never initialize such a field to a client-provided object reference or return the field from an accessor.
Make defensive copies in constructors, accessors, and readObject() methods.
Constructors should create fully initialized objects with all of their invariants established.
The major disadvantage of immutable classes is that
they require a separate object for each distinct value.
Creating these objects can be costly, especially if they are large.
Solution
One way around this is to provide a package-private / public mutable companion class
String has Stringbuilder
it was not widely understood that immutable classes had to be effectively final when BigInteger and BigDecimal were written, so all of their methods may be overridden.
Unfortunately, this could not be corrected after the fact while preserving backward compatibility.
If you write a class whose security depends on the immutability of a BigInteger or BigDecimal argument from an untrusted client, you must check to see that the argument is a "real" BigInteger or BigDecimal , rather than an instance of an untrusted subclass.
If you choose to have your immutable class implement Serializable and it contains one or more fields that refer to mutable objects
you must provide an explicit readObject or readResolve method
use the ObjectOutputStream.writeUnshared and ObjectInputStream.readUnshared methods, even if the default serialized form is acceptable.
Otherwise an attacker could create a mutable instance of your class.
If object is mutable, limit it
Favour composition over inheritance
applies to implementation inheritance (extends), not interface inheritance
what
inheritance allows classes extends (subclass) a non final class (becomes parent/superclass), thus gain it's behaviour for free
If parent class has abstract methods, it must be implemented in the child/subclass class if that class want to be instantiated.
abstract methods allow subclasses to implement subclass specific behaviour which is different to the parent class
This can also apply to non abstract methods in the parent class, if not overridden by child class, they use the default impl from the parent class
inheritance is a powerful way to achieve code reuse
This also allows for polymorphism, as the subclass can be defined by it's parent class type
bad
can lead to fragile software if employed inappropriately
violates encapsulation
subclass depends on implementation details of the superclass - both must evolve in tandem, coupled
Can lead to complex hierarchies, hard to refactor
related cause of fragility is that their superclass can acquire new methods in subsequent releases
subclasses can break
E.g., the parent class may add a public setter to a private field, essentially making the class publicly mutable.
When overriding a method, you cannot be sure whether the behavior any of the (non-overriden) methods changed, because the implementation of such other methods might depend on the overriden method.
Can only extend one class, no multiple inheritance
diamond problem, inheriting same method from multiple superclasses, which one to use?
if extending a class, but dont need all the behaviour
throw unsupportedException, but breaks liskov substitution and the polymorphic contract
Causes user to decide early on what the design is and extract out reuse, but this can change and harder to change design once created with inheritance
when
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
only appropriate for classes that pass the “is-a” test
answer truthfully to question “If B extended A, is B really an A?”
This violated a lot in java libraries
alternative - use Composition
composition:
Create a new class that has as its member an instance/component of the existing class (i.e., the class that would otherwise be extended).
This new class will delegate the behaviour it wants to the component
component can be instantiated in the field or inside a method, but better to inject (method arg or constructor)
forwarding aka decorator:
Instead of having the new class hold a direct reference to the existing class, have it hold a reference to a forward class – another class that has the same public API as the existing class, and wraps the existing class by simply forwarding each call to the actual method on the instance of the existing class.
This also prevents the additional of new methods on the existing class from having any impact on the new class.
This idea is used in multiple patterns
Benefits
they don’t depend on the implementation details of the existing class
issues with inheritance are removed
you can define your own API for your class
bad
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
Design and document for inheritance or else prohibit it
Document precisely the effects of overriding any method.
document any self-use of overridable methods.
The description is to be put in a special section of the specification, labeled "Implementation Requirements," which is generated by the Javadoc tag @implSpec. The documentation should be in the method calling the overridable method, explaining how the overridable method is used.
To allow programmers to write efficient subclasses without undue pain, 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.
Constructors must not invoke overridable methods, directly or indirectly.
Because constructor of superclass are called before the constructor of the subclass is done initializing the entire class, if the constructor of the superclass calls an overriden method that depends on certain state of the class having been set up by the constructor of the subclass, the class may not behave as expected.
If you do decide to implement either Cloneable or Serializable in a class that is designed for inheritance, you should be aware that because the clone() and readObject() methods behave a lot like constructors, a similar restriction applies: neither clone() nor readObject() may invoke an overridable method, directly or indirectly.
In the case of readObject(), the overriding method will run before the subclass's state has been deserialized.
In the case of clone(), the overriding method will run before the subclass's clone() method has a chance to fix the clone's state.
If you decide to implement Serializable in a class designed for inheritance and the class has a readResolve() or writeReplace() method, you must make the readResolve() or writeReplace() method protected rather than private. If these methods are private, they will be silently ignored by subclasses.
To enable safer subclassing, it is possible to eliminate a class's self-use of overridable methods mechanically
Move the body of each overridable method to a private "helper method" and have each overridable method invoke its private helper method. Then replace each self-use of an overridable method with a direct invocation of the overridable method's private helper method.
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
Prefer interfaces to abstract classes
Java permits only single inherinance.
Interfaces are ideal for designing mixins.
A mixin can be thought of as a type that a class can implement in addition to its "primary type", to declare that it provides some optional behavior
Intefaces allow for the construction of nonhierarchical type frameworks.
Interfaces enable safe, powerful functionality enhancements via the wrapper class idiom.
A wrapper class is one that
(a) holds a reference to the base class to be extended
(b) implements the interface of the base class
(c) forwards any methods on the interface to the methods on the base class held as reference.
If abstract classes were used instead, the programmer who wants to add functionality has no choice but to use inheritance instead, which is more fragile.
When there is an obvious implementation of an interface method in terms of other interface methods, consider providing implementation assistance to programmers in the form of a default method.
skeletal implementation classes are called Abstract Interface, where Interface is the name of the interface they implement
downsides
Interfaces are limited in certain aspects: e.g., inability to provide default implementations for hashCode() or equals() method, and inability to contain instances field or nonpublic static members.
writing an implementation class
study the interface and decide which methods are the primitives in terms of which the others can be implemented. These primitives will be the abstract methods in your skeletal implementation.
provide default methods in the interface for all of the methods that can be implemented directly atop the primitives
If the primitives and default methods cover the interface, you're done, and have no need for a skeletal implementation class.
write a class declared to implement the interface, with implementations of all of the remaining interface methods. The class may contain any non public fields ands methods appropriate to the task.
Design interfaces for posterity
With Java 8, it's now possible to add new methods in interfaces without breaking old implementations thanks to default methods. Nonetheless, it needs to be done carefully since it can still break old implementations that will fail at runtime.
The problem that default methods try to solve is, before Java 8, once an interface was released to the wild you could never change it safely.
Default methods allow us to do is provide a default implementation for a particular method so we can evolve an interface without requiring the consumer of the service to implement that method right away.
Issues with Default
we don’t have a guarantee that our default implementation will work with all the implementations of the interface.
Example removeIf from Collections, if impl does not override it will use the defult impl in interface and can cause issues
In the presence of default methods, existing implementations of an interface may compile without error or warning but fail at runtime.
Use interfaces only to define types
Do not use constant interface: an interface that has no methods, and consists solely of static final fields, each exporting a constant.
Use an enum or noninstantiable utility class instead.
If the constants are best viewed as members of an enumerated type, you should export them with an enum type
If the constants are NOT strongly tied to an existing class or interface, export the constants with a noninstantiable utility class
Prefer class hierarchies to tagged classes
A tagged class is one that stores a "tag" variable indicating the "flavor" of the class, and many of the class methods will switch based on the tag.
Those kinds of classes are clutted with boilerplate code (Enum, switch, useless fields depending on the enum). Don't use them.
Tagged classes are verbose, error-prone and inefficient. They have lot of boilerplate code, bad readability, they increase the memory footprint and more shortcomings
use this:
Favour static member classes over non static
four kinds of nested classes:
static member classes
usage
as a public helper class: e.g., an inner enum describing the operations supported by the outer calculator class.
If a nested class needs to be visible outside of a single method or is too long to fit comfortably inside a method,
If each instance of a member class needs a reference to its enclosing instance, make it nonstatic
nonstatic member classes
Each instance of a nonstatic member class is implicitly associated with an enclosing instance of its containing class.
Within instance methods of a nonstatic member class, you can invoke methods on the enclosing instance or obtain a reference to the enclosing instance using the qualified this construct: i.e., EnclosingType.this.
usage
to define an Adapter that allows an instance of the outer class to be viewed as an instance of some unrelated class.
For example, an iterator might be a nonstatic member class implementing the Iterator inteface.
anonymous classes
local classes
defined within a method or a scope block.
Assuming the class belongs inside a method, if you need to create instances from only one location and there is a preexisting type that characterizes the class, make it an anonymous class; otherwise, make it a local class.
If a member class does not need access to its enclosing instance, then declare it static. If the class is non static, each instance will have a reference to its enclosing instance. That can result in the enclosing instance not being garbage collected and memory leaks.
Limit source files to a single top-level class
Having multiple top-level classes would mean that the file name cannot be made correspond to the class(es) defined within that file. Consequently, it would not be immediately obvious when multiple classes of the same name are defined in different files in a particular project. This would lead to hard-to-debug problems where a different class might be compiled into the .jar file depending on the argument passed to javac.
consider using static member classes. Because it enhances readability.
Last updated