Effective java : Classes

Classes

Objective: This section contains guidelines to help you make the best use of powerful elements of Java to design and implement usable, robust, and flexible classes and interfaces.

Key topics:

  1. Accessibility of Classes and Members
  2. Accessor Methods and Public Fields
  3. Mutability
  4. Composition and Inheritance
  5. Inheritance Documentation
  6. Interfaces and Abstract Classes
  7. Interface Design and Posterity
  8. Interfaces and Constant Classes
  9. Class Hierarchies and Tagged Classes
  10. Static and Non-Static Member Classes

Estimated time: 15-30 minutes.

Accessibility of Classes and Members

Can you spot a potential security hole in one of the following three code segments?

 Use of a public list

private static final String[] PRIVATE_COLORS = { … };
public static final List<String> COLORS = ImmutableList.copyOf(PRIVATE_COLORS);

 Use of a public accessor

private static final String[] PRIVATE_COLORS = { … };
public static final String[] colors() {
return PRIVATE_COLORS.clone();
}

 Use of a public array

public static final String[] COLORS = { … };

You should avoid using a public array because a nonzero-length array is always mutable, and thus clients will be able to modify the elements of the array. In general, you should reduce accessibility of program elements as much as possible, so as to hide the implementation as much as possible.

Accessor Methods and Public Fields

Which of the following two implementations would you prefer?

 Use of public accessors

public class Person {
private final String firstName;
private final String lastName;

public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

public String getFirstName() {return firstName; }
public String getLastName() {return lastName; }
}

 Use of public fields

public class Person {
public String firstName;
public String lastName;

public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}

You should avoid using public fields because if a class exposes its data fields then all hope of changing its representation is lost after client codes have been distributed far and wide. Accessor methods preserve flexibility to change internal representation of the class without causing problems for client codes; that is a key feature of encapsulation in OOP. Note also that exposing mutable public fields will result in serious problems, as observed in the implementation of  Point  and  Dimension  in  java.awt  package.

Mutability

Which of the following two implementations would be more flexible?

 Immutable class

public class Complex {
private final double r;
private final double i;

private Complex(double r, double i) {
this.r = r;
this.i = i;
}

public static Complex valueOf(double r, double i) { return new Complex(r, i); }

public double realPart() { return r; }
public double imaginaryPart() { return i; }

public Complex plus(Complex c) { return new Complex(r + c.r, i + c.i); }

}

 Mutable class

public class Complex {
public double r;
public double i;

public Complex(double r, double i) {
this.r = r;
this.i = i;
}

public Complex plus(Complex c) {
r += c.r;
i += c.i;
return this;
}

}

In general, classes should be immutable unless there’s a very good reason to make them mutable; and if so, you should minimize mutability when designing and implementing classes. Follow the following rules to make a class immutable:

  • Don’t provide methods that modify the state of objects that you want to be immutable.
  • Ensure that the class can’t be extended, so as to prevent careless or malicious subclasses from compromising the immutability of the class.
  • Make all fields final to express your intent clearly.
  • Make all fields private to prevent clients from obtaining access to mutable objects referred to by the fields and modifying these objects directly.
  • Ensure exclusive access to any mutable components, for example, by making defensive copies in constructors, accessors, and readObject methods.

Here are some key benefits of immutability:

  • Immutable objects are simple because it is always in only one state, and hence providing failure atomicity for free.
  • Immutable objects are inherently thread-safe, and thus they require no synchronization.
  • You can share immutable objects freely.
  • Immutable objects make great building blocks for other objects.

Keep in mind, however, that the biggest disadvantage of immutable classes is that they require a separate object for each distinct value, and creating those objects can be costly in some cases. So, sometimes you may need to provide supporting classes in addition to those immutable classes, for example,  StringBuilder  for  String .

Composition and Inheritance

Can you spot a flaw in one of the following implementations?

 Use of inheritance

public class MyHashSet<E> extends HashSet<E> {
private int addCount = 0;

public MyHashSet(){}
public MyHashSet(int initCap, float loadFactor) { super(initCap, loadFactor); }

public int getAddCount() { return addCount; }

@Override public boolean add(E e) {
boolean result = super.add(e);
if (result)
addCount++;
return result;
}
@Override public boolean addAll(Collection<? extends E> c) {
boolean result = super.add(c);
if (result)
addCount += c.size();
return result;
}
}

 Use of composition

// Wrapper class
public class MyHashSet<E> extends ForwardingSet<E> {
private int addCount = 0;

public MyHashSet(Set<E> s){ super(s); }

public int getAddCount() { return addCount; }

@Override public boolean add(E e) {
boolean result = super.add(e);
if (result)
addCount++;
return result;
}
@Override public boolean addAll(Collection<? extends E> c) {
boolean result = super.add(c);
if (result)
addCount += c.size();
return result;
}
}

// Reusable forwarding class
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }

public boolean add(E e) { return s.add(e); }
public int addAll(Collection<? extends E> c) { return s.addAll(c); }

}

Inheritance is powerful, but it is problematic in many cases because it violates encapsulation; so, it is appropriate only when a genuine subtype relationship exists between the subclass and the superclass, when both of them are in the same package, and when the superclass is designed for inheritance. In many cases, such as the one above, use of composition and forwarding is usually more robust and powerful than that of inheritance.

Inheritance Documentation

Which of the following implementations would be better documented?

 Keep documentation short

public abstract class AbstractCollection<E> implements Collection<E> {

/**
* {@inheritDoc}
*
* <p>This implementation iterates over the collection looking for the
* specified element.  If it finds the element, it removes the element
* from the collection using the iterator’s remove method.
*/
public boolean remove(Object o) { … }

}

 Provide implementation details

public abstract class AbstractCollection<E> implements Collection<E> {

/**
* {@inheritDoc}
*
* <p>This implementation iterates over the collection looking for the
* specified element.  If it finds the element, it removes the element
* from the collection using the iterator’s remove method.
*
* <p>Note that this implementation throws an
* <tt>UnsupportedOperationException</tt> if the iterator returned by this
* collection’s iterator method does not implement the <tt>remove</tt>
* method and this collection contains the specified object.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException            {@inheritDoc}
* @throws NullPointerException          {@inheritDoc}
*/
public boolean remove(Object o) { … }

}

If you decide that a class can be inherited then you must document its self-use of overridable methods. The above documentation with implementation details leaves no doubt that overriding the  iterator  method will affect the behavior of the  remove  one. It also explains exactly how the behavior of the  iterator  method will affect that of the  remove  method.

Note also that once you publish documentation of a class, you must commit to support it for the whole life of the class. If you fail to do this, subclasses may become dependent on implementation details of the superclass and may break if the implementation of the superclass changes. Also, to enable others to write efficient subclasses you may have to export one or more protected methods. So, designing and implementing inheritance are more difficult than you would think; unless you know for sure there is a real need for subclasses, you are probably better off prohibiting inheritance, for example, by declaring your class final or not providing any accessible constructors.

Interfaces and Abstract Classes

Which of the following designs would make it easier to design a new  SingerSongWriter  type that represents people that are both singers and song writers?

 Use of interfaces

public interface Singer {
AudioClip sing(Song s);
}

public interface SongWriter {
Song compose(int chartPosition);
}

 Use of abstract classes

public abstract class Singer {
AudioClip sing(Song s);
}

public abstract class SongWriter {
Song compose(int chartPosition);
}

In general, use of interfaces is the best way to define a type that permits multiple implementations because a class can implement multiple interfaces whereas it cannot extend multiple abstract classes. If you export a nontrivial interface, you should strongly consider providing a skeletal implementation to go with it, via default methods, so that all implementors of the interface can benefit from that.

Interface Design and Posterity

If you use Java 8+, it is possible to add new methods, with a default implementation, to existing interfaces without breaking existing implementations. Consider the following possible default implementation of  removeIf  method to be added to  Collection  interface in Java 8:

default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean isRemoved = false;
for (Iterator<E> it = iterator(); it.hasNext(); ) {
if (filter.test(it.next())) {
it.remove();
isRemoved = true;
}
}
return isRemoved;
}

Would the above default implementation of  removeIf  method be free of risk?

 Yes
 No

Although the above implementation is the best general-purpose implementation one could possible write for  removeIf  method, it fails on some real-world implementations of  Collection . For example, Apache Commons library’s  SynchronizedCollection  wrapper class provides the ability to use a client-supplied object for locking, in place of the collection. If this class is used in conjunction with Java 8 and haven’t override  removeIf method then it will inherit the above default implementation. Unfortunately, this default implementation doesn’t, indeed can’t, maintain the class’s fundamental promise: automatically synchronize around each method invocation, because the default implementation knows nothing about synchronization and has no access to the field that contains the locking object. If a client calls  removeIf  method on a  SynchronizedCollection  instance in the presence of concurrent modification of the collection by another thread then a  ConcurrentModificationException  exception or other unspecified behaviors may occur. Note that the client’s code compiles successfully without error or warning, but fails at runtime, ouch!

The moral of the above story is that you should always design interfaces, especially ones exposed to public use, with great care. While it is possible to correct some interface design flaws after an interface is released, you cannot count on it! Here are some additional best practices when designing an interface:

  • Choose method names meaningfully and carefully, so as to help your clients to use them easily.
  • Don’t go overboard in providing convenience methods, because maintaining public methods is costly.
  • Avoid long parameter list (four or fewer), because long sequences of identically types parameters are especially confusing and harmful.
  • For parameter types, favor interfaces over classes, so as to pass any implementation type in methods later.
  • Prefer two-element enum types to boolean parameters. For example,  public enum TemperatureScale { FAHRENHEIT, CELSIUS } would be more meaningful and extensible than boolean.

Interfaces and Constant Classes

Which of the following implementations would be preferable?

 Use of constant classes

public class PhysicalConstants {
private PhysicalConstants{}

public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
public static final double BOLTZMANN_CONST  = 1.380_648_520e-23;
public static final double ELECTRON_MASS    = 9.109_383_560e-31;
}

 Use of interfaces

public interface PhysicalConstants {
static final double AVOGADROS_NUMBER = 6.022_140_857e23;
static final double BOLTZMANN_CONST  = 1.380_648_520e-23;
static final double ELECTRON_MASS    = 9.109_383_560e-31;
}

You should use interfaces to define types only; using them to define constants merely is a poor application of interfaces because it represents a commitment that you must support forever, and sometimes it is confusing. Here are some reasonable practices for exporting constants:

  • If the constants are used only one time then you should add them to where it needs, better with a comment, so as to increase the readability of the codebase (i.e., readers don’t need to look for the location where it is defined to know its value). For example, it would be totally fine to use  sleep(1000L); // sleep 1,000 milliseconds
  • If the constants are strongly tied to an existing class or interface, you should add them to the class or interface, for the sake of encapsulation reason.
  • If the constants are best viewed as members of  an enumerated types then you should export them as an enum type.
  • Otherwise, you should export the constants with a noninstantiable utility class such as the one above.

Class Hierarchies and Tagged Classes

Which of the following implementations would be more flexible?

 Use of tagged classes

public class Figure {
private enum Shape { RECTANGLE, CIRCLE };

private final Shape shape;
private final double length; // for RECTANGLE
private final double width;  // for RECTANGLE
private final double radius; // for CIRCLE

public Figure(double l, double w) { length = l; width = w; }
public Figure(double r) { radius = r; }

public double area() {
if (shape == RECTANGLE)
return length * width;
else
return Math.PI * (radius * radius);
}

}

 Use of class hierarchies

public interface Figure {
double area();
}

public class Circle implements Figure {
private final double radius;

public Circle(r) { radius = r; }

public double area() { return Math.PI * (radius * radius); }

}

public class Rectangle implements Figure {
private final double length;
private final double width;

public Rectangle (double l, double w) { length = l; width = w; }

public double area() { return Math.PI * (radius * radius); }

}

Tagged classes are seldom appropriate. If you are writing or encounter such type of classes, consider whether you could eliminate and replace them with a hierarchy, because the latter is much easier to read and maintain, and especially more flexible (e.g., it is easy to extend  Rectangle  class to build a  Square  one).

Static and Non-Static Member Classes

Can you spot a flaw in one of the following implementations?

 Use of static member classes

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

public final K getKey()        { return key; }
public final V getValue()      { return value; }

}
}

 Use of non-static member classes

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

public final K getKey()        { return key; }
public final V getValue()      { return value; }

}

}

A nested class is a class defined within another class; the former should exist only to serve the latter (i.e., the enclosing class). If you declare a member class that doesn’t require access to its enclosing instance then always put  static  modifier in its declaration. If you omit this modifier then each instance will have a hidden extraneous reference to its enclosing instance, and storing this reference takes time and space. More seriously, it can result in the enclosing instance being retained when it would be otherwise be eligible for garbage collection, causing catastrophic memory leak, ouch! This kind of mistake is often difficult to detect because the reference is invisible.

Leave a Reply

%d bloggers like this: