Effective Java 3 - Classes and Interfaces



13. Minimize the accessibility of classes and members

Four accessibilities in Java:

  • private: only accessible inside the class itself
  • package-private(default): accessible inside the package
  • protected: accessible from subclasses of the class where it is declared, and from any class inside the package
  • public: accessible from anywhere

It is not acceptable to make a class, interface or member a part of a package's exported API to facilitate testing.

Instance fields should never be public. if the field is not final, and it is public, that means you give up the control to limit the ability to store in this field. Also, classes with public mutable fields are not thread-safe.

For static field, it is ok if it declares to be final. The naming convention for these field is upper case.

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. The elements inside the array can be modified. You can make a the public array private and add a public immutable list. Or return a clone of that private array

private static final Thing[] PRIVATE_VALUES = { .. };
public static final List< Thing > VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

14. In public classes, use accessor methods, not public fields

The reason to do it is flexibility: easy to change to way to get and set the value, without affecting other parts of the code

However, if a class is package-private or is a private nested class, there is nothing inherently wrong with exposing its data fields.

15. Minimize mutability

Some advices to make things immutable:

  1. Don't provide any methods that modify the object's state(aka. mutators)
  2. Ensure that the class can't be extended
  3. Make all fields final
  4. Make all fields private
  5. Ensure exclusive access to any mutable components. If your class has any fields that refer to mutable objects, ensure that clients of the class cannot obtain references to these objects.

Immutable objects can be shared freely, they require no synchronization.

The only disadvantage of immutable classes is that they require a seperate object for each distinct value. Use lazy initialization.

To ensure the immutability, a class must not permit itself to be suclassed. Static factory is a good way to implement it instead of constructor.

No methods may produce an externally visible change in the object's state.

Example: Complex number

public class Complex {
    private final double re;
    private final double im;

    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }

    public static Complex valueOfPolar(double rm, double theta) {
        return new Complex(r * Math.cos(theta), r * Math.sin(theta) );
    }
}

16. Favor composition over inheritance

Unlike method invocation, inheritance violates encapsulation. If superclass changes implementation, subclass will be affected.

If every A object has a B object inside, the class A is called wrapper class. This is the Decorator design pattern. This is not delegation, unless wrapper passes itself to the wrapped object.

17. Design and document for inheritance or else prohibit it

If design for inheritance, the class must document its self-use of overridable methods. For every public or protected methods, the documentation must indicate which overridable methods the method or constructor invokes

18. Interface is better than Abstract class

Abstract class limits a class more than interface.

Interface is an ideal option for mixin definition of class.

Skeleton Implementation(Abstract interface), makes it easy to provide implementation for interface. It combines the advantage of both interface and abstract class.

Simulated Multiple Inheritance: makes a private internal class implements an interface, so that the public class can implements multiple abstract class if needed.

Example: Skeleton Implementation

public abstract class AbstractMapEntry<K,V> implements Map.Entry<K, V>
{
    public abstract K getKey();
    public abstract V getValue();

    // Simple Implementation
    public V setValue(V value) {
        throw new UnsupportedOperationException();
    }

    @Override 
    public boolean equals(Object o) {
        if (o == this)
            returh true;
        if ( ! (o instanceof Map.Entry))
            returh false;
        Map.Entry<?,?> arg = (Map.Entry) o;
        return   equals(getKey(), arg.getKey()) &&
                equals(getValue(), arg.getValue());
    }
    
    private static boolean equals(Object o1, Object o2) {
        return o1 == null ? o2 == null : o1.equals(o2);
    }

    // Implements the general contract of Map.Entry.hashCode
    @Override
    public int hashCode(){
        return hashCode(getKey()) ^ hashCode(getValue());

    }

    private static int hashCode(Object obj) {
        return obj == null ? 0 : obj.hashCode();
    }
}

Skeleton implementation is designed for inheritance. The methods that don't have a concrete implementation can have a simple implementation, like setValue() in the example.

Once a interface is published and widely adopted, it is almost impossible to change it.

19. Interface for defining type only

Constant interface: contains no methods, only static final fields. It is for defining constants. IT IS NOT A GOOD WAY TO USE INTERFACE. An example is java.io.ObjectStreamConstants. It is a bad example from Java API.

Better implementation is Utility class.

public class PhysicalConstants {
    private PhysicalConstants() {} //Prevents instantiation

    public static final double AVOGADROS_NUMBER = 6.02214199e23;
    public static final double BOLTZMANN_CONSTANT = 1.3806503e-23;

}

20. Prefer class hierarchies to tagged classes

Tagged class: many seperated implementation dumped into one class. Tagged class is always too long, low performance.

Subclass is better than tagged class. Tagged class is a simulation of class hierarchies.

21. Function object as strategies

This is Strategy design pattern.

Example: comparator

class StringLengthComparator {
    private StringLengthComparator() {}
    public static final StringLengthComparator INSTANCE = new StringLengthComparator();
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
}

Since StringLengthComparator is stateless, no fields, therefore it is better to be implemented as a singleton.

If we have many comparators, we'd better define a comparator interface. (Actually it is inside java.util)

public interface Comparator<T> {
    public int compare(T t1, T t2);
}

It is also recommended to have a static strategy factory class, the concrete strategy implementation classes are inside the factory class, and they all implement the strategy interface.

22. Favor static member classes over nonstatic

Nested class is internal class, for serving the enclosing class. There are four types of nested class: static member class, nonstatic member class, anonymouse class and local class.

Static member class is just normal class that defined inside a class, no special purpose. It is an accesory of enclosing class.

Nonstatic member class is much different than static member class. The instance of nonstatic member class may related to an enclosing instance. They may call the method of enclosing class.

Example: nonstatic member class

public class MySet<E> extends AbstractSet<E> {
    //...

    public Iterator<E> iterator() {
        return new MyIterator();
    }

    private class MyIterator implements Iterator<E> {
        // ...
    }
}

For this MyIterator class, if it is not required to visit enclosing class, then it may be declared as static.

Anonymous class has no class name, cannot execute instanceof test. It is suitable for dynamicly create function object. or create process object like Runnable, Thread.

Local class is just other local class.


Share this article