Java Effective Chapter 4 - mariamaged/Java-Android-Kotlin GitHub Wiki
The single most important factor that distinguishes a
well-designed componentfrom apoorly-designed oneis the degree to which the component hides itsinternal dataandother implementation detailsfrom other components.
- A well-designed component hides all its implementation details, cleanly
separating its API from its implementation. - Components then communicate only through their APIs and are oblivious to each other's inner workings.
- This concept, known as information hiding or encapsulation, is fundamental tenet of software design.
Java has many facilities to aid in information hiding.
- The
access control mechanismspecifies the accessibility of classes, interfaces and members. - The
accessibilityof an entity is determined by:- The location of its declaration.
- By which of the access modifiers (private, protected, and public) is present on the declaration.
Rule of thumb: make each class or member as inaccessible as possible.
- For top-level
classesandinterfaces, there are only two possible access levels:- package-private.
- public.
- If you declare a top-level class or interface with the public modifier, it will be public; otherwise it will be package-private.
- If a top-level class or interface can be made package-private, it should be.
- If a top-level class or interface is used by only one class, consider making the top-level class or interface a private static nested class of the sole class that uses it.
- For members (
fields,methods,nested classesandnested interfaces), there are four possible access levels:- private-The member is accessible only from the top-level class where it is declared.
-
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 and from any class in the package where it is declared.
- public- The member is accessible from anywhere.
Note 1
- Only if another class in the same package really needs to access a member should you remove the private modifier, making the member package-private.
- If you find yourself doing this often, you should reexamine the design of your system to see if another decomposition might yield classes that are
better decoupledfrom each other. - These fields, however, can "leak" into the exported API if the class implements Serializable.
Note 2
- For members of public classes, a huge increase in accessibility occurs when the access level goes from package-private to protected.
- A protected member is part of the class's exported API and
must be supported forever. - Also, a protected member of an exported class represents a
public commitmentto animplementation detail. - The need for protected members should be relatively rare.
Note 3
- There is a key rule that restricts your ability to reduce the accessibility of methods.
- If a methods
overrides a superclassmethod, itcannot have a more restrictive access levelin the subclass than in the superclass. - This is necessary to ensure that an instance of the subclass is usable anywhere an instance of the superclass is usable (the Liskov substitution priniciple).
- If you
violate this rule, the compiler will generate anerror messagewhen you try to compile the subclass.
A special case of this rule is that if a class implements an interface, all of the class methods that are in the interface must be
declared publicin the class.
Note 4
- Instance fields of public classes should rarely be public.
- If an instance field is
nonfinalor is areference 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.- This means you give up the ability to enforce
invariantsinvolving the field. - Also, you give up the ability to take any action when the field is modified.
- So classes with public mutable fields are not generally
thread-safe.
- This means you give up the ability to enforce
- Even if a field is
finalorrefers to an immutable object, by making it public, you give up the flexibility to switch to a new internal data representation in which the fielddoes not exist.
- The same advice applies to static fields, with one exception.
- You can expose constants via public static final fields, assuming the constants form an integral part of the abstraction provided by the class.
- By convention, such fields have names consisting of
capital letters, with words separated byunderscores.
- By convention, such fields have names consisting of
- It is critical that these fields contain either primitive values or references to immutable objects.
- A field containing a reference to a mutable object has all the disadvantages of a non final field.
- While the reference cannot be modified, the referenced object can be modified-with disastrous results.
Note that a nonzero-length array is always mutable, so it wrong for a class to have a public static final array field, or an accessor that returns such a field.
Solution 1
- Make the public array
privateand add apublic immutable list.
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));Solution 2
- Alternatively, you can make the array
privateand add apublic methodthat returns a copy of a private array.
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUE.clone();
}- As of Java 9, there are two additional, implicit access levels introduced as part of the module system.
-
Module: grouping of packages, like a package is a
grouping of classes. - A module may explicitly export some of its packages via
export declarationsin itsmodule declaration(which is by convention contained in a source file called module-info.java.
- Occasionally, you maybe tempted to write degenerate classes that serve no purpose other than to group instance fields.
// Degenerate classes like this should not be public !
class Point {
public double x;
public double y;
}- Because the data fields of such classes are accessed directly, these classes do not offer the benefits of encapulation.
- You cannot change the representation without changing the API.
- You can't enforce invariants.
- You can't take an auxiliary action when a field is accessed or modified.
- These classes should be replaced by classes with:
- private fields.
- public accessor methods (getters), and for
mutable classes, - public mutators (setters).
// Encapsulation of data by accessor methods and mutators
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
}Case 1: If a class is accessible outside its package
- Provide accessor methods to preserve the flexibility to change the class's internal representation.
- If a public class exposes its data fields, all hope of changing its representation is lost because client code can be distributed far wide.
Case 2: If a class is package-private or is a private nested class.
- There is nothing inherently wrong with exposing its data fields- assuming they do an adequate job of describing the abstraction provided by the class.
- This approach generates less visual clutter than the accessor method approach.
- While it is never a good idea for a public class to expose fields directly, it is less harmful if the
fields are immutable.- You can't change the representation of such a class without changing its API.
- You can' take auxiliary actions when a field is read, but you can enforce invariants.
- For example, this class guarantees that each instance represents a valid time:
// Public class with exposed immutable fields - questionable
public final class Time {
private static final int HOURS_PER_DAY = 24;
private static final int MINUTES_PER_HOUR = 60;
public final int hour;
public final int minute;
public Time(int hour, int minute) {
if(hour < 0 || hour >= HOURS_PER_DAY)
throw new IllegalArgumentException("Hour: " + hour);
if(minute <0 || minute>= MINUTES_PER_HOUR)
throw new IllegalArgumentException("Min: " + minute);
this.hour = hour;
this.minute = minute;
}
}- An immutable class is simply a class whose instances cannot be modified.
- All of the information contained in each instance is fixed for the lifetime of the object, so no changes can ever be observed.
- The
Java Platform Librariescontain many immutable classes, including:- String.
- The boxed primitive classes.
- BigInteger.
- BigDecimal.
- Immutable classes are easier to
design,implement, and thenusethan mutable classes. - They are less prone to error, and are more secure.
To make a class immutable, follow these rules:
- Don't provide methods that modify the object's state. (known as mutators).
-
Ensure that the class can't be extended.
- This prevents
carelessormalicioussubclasses from compromising the immutable behavior of the class by behaving as if the object's state has changed. - Preventing subclassing is generally accomplished by
making the class final, but there is an alternative.
- This prevents
-
Make all fields final.
- This clearly expresses your intent in a manner that is enforced by the system.
- Also, it is necessary to ensure correct behavior if a
referenceto a newly createdinstanceispassed from one thread to anotherwithoutsynchronization.
-
Make all fields private.
- This prevents clients from obtaining access to mutable objects referred referred to by fields and modifying these fields directly.
- While it is technically permissible for immutable classes to have
public final fieldscontaining primitive values or references to immutable objects, it isnot recommendedbecause it precludes changing theinternal representationin a latter release.
-
Ensure that exclusive access to any mutable components.
- If your class has fields that refer to mutable objects, ensure that clients of the class
cannot obtain references to these objects.- Never (1) initialize such a field to a client-provided object reference or return the field or (2) return the field from an accessor.
- Make
defensive copiesin constructors, accessors, andreadObjectmethods.
- If your class has fields that refer to mutable objects, ensure that clients of the class
// Immutable complex number class
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public double realPart() {
return re;
}
public double imaginaryPart() {
return im;
}
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c) {
return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
}
public Complex dividedBy(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / tmp,
(im * c.re - re * c.im) / tmp);
}
@Override
public boolean equals(Object o) {
if(o == this)
return true;
if(!(o instanceof Complex))
return false;
Complex c = (Complex) o;
return Double.compare(c.re, re) == 0
&& Double.compare(c.im, im) == 0;
}
@Override
public int hashCode() {
return 31 * Double.hashCode(re) + Double.hashCoode(im);
}
@Override
public String toString() {
return "(" + re " + " + im + "i)";
}
}Notice how the arithmetic operations create and return a new Complex instance rather than
modifying this instance.
-
Functional approach pattern: methods return the result of applying a function to their operand, without modifying it.
- The BigInteger and BigDecimal classes did not obey this naming convention, and it led to many usage errors.
-
Procedural or imperative pattern: in which methods apply a procedure to their operand,
causing its state to change.
-
Immutable objects are inherently thread-safe; they require no synchronization.
- They cannot be corrupted by multiple threads accessing them concurrently.
- This is far and away the easiest approach to achieve thread safety.
- Since no thread
can ever observe any effectof another thread on an immutable object,immutable objects can be shared safely. - Immutable classes should therefore encourage clients to reuse existing instances whenever possible.
- One easy way to do this is to provide
public static final constantsfor commonly used values.
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);- This step can be taken one step further.
- An immutable class can provide static factories that
cache frequently requested instances when existing ones would do.- This reduces memory footprint.
- And garbage collection costs.
- Opting for static factories in place of public constructors when designing a new class gives you the flexibility to add caching later, without modifying clients.
- A consequence of the fact that immutable objects can be shared freely is that you never have to make defensive copies of them.
- In fact, you never have to make any copies at all because the copies would
forever be equivalent to the originals. - Therefore, you need not and should not provide a clone method or copy constructor.
- This was not understood in the early days of the Java platform, so the String class does have a copy constructor, but it should rarely, if ever, be used.
Not only can you share immutable objects, but they can share their internals.
- For example, the BigInteger class uses a
sign magnituderepresentation internally.- The
signis represented by anint. - The
magnitudeis represented by anint array.
- The
- The negate method produces a new BigInteger of
like magnitudeandopposite sign. - It does not copy the array even though it is mutable; the newly created BigInteger points to the same internal array as the original.
Immutable objects make great building blocks for other objects, whether mutable or immutable.
- It is much easier to maintain the invariants of a complex object if you know that its component will not change underneath.