32

I stumbled across the source of AtomicInteger and realized that

new AtomicInteger(0).equals(new AtomicInteger(0))

evaluates to false.

Why is this? Is it some "defensive" design choice related to concurrency issues? If so, what could go wrong if it was implemented differently?

(I do realize I could use get and == instead.)

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • 2
    I wonder what the chances are of Doug Lea turning up and giving us an answer. – Gareth Davis Sep 27 '11 at 10:34
  • 3
    Pretty slim :-) I met him at a summer school recently and he started by saying that *"I don't bother looking at things that doesn't affect millions of people"* :-) – aioobe Sep 27 '11 at 10:40
  • @aioobe: Good one, but i also tried with `==` but it doesn't equal as we are checking references not the actual values and its but obvious that object's references will be different in this case `if(new AtomicInteger(0)==(new AtomicInteger(0))){System.out.println("equals");} `doesn't prints equals. – Anand Kadhi Dec 30 '15 at 06:23

9 Answers9

30

This is partly because an AtomicInteger is not a general purpose replacement for an Integer.

The java.util.concurrent.atomic package summary states:

Atomic classes are not general purpose replacements for java.lang.Integer and related classes. They do not define methods such as hashCode and compareTo. (Because atomic variables are expected to be mutated, they are poor choices for hash table keys.)

hashCode is not implemented, and so is the case with equals. This is in part due to a far larger rationale that is discussed in the mailing list archives, on whether AtomicInteger should extend Number or not.

One of the reasons why an AtomicXXX class is not a drop-in replacement for a primitive, and that it does not implement the Comparable interface, is because it is pointless to compare two instances of an AtomicXXX class in most scenarios. If two threads could access and mutate the value of an AtomicInteger, then the comparison result is invalid before you use the result, if a thread mutates the value of an AtomicInteger. The same rationale holds good for the equals method - the result for an equality test (that depends on the value of the AtomicInteger) is only valid before a thread mutates one of the AtomicIntegers in question.

aioobe
  • 413,195
  • 112
  • 811
  • 826
Vineet Reynolds
  • 76,006
  • 17
  • 150
  • 174
11

On the face of it, it seems like a simple omission but it maybe it does make some sense to actually just use the idenity equals provided by Object.equals

For instance:

AtomicInteger a = new AtomicInteger(0)
AtomicInteger b = new AtomicInteger(0)

assert a.equals(b)

seems reasonable, but b isn't really a, it is designed to be a mutable holder for a value and therefore can't really replace a in a program.

also:

assert a.equals(b)
assert a.hashCode() == b.hashCode()

should work but what if b's value changes in between.

If this is the reason it's a shame it wasn't documented in the source for AtomicInteger.

As an aside: A nice feature might also have been to allow AtomicInteger to be equal to an Integer.

AtomicInteger a = new AtomicInteger(25);

if( a.equals(25) ){
    // woot
}

trouble it would mean that in order to be reflexive in this case Integer would have to accept AtomicInteger in it's equals too.

Gareth Davis
  • 27,701
  • 12
  • 73
  • 106
  • 2
    I see your point (and it's a good one :-) Other mutable classes such as `ArrayList` do however implement `equals`. – aioobe Sep 27 '11 at 10:30
  • I think this is the key - AtomicInteger is explicitly mutable, so it makes more sense to force the developer to explicitly compare the held value rather than the object as a whole. Makes sense to me, anyway. – mcfinnigan Sep 27 '11 at 10:34
  • @aioobe - ArrayList.equals() will return true when two references refer to the same object. In this case, the OP's desired behaviour was to have .equals() return true when the content of the referenced objects was identical, regardless of whether the objects were in fact the same - I believe the designers might not have thought this behaviour was safe. – mcfinnigan Sep 27 '11 at 10:35
  • 2
    I don't quite grasp that last comment. Look at AbstractList's implementation of equals(). It iterates over both lists comparing element by element using their equals method. Yet ArrayList is just as mutable as AtomicInteger. I think it's a trade-off. The convenience of having AtomicInteger.equals() does not outweigh the problems that would arise from it. But for Lists, it's the other way around. – waxwing Sep 27 '11 at 14:11
  • It doesn't make sense that mutability (ie time) should be a factor in whether or not two objects are equal. When you compare two objects, you obviously want to know if they are equal at that moment, not forever :) – GreenieMeanie Sep 27 '11 at 18:10
  • `Other mutable classes such as ArrayList do however implement equals.` Yes but they presume access from a single thread: a correct program calling `arrlist.equals(another)` either is single-threaded, or acquires both the `arrlist` and `another` locks before performing the comparison. – deprecated Jan 04 '13 at 01:49
  • @GreenieMeanie: Arguably, the only definition of equality which is truly applicable to all objects is whether they should be considered equivalent, and equivalence is fundamentally an immutable relationship (two things have the same "value" now, but might not in future, are not equivalent). It might be useful to have an interface to compare the transitory values of things, to facilitate the creation of immutable wrappers which would encapsulate instances of mutable `T` which were promised not to change, and would override `equals` to compare the transitory state of the encapsulated object; ... – supercat Jan 07 '13 at 19:58
  • Under such an approach, `ArrayList` would implement `equals` to mean reference equality, but one could have an `UnchangingArrayList` whose `Equals` method would compare the sequences of encapsulated `T`s. – supercat Jan 07 '13 at 20:01
3

I would argue that because the point of an AtomicInteger is that operations can be done atomically, it would be be hard to ensure that the two values are compared atomically, and because AtomicIntegers are generally counters, you'd get some odd behaviour.

So without ensuring that the equals method is synchronised you wouldn't be sure that the value of the atomic integer hasn't changed by the time equals returns. However, as the whole point of an atomic integer is not to use synchronisation, you'd end up with little benefit.

beny23
  • 34,390
  • 5
  • 82
  • 85
  • 1
    This is surely the answer. There is simply no way to implement a value-comparing `AtomicInteger.equals` that is threadsafe, and a non-threadsafe implementation would be a pretty weird thing to put on a class in java.util.concurrent! – Tom Anderson Sep 27 '11 at 17:35
3

I suspect that comparing the values is a no-go since there's no way to do it atomically in a portable fashion (without locks, that is).

And if there's no atomicity then the variables could compare equal even they never contained the same value at the same time (e.g. if a changed from 0 to 1 at exactly the same time as b changed from 1 to 0).

NPE
  • 486,780
  • 108
  • 951
  • 1,012
1

equals is correctly implemented: an AtomicInteger instance can only equal itself, as only that very same instance will provably store the same sequence of values over time.

Please recall that Atomic* classes act as reference types (just like java.lang.ref.*), meant to wrap an actual, "useful" value. Unlike it is the case in functional languages (see e.g. Clojure's Atom or Haskell's IORef), the distinction between references and values is rather blurry in Java (blame mutability), but it is still there.

Considering the current wrapped value of an Atomic class as the criterion for equality is quite clearly a misconception, as it would imply that new AtomicInteger(1).equals(1).

deprecated
  • 5,142
  • 3
  • 41
  • 62
1

One limitation with Java is that there is no means of distinguishing a mutable-class instance which can and will be mutated, from a mutable-class instance which will never be exposed to anything that might mutate it(*). References to things of the former type should only be considered equal if they refer to the same object, while references to things of the latter type should often be considered equal if the refer to objects with equivalent state. Because Java only allows one override of the virtual equals(object) method, designers of mutable classes have to guess whether enough instances will meet the latter pattern (i.e. be held in such a way that they'll never be mutated) to justify having equals() and hashCode() behave in a fashion suitable for such usage.

In the case of something like Date, there are a lot of classes which encapsulate a reference to a Date that is never going to be modified, and which want to have their own equivalence relation incorporate the value-equivalence of the encapsulated Date. As such, it makes sense for Date to override equals and hashCode to test value equivalence. On the other hand, holding a reference to an AtomicInteger that is never going to be modified would be silly, since the whole purpose of that type centers around mutability. An AtomicInteger instance which is never going to be mutated may, for all practical purposes, simply be an Integer.

(*) Any requirement that a particular instance never mutate is only binding as long as either (1) information about its identity hash value exists somewhere, or (2) more than one reference to the object exists somewhere in the universe. If neither condition applies to the instance referred to by Foo, replacing Foo with a reference to a clone of Foo would have no observable effect. Consequently, one would be able to mutate the instance without violating a requirement that it "never mutate" by pretending to replace Foo with a clone and mutating the "clone".

supercat
  • 77,689
  • 9
  • 166
  • 211
  • I like this answer, even though it's a bit "extreme". I wouldn't go as far as saying "*mutable-class instance which can and will be mutated [...] should only be considered equal if they refer to the same object,*". If the objects are shared among multiple threads or used as, say, keys in a hash map I would probably agree with what you're saying. I can however imagine many single threaded scenarios where comparing mutable objects using `equals` makes sense. – aioobe Jul 25 '13 at 22:08
  • @aioobe: I think it's helpful to, as much as possible, categorize objects as either representing entities or values. Objects representing entities are only equal to themselves, while objects representing values are equal to other objects which represent the same values. Conceptually, values should be immutable--adding 1 to the value 4 generates a new value (i.e. 5) but does not change the meaning of the value 4 (which will always continue to mean (1+1+1+1). Unfortunately, in Java, it would often be impractical to create a new object every time one creates a new value, ... – supercat Jul 25 '13 at 22:31
  • ...and so some types like `Date` can be use three ways: (1) Create a privately-held instance of `Date` and use it to hold a date value; change the date one holds by changing that instance; (2) Use a reference to an instance of `Date` which all holders agree not to mutate to hold a date value; change the date value by storing a reference to a different `Date` that holds the desired value; (3) Use a `Date` as an entity. In usage pattern #1, having `equals()` compare values might be reasonable, but only if one fudges on "privately held" enough to make the reference available for comparison. – supercat Jul 25 '13 at 22:41
  • @aioobe: I added a footnote about "never". Let me know what you think. In any case, `AtomicIntegers` only make sense as entities, and as such reference comparison is appropriate. – supercat Jul 25 '13 at 23:28
1

AtomicInteger inherits from Object and not Integer, and it uses standard reference equality check.

If you google you will find this discussion of this exact case.

jornb87
  • 1,441
  • 10
  • 15
1

Imagine if equals was overriden and you put it in a HashMap and then you change the value. Bad things will happen:)

Petar Minchev
  • 46,889
  • 11
  • 103
  • 119
  • 4
    That argument holds for all mutable classes. And there are plenty of them in the standard API that implements equals. – aioobe Sep 27 '11 at 10:31
  • 1
    @aioobe - Yes, but maybe for many people `AtomicInteger` is like `Integer`. I can put an `Integer` in a map, so why don't I put also an `AtomicInteger`... It is dangerous. – Petar Minchev Sep 27 '11 at 10:33
  • `That argument holds for all mutable classes.` Only if you treat reference types and value types as the same thing. – deprecated Jan 04 '13 at 01:55
1

equals is not only used for equality but also to meet its contract with hashCode, i.e. in hash collections. The only safe approach for hash collections is for mutable object not to be dependant on their contents. i.e. for mutable keys a HashMap is the same as using an IdentityMap. This way the hashCode and whether two objects are equal does not change when the keys content changes.

So new StringBuilder().equals(new StringBuilder()) is also false.

To compare the contents of two AtomicInteger, you need ai.get() == ai2.get() or ai.intValue() == ai2.intValue()

Lets say that you had a mutable key where the hashCode and equals changed based on the contents.

static class BadKey {
    int num;
    @Override
    public int hashCode() {
        return num;
    }

    @Override
    public boolean equals(Object obj) {
        return obj instanceof BadKey && num == ((BadKey) obj).num;
    }

    @Override
    public String toString() {
        return "Bad Key "+num;
    }
}

public static void main(String... args) {
    Map<BadKey, Integer> map = new LinkedHashMap<BadKey, Integer>();
    for(int i=0;i<10;i++) {
        BadKey bk1 = new BadKey();
        bk1.num = i;
        map.put(bk1, i);
        bk1.num = 0;
    }
    System.out.println(map);
}

prints

{Bad Key 0=0, Bad Key 0=1, Bad Key 0=2, Bad Key 0=3, Bad Key 0=4, Bad Key 0=5, Bad Key 0=6, Bad Key 0=7, Bad Key 0=8, Bad Key 0=9}

As you can see we now have 10 keys, all equal and with the same hashCode!

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Basically you're saying "AtomicInteger does not implement equals because it doesn't work well with hash-based collections". Fine. Other mutable classes in the Java library *do* however implement equals. What differentiates these from AtomicInteger and StringBuilder? – aioobe Sep 27 '11 at 11:38
  • Can you give me some examples of mutable classes which implement equals as none come to mind at the moment? – Peter Lawrey Sep 27 '11 at 11:40
  • Apart from classes like `Date` which is generally considered to be poorly designed. ;) – Peter Lawrey Sep 27 '11 at 11:42
  • There are some mutable classes where hashCode and equals does not depend on mutable fields. e.g. Field and Method. – Peter Lawrey Sep 27 '11 at 11:43
  • ByteBuffer and similar classes do implement hashCode and equals. Perhaps the reason this does is because the class hierarchy is complicated enough without adding truly immutable variant types as well. – Peter Lawrey Sep 27 '11 at 11:47