Remix.run Logo
jmyeet 4 hours ago

I wanted to comment on this as well. The article mentions it but if you've never used Java in anger (is there any other way?) then readers may not understand the true implications of this because it's a breaking change, something Java rarely does. I'll explain for the non-Java people.

Java separates checking identity and equality for objects. == basically checks if two pointers are the same. Equality is a subjective concept based on an interface (ie equals/hashCode). So this means:

    new Integer(1000) == new Integer(1000) // true, used to be false
    new Integer(1000).equals(new Integer(1000)) // true
    new Integer(10) == new Long(10) // compiler error, used to false
    new Integer(10) == new Integer(10) // true
There's a lot going on here. The complication is that in previous versions of Java (and I'm not sure when this changed), integers below a certain value would be replaced with canonical types below a certain value. I think it was 128 but its's been awhile. This led to the difference between 10 and 1000. That's now changed, I suspect because the above comparisons are being implicitly unboxed. That didn't used to happen either. I saw this because the Integer/Long comparison used to return false and it's now a compiler error so there must be unboxing going on.

You may still be able to get the old behavior through variables too.

Anyway, if value classes lose identity then == changes from pointer equality to bitwise equality. That will hopefully resolve a bunch of corner cases like this but it is a breaking change, technically.

papercrane 3 hours ago | parent [-]

    new Integer(10) == new Integer(10) // true
Before value classes this would always be false. The only time comparing Integer objects with == could be true is if Integer object was create by going through Integer.valueOf (or obviously if they were the same object reference.) By default the cached values where -127 to 127, but that is tuneable at runtime.

https://github.com/openjdk/jdk/blob/jdk-27%2B27/src/java.bas...

tsimionescu 3 hours ago | parent | next [-]

It could also be true if the instances were created through auto-boxing (e.g. arrayList.add(10); arrayList.add(10); arrayList.get(0) == array List.get(1) //would return true, but false if you used 1000 instead of 10).

papercrane 2 hours ago | parent [-]

Yes, because auto-boxing is just compiling to Integer.getValue under the hood, the bytecode for Integer.getValue(1) and ((Integer) 1) is the same. They'll both compile to something like:

   iconst_1
   invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer
tsimionescu 2 hours ago | parent [-]

Sure, I was just talking about Java syntax, not the bytecode internals.

jmyeet an hour ago | parent | prev [-]

So you've made my point in showing how complex this is because you're incorrect [1][2]:

> By default, Java maintains a cache of Integer objects for values between -128 and +127.

[1]: https://stackoverflow.com/questions/3130311/weird-integer-bo...

[2]: https://dev.to/marzuk16/understanding-integer-caching-in-jav...

xxs an hour ago | parent [-]

it's easier to remember that it originated from the Byte range, where all bytes could be kept in. Character didn't have negative values so it did [0-128) instead. Long and Short are the same as Byte.

Years before the autoboxing/Integer.valueOf() caching stuff (and before generics), (I) used to have IntegerProvider that did similar stuff to higher ranges. Personally, I have considered autoboxing on integers net-negative for Java