Search code examples
javatype-conversionhashsetautoboxing

Why does .contains(1) return false when called on a HashSet<Long> that contains 1l?


public static void main(String[] args) {
    List<Long> list = Arrays.asList(1l,2l,3l);
    Set<Long> set = new HashSet<>(list);
    System.out.println(set); // [1, 2, 3]
    System.out.println(set.contains(1)); // false
}

The set contains {1,2,3}, but when I call set.contains(1) it returns false. Why isn't 1 auto casted to 1l? I think it may be related to Java autoboxing and casting problem.


Solution

  • Comments have mentioned that because a long is not an int, the set will not contain it, but I believe there is a more concrete answer that can be given to this case specifically.

    If we look into the HashSet source code, we find that its contains method falls back to a HashMap's containsKey method. Looking into that, we find that the fundamental check is this:

    if (e.hash == hash && e.key.equals(key))
    

    A simple check will verify that the two values will produce the same hash, but that the direct check below will fail.

    new Integer(1).equals(new Long(1l));
    

    This happens for the precise reason mentioned in the comments. When you use the Integer (or Long) equals() method, the very first thing that Java will do is check if the types are the same. It will not do any casting. The types are different, and so the result is false. To avoid this, cast your integer to a long before checking if the set contains it.

    Note that this behavior is different from using a primitive-type comparison 1 == 1l. Java will not wrap these in objects and will not use the object .equals() method, so this evaluation is skipped. Casting happens and the result is true.