Item 41 - Use overloading judiciously

From Effective Java 2/e by Joshua Bloch

  • The choice of which overloading to invoke is made at compile time
  • Selection among overloaded methods is static, while selection among overridden methods is dynamic.
  • The compile-time type of an object has no effect on which method is executed when an overridden method is invoked; the “most specific” overriding method always gets executed. Compare this to overloading, where the runtime type of an object has no effect on which overloading is executed;

Example shows difference between overloading and overriding

// It prints Unknown Collection three times
public class CollectionClassifier {
   public static String classify(Set<?> s) {
      return "Set";
   }
   public static String classify(List<?> lst) {
      return "List";
   }
   public static String classify(Collection<?> c) {
      return "Unknown Collection";
   }
   public static void main(String[] args) {
      Collection<?>[] collections = {
         new HashSet<String>(),
         new ArrayList<BigInteger>(),
         new HashMap<String, String>().values()
      };
      for (Collection<?> c : collections)
         System.out.println(classify(c));
   }
}
// It prints out wine, sparkling wine, and champagne
class Wine {
   String name() { return "wine"; }
}
class SparklingWine extends Wine {
   @Override String name() { return "sparkling wine"; }
}
class Champagne extends SparklingWine {
   @Override String name() { return "champagne"; }
}
public class Overriding {
   public static void main(String[] args) {
      Wine[] wines = {
         new Wine(), new SparklingWine(), new Champagne()
      };
      for (Wine wine : wines)
         System.out.println(wine.name());
   }
}

Avoid confusing uses of overloading

  • A safe, conservative policy is never to export two overloadings with the same number of parameters
// ObjectOutputStream
void writeBoolean(boolean);
void writeInt(int);
void writeLong(long);

boolean readBoolean();
int readInt();
long readLong();
  • For constructors, you do have the option of exporting static factories instead of constructor
  • When at least one corresponding formal parameter in each pair of overloadings has a “radically different” type in the two overloadings

Confusion by auto-boxing

// It prints out [-2, 0, 2]
public class ListOverloading {
   public static void main(String[] args) {
      List<Integer> list = new ArrayList<Integer>();
      for (int i = -3; i < 3; i++)
         list.add(i);
      for (int i = 0; i < 3; i++)
         list.remove(i); // should be list.remove((Integer) i);
      System.out.println(list);
   }
}

public class List {
   void remove(int);
   void remove(E);
}

There may be times when you feel the need to violate the guidelines in this item, especially when evolving existing classes.

  • The standard way to ensure this behavior is to have the more specific overloading forward to the more general
public boolean contentEquals(StringBuffer sb) {
   return contentEquals((CharSequence) sb);
}

Posted by The Finest Artist