My previous post (Java Oddities Part I) created a lot of discussion on reddit. People suggested many interesting cases and I would like to describe some of them with additional details.
Thanks to Ben Evans for contributing some further information.
Dangerous Method Overloading
// credit to choychoy List<Integer> list = new ArrayList(Arrays.asList(1,2,3)); int v = 1; list.remove(v); System.out.println(list); // prints [1, 3] List<Integer> list = new ArrayList (Arrays.asList(1,2,3)); Integer v = 1; list.remove(v); System.out.println(list); // prints [2, 3]
The java.util.List interface describes two methods named remove.
The first one is remove(int). It removes an element from the list based on its index, which is represented by a value of type int (note: an index starts at 0). The second one is remove(Object). It removes the first occurrence of the object passed as argument.
This is referred to as method overloading: the same method name is used for describing two different operations. The choice of the operation is based on the types of the method parameters. In academic terminology we will say that it is an example of ad-hoc polymorphism.
So what happens in the piece of code above? The first case is straightforward as we pass a variable of type int and there's a signature for remove which expects exactly an int. This is why the element at index 1 is removed.
In the second case, we pass an argument of type Integer. Since there is no signature for remove that directly takes an Integer parameter, Java tries to find the closest matching signature. The Java Language Specification (Determine Method Signature) states that resolution based on subtyping comes before allowing boxing/unboxing rules. Since java.lang.Integer is a subtype of java.lang.Object, the method remove(Object) is invoked. This is why, the call remove(v) finds the first Integer containing the value 1 and removes it from the list.
Note that this problem wouldn't exist if the java.util.List interface differentiated the two remove operations with two different method names: removeAtIndex(int) and removeElement(Object). For those interested in getting more views about method overloading, there is a famous paper from Bertrand Meyer on the topic.
Array Initializer Syntax Curiosity
Java just like C and C# allows a trailing comma after the last expression in an array initializer. This is documented in the Java Language Specification (Array Initializer).
However, what if the initializer contains no expression? This is where Java differs from other languages like C and C#:
// Java int a[] = {}; // valid int b[] = {,}; // also valid, an array of length 0 >:o
// C int a[] = {,}; // error: expected expression before ‘,’ token
// C# int a[] = {,}; // Unexpected symbol ','
The Type of a Conditional Expression
// credit to fragglet Object o = true ? 'r' : new Double(1); System.out.println(o); // 114.0 System.out.println(o.getClass()); // class java.lang.Double
This looks a bit odd. The conditional expression is true, so you might expect that the char 'r' would be boxed into java.lang.Char.
How did we end up with java.lang.Double as the runtime type of o? The value 114.0 looks suspicious as well - but we might guess that it's the ASCII value which corresponds to the character 'r'. But why is it ending up in a numeric type?
Let's take a step back, and examine the general question - which is: what should the type of the conditional expression be if the type of the second and third operand are different?
Java has a set of rules to determine this as explained in the Java Language Specification (Conditional Expression).
In this case, the rules say that first of all the third operand is unboxed to the primitive type double. This is specified by the binary numeric promotion rules. After that, a more familiar rule kicks in - the promotion rule for doubles.
This says that if either operand is of type double, the other is converted to double as well. This is why the second operand of type char is widened to a double.
The second and third operand have now the same type and this is the resulting type of the conditional expression - so the expression's type is the primitive type double (and it's value is now the primitive value 114.0). Finally, since we are assigning the result of the conditional expression to a variable of type Object, Java performs assignment conversion. The primitive type double is boxed to the reference type Double (java.lang.Double).
Note that such a mechanism wouldn't be needed for conditional expressions if Java restricted the second and third operands to be strictly of the same type. An alternative option could be union types.