"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.
Effective Java 2nd Edition, Item 18: Prefer interfaces to abstract classes: Existing classes can be easily retrofitted to implement a new interface".
Examples: String implements CharSequence
, Vector implements List
, etc.
Effective Java 2nd Edition, Item 42: Use varargs judiciously: You can retrofit an existing method that takes an array as its final parameter to take varags instead with no effect on existing clients.
An (in)famous example is Arrays.asList
, which caused confusions (discussion), but not breakage.
This question is about a different kind of retrofitting:
Can you retrofit a void
method to return something without breaking existing code?
My initial hunch points to yes, because:
- Return type doesn't affect which method is chosen at compile time
- See: JLS 15.12.2.11 - Return Type Not Considered
- Thus, changing return type doesn't change which method is chosen by the compiler
- Retrofitting from
void
to return something is legal (but not the other way around!)
- Even when you use reflection, things like
Class.getMethod
doesn't distinguish on return type
However, I'd like to hear a more thorough analysis by others more experienced in Java/API design.
Appendix: The Motivation
As suggested in the title, one motivation is to facilitate fluent interface style programming.
Consider this simple snippet that prints a shuffled list of names:
List<String> names = Arrays.asList("Eenie", "Meenie", "Miny", "Moe");
Collections.shuffle(names);
System.out.println(names);
// prints e.g. [Miny, Moe, Meenie, Eenie]
Had Collections.shuffle(List)
been declared to return the input list, we could have written:
System.out.println(
Collections.shuffle(Arrays.asList("Eenie", "Meenie", "Miny", "Moe"))
);
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:
// DOES NOT COMPILE!!!
// unless Arrays.sort is retrofitted to return the input array
static boolean isAnagram(String s1, String s2) {
return Arrays.equals(
Arrays.sort(s1.toCharArray()),
Arrays.sort(s2.toCharArray())
);
}
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.
// we can write this:
StringBuilder sb = new StringBuilder();
sb.append("this");
sb.append("that");
sb.insert(4, " & ");
System.out.println(sb); // this & that
// but we also have the option to write this:
System.out.println(
new StringBuilder()
.append("this")
.append("that")
.insert(4, " & ")
); // this & that
// we can write this:
BitSet bs1 = new BitSet();
bs1.set(1);
bs1.set(3);
BitSet bs2 = new BitSet();
bs2.flip(5, 8);
bs1.or(bs2);
System.out.println(bs1); // {1, 3, 5, 6, 7}
// but we can't write like this!
// System.out.println(
// new BitSet().set(1).set(3).or(
// new BitSet().flip(5, 8)
// )
// );
Unfortunately, unlike StringBuilder
/StringBuffer
, ALL of BitSet
's mutators return void
.