Overloading by return type

It is known that one of the limitations of Java is the inability to overload a method by return type as the return type is not part of the signature of the method.
According to https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.2,

“Two methods or constructors, M and N, have the same signature if they have the same name, the same type parameters (if any) (§8.4.4), and, after adapting the formal parameter types of N to the the type parameters of M, the same formal parameter types.”

 

In other words, a class like this would be rejected by the compiler:

public class MyClass {

    public static Integer print(){
        System.out.println("hello");
        return null;
    }

    public static String print(){
        System.out.println("hello,Integer");
        return null;
    }
}
javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/overloading/MyClass.java
src/main/java/fjab/overloading/MyClass.java:13: error: method print() is already defined in class MyClass
    public static String print(){
                         ^
1 error

 

 

The reasoning for this behaviour is that when calling a method, the return type is not specified as part of the invocation and therefore there is no way for the compiler to know which method to call.

However, when the class is compiled to bytecode, the return type if part of the descriptor of the method and therefore the JVM allows to overload by return type.

The rest of this post will illustrate this behaviour with an example.

 

 

Example

Let’s consider these classes

public class MyClass {

    public static Integer print(){
        System.out.println("hello");
        return null;
    }
}

public class MyApp {

    public static void main(String[] args){
        MyClass.print();
    }
}

 

 

After compiling and running MyApp, everything looks ok.

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/overloading/*.java
java -cp myclasses/ fjab.overloading.MyApp
hello

 

 

Let’s change the return type of MyClass.print. As the return type is not part of the method signature, this change should not have any effect

public class MyClass {
    public static String print(){
        System.out.println("hello");
        return null;
    }
}

 

 

After compiling MyClass and running MyApp

javac -cp myclasses/ -g -d myclasses/ src/main/java/fjab/overloading/MyClass.java
java -cp myclasses/ fjab.overloading.MyApp
Exception in thread "main" java.lang.NoSuchMethodError: fjab.overloading.MyClass.print()Ljava/lang/Integer;
at fjab.overloading.MyApp.main(MyApp.java:10)

 

 

What??? After all, the return type is taken into consideration by the JVM. As far as the JVM is concerned, the return type is part of the method signature.

So, would it be possible to overload two methods by return type in the JVM? Using the ASM framework, we will try to do so.

At the moment, MyClass.class contains only the constructor and a static method:

javap myclasses/fjab/overloading/MyClass.class
Compiled from "MyClass.java"
public class fjab.overloading.MyClass {
  public fjab.overloading.MyClass();
  public static java.lang.String print();
}

 

 

After running the program to modify the bytecode (https://github.com/fjab76/asm/blob/master/src/main/java/fjab/adapter/overloading/DuplicateMethodAdapter.java), a new static method print() is added with the same signature than the existing one except the return type

javap myclasses/fjab/overloading/MyClass.class
Compiled from "MyClass.java"
public class fjab.overloading.MyClass {
  public fjab.overloading.MyClass();
  public static java.lang.String print();
  public static java.lang.Integer print();
}

 

 

This class is completely functional as it can be shown by running MyApp again

java -cp myclasses/ fjab.overloading.MyApp
hello

 

 

Examining the classes more in detail, it is clear that both print() methods are identical but for the return type:

javap -v  myclasses/fjab/overloading/MyClass.class

public static java.lang.String print();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: aconst_null
         9: areturn
      LineNumberTable:
        line 9: 0
        line 10: 8

  public static java.lang.Integer print();
    descriptor: ()Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #34                 // String hello,Integer
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: aconst_null
         9: areturn

 

 

Interestingly, Integer print() does not have LineNumberTable as it does not exist in the source file!! LineNumberTable matches the lines in the source file to the bytecode instructions. This information is useful for the debuggers.

This bytecode represents the class at the beginning of this post. What the java compiler would not allow has been accomplished manipulating the bytecode.

As to MyApp, the bytecode indicates that it is invoking the method print() that returns String

javap -v  myclasses/fjab/overloading/MyApp.class

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=1
         0: invokestatic  #2                  // Method fjab/overloading/MyClass.print:()Ljava/lang/String;
         3: pop
         4: return
      LineNumberTable:
        line 10: 0
        line 11: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  args   [Ljava/lang/String;

 

 

MyApp can even be compiled again without any problem, the compiler keeps linking the call MyClass.print() to the method returning String.

javac -g -cp myclasses/ -d myclasses/ src/main/java/fjab/overloading/MyApp.java
java -cp myclasses/ fjab.overloading.MyApp
hello

 

 

What if we manipulate the bytecode to add the method that returns Integer before the one returning String? Using https://github.com/fjab76/asm/blob/master/src/main/java/fjab/adapter/overloading/PrependDuplicateMethodAdapter.java it is possible to do so.

javap -v  myclasses/fjab/overloading/MyClass.class

public static java.lang.Integer print();
    descriptor: ()Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #34                 // String hello,Integer
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: aconst_null
         9: areturn

  public static java.lang.String print();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: aconst_null
         9: areturn
      LineNumberTable:
        line 9: 0
        line 10: 8

 

 

In this case, the compiler links the call MyClass.print() to the method returning Integer. So the compiler seems to choose the first suitable method found in the target class.

javac -g -cp myclasses/ -d myclasses/ src/main/java/fjab/overloading/MyApp.java
java -cp myclasses/ fjab.overloading.MyApp
hello,Integer

 

 


 

Technical details

This examples have been run on OS X 10.11.1 using:

java -version
java version “1.8.0_45”
Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

javac -version
javac 1.8.0_45

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.