Java Effective Chapter 4 - mariamaged/Java-Android-Kotlin GitHub Wiki

Java - Effective Chapter 4

Item 15: Minimize the Accessibility of Classes and Members

The single most important factor that distinguishes a well-designed component from a poorly-designed one is the degree to which the component hides its internal data and other implementation details from 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 mechanism specifies the accessibility of classes, interfaces and members.
  • The accessibility of 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 classes and interfaces, 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 classes and nested 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 decoupled from 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 commitment to an implementation 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 superclass method, it cannot have a more restrictive access level in 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 an error message when 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 public in the class.


Note 4
  • 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.
    • This means you give up the ability to enforce invariants involving 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.
  • Even if a field is final or refers to an immutable object, by making it public, you give up the flexibility to switch to a new internal data representation in which the field does 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 by underscores.
  • 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. enter image description here
 

Solution 1
  • Make the public array private and add a public 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 private and add a public method that 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 declarations in its module declaration (which is by convention contained in a source file called module-info.java.

Item 16: In Public Classes, Use Accessor Methods, Not Public Fields

  • 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;	
}
}

Item 17: Minimize Mutability

  • 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 Libraries contain many immutable classes, including:
    • String.
    • The boxed primitive classes.
    • BigInteger.
    • BigDecimal.
  1. Immutable classes are easier to design, implement, and then use than mutable classes.
  2. They are less prone to error, and are more secure.

To make a class immutable, follow these rules:
  1. Don't provide methods that modify the object's state. (known as mutators).
  2. Ensure that the class can't be extended.
    • This prevents careless or malicious subclasses 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.
  3. 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 reference to a newly created instance is passed from one thread to another without synchronization.
  4. 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 fields containing primitive values or references to immutable objects, it is not recommended because it precludes changing the internal representation in a latter release.
  5. 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 copies in constructors, accessors, and readObject methods.

Example

// 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 effect of 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 constants for 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 magnitude representation internally.
    • The sign is represented by an int.
    • The magnitude is represented by an int array.
  • The negate method produces a new BigInteger of like magnitude and opposite 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.
⚠️ **GitHub.com Fallback** ⚠️