Overloading and polymorphism

[polymorphism] [overloading]

Polymorphism allows a single method to work with an arbitrary, unknown type, while overloading allows one of multiple methods to be selected by examining the types of the parameters. With overloading, the parameter types become part of the name of the method. With polymorphism, the parameter types might not be known.

The two features are in direct conflict, because the information that overloading requires is unavailable in a polymorphic context. Attempts to combine them are tricky, as this counterexample in Java1 shows:

// Counterexample by Hiromasa Kido
class A{
       public int compareTo(Object o){ return 0; }
}
class B extends A implements Comparable<B>{
       public int compareTo(B b){ return 0; }
       public static void main(String[] argv){
               System.out.println(new B().compareTo(new Object()));
       }
}
// Counterexample by Kota Mizushima
// (translation of Java counterexample, with the same effect)
class A {
  def compareTo(o: Any): Int = 0
}
class B extends A with Comparable[B] {
  def compareTo(b: B): Int = 0
}
object C {
  def main(args: Array[String]): Unit = {
    println(new B().compareTo(new Object()))
  }
}

On earlier versions of Java (and Scala2), this program crashed with a ClassCastException, despite containing no casts. The issue is that in order to implement B's compareTo(B), the compiler inserts a "bridge method" compareTo(Object) containing a cast to B. The bridge method is necessary because the compareTo method is specified by Comparable, and other users of the Comparable interface will select the compareTo(Object) overload, as they do not necessarily know about B. However, this bridge method accidentally overrides A's compareTo(Object) method, and gets called from main instead, and the cast fails.

In current Java, bridge methods still exist, but the program above is rejected.

Scala3 additionally has a related issue, also caused by an interaction of overloading and polymorphism. Scala allows a mixture of structural and nominal typing. An object can be given a structural type that exposes only some of its capabilities, including exposing only special cases of polymorphic methods:

// Counterexample by Paul Phillips
object Test {
  class MyGraph[V <: Any] {
    def addVertex(v: V): Boolean = true
  }

  type DuckGraph = {
    def addVertex(vertex: Int): Boolean
  }
  
  def fail(graph: DuckGraph) = graph addVertex 1

  def main(args: Array[String]): Unit = {
    fail(new MyGraph[Int])
  }
}

However, overloading causes this program to fail, by attempting to find a nonexistent addVertex(Int) method, even though the underlying polymorphic method has signature addVertex(Object).

1

Java generics unsoundness? (TYPES mailing list), Eijiro Sumii (2006)