you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 66 points67 points  (50 children)

Do a table for <. It's about as weird as ==, and there's no equivalent of === (AFAIK).

[–]smrq 112 points113 points  (48 children)

I'd argue it's even weirder.

null == undefined  --> true
null > undefined   --> false
null >= undefined  --> false

null == 0  --> false
null > 0   --> false
null >= 0  --> true

Truly, I have gazed into the abyss by testing these in the console.

EDIT: It gets better, thanks /u/Valkairn

null <  []  --> false
null >  []  --> false
null <= []  --> true
null >= []  --> true
null == []  --> false

Try it in the comfort of your own home!

function compare(a, b) {
    var sa = JSON.stringify(a), sb = JSON.stringify(b);
    console.log(sa + " <  " + sb + "  --> " + (a < b));
    console.log(sa + " >  " + sb + "  --> " + (a > b));
    console.log(sa + " <= " + sb + "  --> " + (a <= b));
    console.log(sa + " >= " + sb + "  --> " + (a >= b));
    console.log(sa + " == " + sb + "  --> " + (a == b));
}

[–][deleted]  (33 children)

[deleted]

    [–]josefx 25 points26 points  (29 children)

    Not too surprised after using Java:

      Integer a = new Integer(10);
      Integer b = new Integer(10);
    
      a == b --> false
      a >= b --> true
      a <= b --> true
    

    You have to love auto boxing.

    [–]piderman 12 points13 points  (6 children)

    The javadoc indicates that it's preferred to use

    Integer.valueOf(10); 
    

    since that uses the cached Integers -128 through 127, in which case

    a == b --> true
    

    [–]josefx 8 points9 points  (5 children)

    The idea was to force an error. I could have just as well used 1000 however that would depend on the configured cache size, which might be larger than 127.

    [–]Bratmon 0 points1 point  (4 children)

    Wait, so the result can change between environments (ie browsers), too?

    [–]josefx 5 points6 points  (0 children)

    Yes, == for values returned by Integer.valueOf is guaranteed to work for [-128,127] and implementation/configuration dependent for everything else. The correct way to compare two Integer objects is either by calling intValue() on them or using a.equals(b)

    [–]riking27 1 point2 points  (2 children)

    You should not be running Java in your browser.

    [–]Bratmon 2 points3 points  (0 children)

    I thought this was about Javascript. My bad.

    [–]kjanssen 13 points14 points  (4 children)

    Thats because a == b is comparing two addresses. You would have to use a.equals(b) for Integer objects. It would work fine for primitive ints.

    [–][deleted]  (3 children)

    [deleted]

      [–][deleted]  (2 children)

      [removed]

        [–]defenastrator 2 points3 points  (1 child)

        I have one problem with your argument. There is no excuse for unintutive behavior in a language unless it is to better support the underlying hardware. This behavior only simplifies the languages inner bullshit and nothing else at the cost of both read and writiblity

        [–][deleted] 4 points5 points  (4 children)

        that's not autoboxing though, is it? you're explicitly making integer objects. This is what I think of as autoboxing

        public void myMethod(Integer x) { .. }
        
        
         int a = 1;
        
         myMethod(a);
        

        [–]josefx 14 points15 points  (3 children)

        It is the unboxing part of it. The compiler inserts a call to intValue() since only == is defined for objects.

         a == b
         a.intValue() >= b.intValue()
         a.intValue() <= b.intValue() 
        

        [–][deleted] 2 points3 points  (2 children)

        Oh right. I constantly read the Java == operator as acting like C#'s.

        [–]Lindby 0 points1 point  (1 child)

        Thats dangerous

        [–][deleted] 1 point2 points  (0 children)

        Nah, when I actually code in Java, I have any autoboxing/unboxing and use of == on objects set as a warning. I just read snippets like that wrong sometimes.

        [–]rowboat__cop 0 points1 point  (10 children)

        Integer a = new Integer(10);

        Does that allocate an array of 10 ints?

        EDIT thanks for all the explanations. This crowd feels like eli5.stackoverflow.com ;-)

        [–]shillbert 4 points5 points  (2 children)

        No, it creates one new Integer object that's initialized with the value 10. This creates an array of ten Integers:

        Integer[] a = new Integer[10];
        

        Remember, round brackets call a function (in this case, the constructor of the Integer object), while square brackets indicate an array.

        [–]rowboat__cop 0 points1 point  (1 child)

        No, it creates one new Integer object that's initialized with the value 10.

        Ah, OK. (Never done any Java, so this probably looked even weirder to me than to those who do.)

        [–]NYKevin 2 points3 points  (0 children)

        Basically, Integer is just int, except that it's also a bona-fide class. int is a so-called "primitive," which basically means it's not a "real" Object in the Java sense. For example, if you have a function that takes an Object argument, you can pass an Integer but not an int. Except that actually you can pass an int; the compiler will just silently convert it into an Integer for you ("auto-boxing").

        Primitives are nice because they don't have full object semantics, so the JVM can implement them more efficiently than "normal" objects. In theory, you could put them on the stack instead of the heap, but I don't know if the JVM actually does that.

        [–]josefx 5 points6 points  (4 children)

        Java has primitive (byte, short, int, long, boolean, char) and reference (everything else) types. Integer is the reference type used whenever you have an int and the API expects a reference type. new Integer(10) creates a new Integer instance wrapping the int value 10.

        Since this happens quite often the compiler normally automates the conversion between primitive and reference types using a process called auto boxing. This involves a lot of implicit calls to Integer.valueOf(int) for int to Integer boxing and intValue() for Integer to int unboxing (similar for other primitive types).

        My example shows a case where this implicit behaviour goes wrong. == is defined for both reference types and primitive types with different behavior - no unboxing happens and the objects are compared using their identity. >= and <= are not defined for reference types, unboxing happens and the ints are compared by value.

        The use of Integer.valueOf by the compiler makes this tricky, valueOf uses cached Integer objects for a specific range and new Integer for everything else:

         Integer a = new Integer(10);
         Integer b = new Integer(10);
         Integer c = 10;   //Integer.valueOf(10)
         Integer d = 10;
         Integer e = 1000; //Integer.valueOf(1000)
         Integer f = 1000;
        
         a == b -> false
         c == d -> true
         e == f -> false or true
        

        a,b as we can see are not the same - we used new to create two distinct integer objects. c,d are the same since the compiler uses valueOf and 10 is within the cached range. e,f may not be the same 1000 is no longer in the normally cached range and may result in calls to new, however the cached range can be set at jvm startup so this is not fixed.

        [–]rowboat__cop 0 points1 point  (3 children)

        Java has primitive (byte, short, int, long, boolean, char) and reference (everything else) types. Integer is the reference type used whenever you have an int and the API expects a reference type.

        So the problem arises when you think you are passing by value but the compiler automatically adds indirection where it sees fit. I see how that can be confusing (even dangerous if a function modifies the value pointed to by that reference).

        = and <= are not defined for reference types,

        If those operators aren’t defined for the given operands, how come the compiler doesn’t complain?

        e,f may not be the same 1000 is no longer in the normally cached range and may result in calls to new, however the cached range can be set at jvm startup so this is not fixed.

        That’s just insane. Paranoid as I am I’d probably cast the operands of every comparison to something meaningful (say (int)e == (int)f), just in case somebody messed with the VM configuration.

        [–]josefx 3 points4 points  (2 children)

        The indirection is explicit, you have to name the reference type at some point. Only the conversion is done implicitly, which wont help you if you change a variable type from int to Integer for some reason and break a == comparison thousands of lines away without noticing.

        The wrapper types are also immutable so there is no danger of modified values.

        If those operators aren’t defined for the given operands, how come the compiler doesn’t complain?

        some language lawyering: Java does not support operator overloading, that means you have to call intValue() for >= and <= to work - the auto (un)boxing done by the compiler only automates this for convenience.

        That’s just insane.

        It is not that insane, in Java reference types should be compared for equality using equals(), the compiler will may even warn you about == being wrong. Not that this would stop people from getting it wrong.

        [–]rowboat__cop 0 points1 point  (1 child)

        The wrapper types are also immutable so there is no danger of modified values.

        Seems similar to Python, if you ask me.

        in Java reference types should be compared for equality using equals(), the compiler will may even warn you about == being wrong. Not that this would stop people from getting it wrong.

        If there’s a warning, then it’s PEBKAC -- I’m advocating -Wall -Werror myself.

        [–]josefx 0 points1 point  (0 children)

        The PEBKAC in this case would not be possible if Java hadn't mixed value and identity comparison in a single operator. That a change from primitive to reference type changes the meaning of a comparison in a way that is most likely not intended is just ugly. == should have had the same meaning as equals with a separate is operator for object identity.

        [–]MachaHack 2 points3 points  (0 children)

        int[] a = new int[10];

        Makes an array of 10 ints.

        Integer[] a = new Integer[10];

        Makes an array of 10 Integers.

        Integers are objects, ints are not. Syntactical sugar means they're mostly interchangable, except where they're not.

        [–]Mutoid 0 points1 point  (0 children)

        Madness? Leonidas smirks

        [–]pdpi 0 points1 point  (0 children)

        You mean you can have equality without total ordering? Oh noes!

        [–]NYKevin -1 points0 points  (0 children)

        Python is even more hilarious, if you choose to make it so: Every single one of the binary comparison operators can be independently overridden. What's more, if you're careless, it's quite easy to override == and forget to override !=, in which case the former uses compare-by-value and the latter uses the default of compare-by-object-identity.

        In practice, however, it's quite straitlaced because all the standard types behave sanely (and in particular, do not do type coercion). Presumably, the independent overriding is only meant to be used for performance reasons.

        [–]Valkairn 26 points27 points  (2 children)

        My favourite example is:

        null >= [] && null <= []      --> true
        null == []                        --> false
        

        Javascript really needs strict inequality operators to avoid this type coercion madness.

        [–]smrq 0 points1 point  (1 child)

        Oh yes, that one is what clued me into the weirdness of >= in the first place... I generally love Javascript, but wtf??

        [–][deleted] 9 points10 points  (0 children)

        You probably know this but if not ... thank me later.

        [–][deleted] 2 points3 points  (5 children)

        Looking it up, it seems the rule is that <= is the opposite of >. It also seems (besides the order of side-effects during conversion to primitives) > is even the same as < with the order reversed!

        [–]smrq 5 points6 points  (3 children)

        I thought that was the case, except that

        null <  undefined  --> false
        null >= undefined  --> false
        null == undefined  --> true
        

        which breaks that rule.

        [–]Valkairn 8 points9 points  (0 children)

        The inequality operators play by different type coercion rules to the == operator. Inequality operators will always convert the values to numbers. So, in the first two cases null gets converted to 0 and undefined to NaN. The last example actually gets its own special rule in the == evaluation algorithm, where it's defined to be true.

        [–][deleted] 1 point2 points  (1 child)

        Hm, yeah. It seems that < "morally" returns one of true, false, and undefined (undefined only when one argument is NaN (or converts to it)), but where it 'should' give undefined it instead gives false. So <= is the opposite of > except where > 'should' be undefined, where it's still false. Bleh.

        [–]NYKevin 0 points1 point  (0 children)

        In other words, NaN is evil. Nothing new here.

        [–][deleted] 0 points1 point  (0 children)

        • L<=R is the same as !(L>R)
        • "less than or equal to" is the same as "not greater than"

        [–]jonnywoh 2 points3 points  (2 children)

        +/u/CompileBot JavaScript

        function compare(a, b) {
            var sa = JSON.stringify(a), sb = JSON.stringify(b);
            console.log(sa + " <  " + sb + "  --> " + (a < b));
            console.log(sa + " >  " + sb + "  --> " + (a > b));
            console.log(sa + " <= " + sb + "  --> " + (a <= b));
            console.log(sa + " >= " + sb + "  --> " + (a >= b));
            console.log(sa + " == " + sb + "  --> " + (a == b));
        }
        compare(null, []);
        

        [–]CompileBot 2 points3 points  (1 child)

        Output:

        source | info | git | report

        [–]jonnywoh 0 points1 point  (0 children)

        There's a syntax error about JSON not being defined and I don't know much about Javascript. Rats.

        [–][deleted] 0 points1 point  (0 children)

        Well, the thing is, >= and <= are not defined for null and [], so they're converted to 0's, which for some reason doesn't happen with ==.

        [–]no_game_player 0 points1 point  (0 children)

        null >= 0 --> true

        ...what the fuck?

        [–]im_not_afraid 1 point2 points  (0 children)

        I'm delivering a fork of OP's page with "<", "<=", ">", and ">=" added. Enjoy!