Search code examples
javabindingparametersconstructorbinary-compatibility

Changing a constructor param type breaks class in another jar


I have the following class in a common jar:

public class Common 
{
  public Common(List list)
  {
    ...  
  }  
}

I then change the constructor parameter from a List to a Collection as follows:

public class Common 
{
  public Common(Collection collection)
  {
    ...
  }
}

Rebuilding the common jar and running the system causes a NoSuchMethodError in any dependent class when it invokes the constructor until I recompile that class.

I've got a few ideas what's causing this, along the lines of how the constructor is bound in the bytecode of the dependent class, but I'm not 100% sure.

Please can somebody shed some light onto what's going on here?

Update

I've subsequently done a quick test and taken a look at the bytecode:

Compiled from "Client.java"
public class Client extends java.lang.Object{
public Client();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   new #2; //class ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   new #4; //class Common
   11:  dup
   12:  aload_1
   13:  invokespecial   #5; //Method Common."<init>":(Ljava/util/List;)V
   16:  pop
   17:  return

}

Like Tom said, and as you can see on line 13, the exact constructor is bound at compile time.

You learn something new every day :-)


Solution

  • javac resolves exactly which method or constructor to call at compile time. This does not occur at link time. As the constructor signature has changed, the linking step cannot find the requested method and therefore throws an error. You can fix the error by providing to constructors - one which takes an Collection the other List. Later a constructor taking an Iterable could be added.

    Note, generic types do not form part of the signature, so those can be changed whilst still keeping binary compatibility. Both parameter and return types form part of the signature for methods (covariant returns cause synthetic bridge methods to be created).

    There is a nice big section in the JLS defining exactly what constitutes binary compatible changes.