"API design is like sex: make one mistake and support it for the rest of your life"(Josh Bloch on twitter)

There are many design mistakes in the Java library. Stack extends Vector (discussion), and we can't fix that without causing breakage. We can try to deprecate Integer.getInteger (discussion), but it's probably going to stay around forever.

Nonetheless, certain kinds of retrofitting can be done without causing breakage.

There are other methods in Collections that would've been much more pleasant to use if they were to return the input list instead of void, e.g. reverse(List), sort(List), etc. In fact, having Collections.sort and Arrays.sort return void is especially unfortunate, because it deprives us from writing expressive code such as this:

This void return type preventing fluency isn't just restricted to these utility methods, of course. The java.util.BitSet methods could've also been written to return this (ala StringBuffer and StringBuilder) to facilitate fluency.

3 Answers
3

Unfortunately, yes, changing a void method to return something is a breaking change. This change will not affect source code compatibility (i.e. the same Java source code would still compile just like it did before, with absolutely no discernible effect) but it breaks binary compatibility (i.e. bytecodes that was previously compiled against the old API will no longer run).

Here are the relevant excerpts from the Java Language Specification 3rd Edition:

Deleting a method or constructor from a class may break compatibility with any pre-existing binary that referenced this method or constructor; a NoSuchMethodError may be thrown when such a reference from a pre-existing binary is linked. Such an error will occur only if no method with a matching signature and return type is declared in a superclass.

That is, while the return type of a method is ignored by the Java compiler at compile-time during the method resolution process, this information is significant at run-time at the JVM bytecode level.

On bytecode method descriptors

A method's signature does not include the return type, but its bytecode descriptor does.

The most applicable method is chosen at compile time; its descriptor determines what method is actually executed at run time.

If a new method is added to a class, then source code that was compiled with the old definition of the class might not use the new method, even if a recompilation would cause this method to be chosen.

Ideally, source code should be recompiled whenever code that it depends on is changed. However, in an environment where different classes are maintained by different organizations, this is not always feasible.

A little inspection of the bytecode will help clarify this. When javap -c is run on the name shuffling snippet, we see instructions like this:

On non-breaking retrofitting

Changing the direct superclass or the set of direct superinterfaces of a class type will not break compatibility with pre-existing binaries, provided that the total set of superclasses or superinterfaces, respectively, of the class type loses no members.

Retrofitting a new interface does not cause the type to lose any member, hence this does not break binary compatibility. Similarly, due to the fact that varargs is implemented using arrays, this kind of retrofitting also does not break binary compatibility.

Related questions

Is there absolutely no way to do this?

Actually, yes, there is a way to retrofit a return value on previously void methods. We can't have two methods with the same exact signatures at the Java source code level, but we CAN have that at the JVM level, provided that they have different descriptors (due to having different return types).

Thus we can provide a binary for e.g. java.util.BitSet that has methods with both the void and non-void return types simultaneously. We only need to publish the non-void version as the new API. In fact, that's the only thing we can publish at the API, since having two methods with the exact same signature is illegal in Java.

This solution is a terrible hack, requiring special (and spec-defying) processing to compile BitSet.java to BitSet.class, and thus it may not be worth the effort to do so.

If you can't retrofit, you still can wrap your class into a new one which uses the same methods but with correct returns (MyClassFluent).
Or you can add new methods but with different names, instead of Arrays.sort() we could have Arrays.getSorted().

I think that the solution isn't to force things, just deal with them.

EDIT: I know I didn't answer to the "retrofitting of void methods" question, but your answer is already really clear.

Alternatives of course exist, but since some types of retrofitting to improve an API is possible (e.g. can you imagine if making String a Comparable is a breaking change???), I'd like to know where the boundaries are, and how far it can be pushed.
–
polygenelubricantsAug 28 '10 at 8:48

As you said, this kind of retrofitting isn't the "standard one". Making String a Comparable means that we add something, here you want to change something. I'm not even sure that we can call that retrofitting.
–
Colin HebertAug 28 '10 at 8:54

1

LOL I just realized that you're the one who wrote this answer. stackoverflow.com/questions/3550469/… - yes, we 10K-ers can see deleted answers. That answer is the one that finally made me investigate this problem in greater detail. I'm glad that I'm not the only one who wished that Arrays.sort returns the array.
–
polygenelubricantsAug 28 '10 at 9:08

Well, the Arrays.sort() problem happens to me way too often. But I expect a returned result from a static method, not parameters updated. For example if StringBuilderUtils.toUpperCase(StringBuilder sb) existed, I would expect to get a new StringBuilder, not a modified sb and a void return.
–
Colin HebertAug 28 '10 at 13:53

If you have to deal with source compatability only, then just go ahead. Changing from void to a return type will not break.

But to address the thing you actually want to do:
I consider the problem with fluent interfaces is that the lines tend to get rather long and - after formatting - somewhat unreadable. For builders it works fine, but I'd probably not use it for anything else.

Is this for playing with it, or because you've found that this is really great?