Okay, calling them heresies may be a bit overexaggerating. Calling them quirks or gotcha would be more appropriate. So let's dig into some of the Java's most unexpected gotcha.
When 1000 Is NOT Equal to 1000
You saw this kind of post all over LinkedIn, by people who copy/paste and then post to their profile. No charges here, just a funny poking.
TL;DR:
Integer a = 1000;
Integer b = 1000;
// Yes, you don't use this method
// But this is to prove the point
Assertions.assertFalse(a == b); // a == b will return false
Ah, yes, the classic equality by identity versus equality by value. Never ever compare two objects using == if your intention was to compare their values (the exception being enum).
But this will return true:
Integer a = 100;
Integer b = 100;
Assertions.assertTrue(a == b); // True
For Integer, value from -128 to 127 (by default) are cached, so you have this funny heresy gotcha.
And then again, why would you use Integer instead of int?
The Great Type Erasure
The code below will NOT compile:
class Utils {
void doSomething(List<String> strings) {
// Do something
}
void doSomething(List<Integer> ints) {
// Do something
}
}
You will get a nasty warning:
'doSomething(List<String>)' clashes with 'doSomething(List<Integer>)'; both methods have same erasure
That's not your fault, that is how Java becomes too committed to preserve backward compatibility. At runtime, the types are erased, leading to two methods with identical signature doSomething(List), and by the ultimate rule of overloading, this is not acceptable.
Can this ever be fixed? Who knows? Will Valhalla fix this? Probably? Do people use Java version 4 or less? They are now old boomers now.
The original intention of Generics was mainly to turn the nasty ClassCastException hiccups during runtime to errors that can be caught by compiler, so yeah, get used to it.
Unsigned When?
Why didn't Java have unsigned data types? Is there any reason why this was not a thing in Java? Can I have an int value of 4 billion without needing the 8 bytes of long?
String's Questionable Immutability
Read my post here.
The Collection Shenanigans
Look at this code:
Collection<Integer> collection = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
collection.remove(1); // Result: [2, 3, 4, 5] - removes the VALUE 1
list.remove(1); // Result: [1, 3, 4, 5] - removes the element at INDEX 1
Yes, the culprit is due to method overloading. See here:
// From Collection interface
boolean remove(Object o);
// From List interface
E remove(int index);
By the rule of method overloading, primitive types trump over Object ones, so for the List instance, the overloading version of remove(int) is used, leading to this weird case.
The fix? Use one of those:
// Remove the first occurrence of element `1`
list.remove(Integer.valueOf(1)); // Yes, why would you?
// Remove all element `1` in this list
// This is for JDK 8+ only
list.removeIf(e -> Objects.equals(1, e));
Still, Java please, can we not have a removeAt(int index) method, belongs to List interface?
The Hostility Between Primitive Types Versus Generics
Have a look
var ints = new int[] {1, 2, 3, 4, 5, 6};
var list = Arrays.asList(ints);
What do you think the data type of list would be?
Is it List<Integer>?
Nope. It is List<int[]>.
Ah yes, how dare the primitive types throw a hissing fit here. How dare they!
So we have:
An
int[]is anObject, butint[]is NOTInteger[], sorry, no autoboxing here.On the same vein, an
Integer[]is indeed anObject[], which in turn, anObject🤡 Yes, what do you even expect?
So yes, this is also a thing:
void doSomethingObj(Integer... a) {
// Do something
}
void doSomethingInt(int... a) {
// Do something
}
doSomethingObj(new int[] {1, 2}); // Not complile
doSomethingInt(new Integer[] {1, 2}); // Also not compile
You can't have everything, I guess. Primitive types have harbored an intense hatred to everything object it seems.
The Great Profileration of UnsupportedOperationException
That nasty exception is everywhere in Collections API. Yes, it is there to ensure the immutability of the collections. And also yes, it meant stepping out from comfort zone of ArrayList, HashSet and HashMap means walking into an uncharted territory, where every step is a potential trigger to detonate the land mines (except the mines here are the presence of said exception).
The convenience of List.of, Set.of and Map.of? Those things throw UnsupportedOperationException when you try to mutate them. Again, why would you, but there are cases when you genuinely need to mutate your collections.
Do your collections accept null? Some do, others would ruin your day, for example, thread-safe collection like ConcurrentHashMap, or some specialized collections like TreeSet do not tolerate null. Again, working with null is generally frowned upon, but again, there will always be null in data, just like there will always be shadow accompanying someone under a sunny day.
Test your code carefully!
Speaking of UnsupportedOperationException
Take a look at this code:
var array = new Integer[] {1, 2, 3, 4, 5};
var list = Arrays.asList(array);
list.add(6); // Nope, UnsupportedOperationException
list.remove(1); // Also nope, UnsupportedOperationException
list.set(0, -1); // This is okay
So we have some sort of half-hearted immutable collection. Technically, Arrays.asList creates a view over a backing array. The original array cannot shrink or expand, so add and remove are not supported. However, modification is accepted.
But then, you run this code:
// Welcome to JDK 25, folks!
IO.println(Arrays.toString(array));
// Prints -1, 2, 3, 4, 5
The change you made to the list now reflects back to the backing array. The nature of a view, isn't it?
To truly ensure the complete immutability, use List.of in JDK 9+, or Collections.unmodifiableList.
Let me know if you have any other gotcha you wish to share, or an experience when you are the victim of above heresies 😂
Top comments (0)