2

So imagine I have two instances of a class:

public class MyClass {
    public void sayHello() {
         System.out.println("Hello");
    }
}

a = new MyClass();
b = new MyClass();

Now I add those to another object, such as:

public class OtherClass {
    private ArrayList<MyClass> myClsList = new ArrayList<>();

    public void add(MyClass obj) {
        myClsList.add(obj);
    }

    public void remove(MyClass obj) {
        // ????
    }
}

c = new OtherClass();

c.add(a);
c.add(b);

Now I want to remove one specific instance e.g

c.remove(a);
  1. Could I just iterate over them and test for equality, I mean this should theoretically work, since the two instances have distinct "internal pointers"?

  2. I guess using a HashMap based approach would be more efficient, but what can I use as an key there (suppose I can't add unique instance ids or something).

EDIT: There is some confusion as to what exactly I'd like to know. The key here is that I'd like to know if there is any way of removing that specific instance from c's ArrayList or whatever Aggregator Object I might use, just by providing the respective object reference. I imagine this could be done by just keeping the ArrayList and testing for equality (although I'm not a 100% sure) but it would be cleaner if it was possible without iterating through the whole list. I'd just like to know if anything of the like is possible in Java. (I know how to workaround it by using additional information but the clue is to just have the respective object reference for filtering/ retrieving purposes.

meow
  • 2,062
  • 2
  • 17
  • 27
  • You could use an unique identifier for your class (e.g. type int) and, use that identifier as an entry for your hash. – Gio Aug 20 '17 at 00:52
  • Is there any specific reason you want to find an unindex appendence? All I see is you append one but I dont see any reason why one could not extend this specific class. Keep in mind Java ***is*** an interpreted language while in practice you can do this you're not giving us a good example. – Xorifelse Aug 20 '17 at 00:53
  • For 2. you can use a `HashSet`, and if you need to keep the order of inserting a `LinkedHashSet`. PS: this will now require `hashcode()`. – Izruo Aug 20 '17 at 00:53
  • @Gio I know but that's what I do not want, as stated in the question. – meow Aug 20 '17 at 00:54
  • What's wrong with using Object.equals? That's basically implementing the unique id equality for you right there. – Mad Physicist Aug 20 '17 at 00:57
  • @meow I cannot find that statement in the question. – Gio Aug 20 '17 at 00:57
  • @Gio (suppose I can't add unique instance ids or something) - I should probably have made it clearer, sorry for that. – meow Aug 20 '17 at 00:58
  • Yes, because in the contrary of you statement, you can add an unique instance id very easily, e.g. keep track of the amount of created instances with a static member variable, and then assign that value as an unique id to a non static member upon instance creation. – Gio Aug 20 '17 at 01:00
  • @Gio of course, but I want to know if it is possible without changing anything like this. – meow Aug 20 '17 at 01:04
  • @MadPhysicist Do you think that works reliably? I mean do two different instances (which are exactly the same) actually NOT pass the test? Further it would be better to use something where I don't have to iterate over all objects to find a specific one. – meow Aug 20 '17 at 01:07
  • @meow. Object.equals guarantees that it considers two instances to be equal only if they occupy the same memory. While the contract does not require it, this means that it usually compares something like the native pointers, which are basically the unique ids you want. If you don't want to iterate through all the elements, ArrayList is off the table. An alternative to HashMap is TreeMap, but then you need to implement Comparable in your data type. – Mad Physicist Aug 20 '17 at 01:53
  • Note that you could just keep the ArrayList and use its `remove` method directly. This would iterate under the hood, but who cares. – Mad Physicist Aug 20 '17 at 01:57
  • Alternatively, you could use a hashtable as-is. Every object in Java has a built in `hashCode`, so I am not 100% sure what is tripping you up here. – Mad Physicist Aug 20 '17 at 01:59
  • Sorry , on mobile. – Mad Physicist Aug 20 '17 at 02:00
  • @Mad Physicist well, I was not 100% sure whether it is really reliable. As far as I could grasp it is not, no matter what I use. Basically it seems to come down to me implementing unique index or trusting the Java internals, which should work in most cases but does not seem to be a 100% safe. Thanks anyways for your input. – meow Aug 20 '17 at 11:24
  • @meow. The Java internals should be considered 100% reliable. I will post an answer referring to the contact for those internals if you'd like. You will be using those same internals to implement your wrapper. Why trust them in one place but not the other? – Mad Physicist Aug 21 '17 at 00:47

2 Answers2

2

You can use a.toString(), according to the Java doc,

The toString method for class Object returns a string consisting of the name of the class of which the object is an instance, the at-sign character `@', and the unsigned hexadecimal representation of the hash code of the object.

This should give you an unique identifier for your class instance, hence you can use this as a hash key without storing / creating any extra identifiers.

NB: Be careful with this practice, don't rely on the the value returned by `Object.toString(), as being related to the actual object addres, see detailed explanation here.

Gio
  • 3,242
  • 1
  • 25
  • 53
  • Thank you, that seems about what I've been looking for. So this means as long as GC did not release the memory for that object, it should be fine and since I store a non-weak reference to the objects, it would mean as long as the OtherClass instance is alive (if not for some strange JVM internals). Going down this road, wouldn't it be safer to use `System.identityHashCode(obj)` as the key? – meow Aug 20 '17 at 01:31
  • Safety wise I don't see any difference, but yes as long as your objects are of the same type, you can also just use hashCode as a key (object instances of different type may share the same hash key). – Gio Aug 20 '17 at 10:00
  • This just adds an unnecessary layer of overhead. – Mad Physicist Aug 21 '17 at 00:48
1

While your question is one that many beginners have (including myself), I believe that your concern is not justified in this case. The features you are asking for are already built into the Java language at the specification level.

First of all, let's look at Object.equals(). On the one hand, the Language Specification states that

The method equals defines a notion of object equality, which is based on value, not reference, comparison.

However, the documentation for Object.equals() clearly states that

The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).

This means that you can safely redirect OtherClass.remove to ArrayList.remove(). Whatever Object.equals is comparing works exactly like a unique ID. In fact, in many (but not all) implementations, it compares the memory addresses to the objects, which are a form of unique ID.

Quite understandably, you do not wish to use linear iteration every time. As it happens, the machinery of Object is perfectly suited for use with something like a HashSet, which, by the way is the solution I recommend you use in this case.

If you are not dealing with some huge data set, we do not need to discuss the optimization of Object.hashCode(). You just need to know that it will implement whatever contract is necessary to work correctly with Object.equals to make HashSet.remove work correctly.

The spec itself only states that

The method hashCode is very useful, together with the method equals, in hashtables such as java.util.Hashmap.

This does not really say much, so we turn to the API reference. The two relevant point are:

  • If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  • It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

Simply put, the hashCode of equal objects must be the same, but an equal hashCode does not necessarily mean equal objects. Object implements this contract, so you can use it with a HashSet, which is backed by a HashMap.

The one piece of information that is missing to make this a formal argument in favor of not doing any additional work, is why I keep citing the API reference as if it was the language specification. As it happens:

As noted above, this specification often refers to classes of the Java SE platform API. In particular, some classes have a special relationship with the Java programming language. Examples include classes such as Object, Class, ClassLoader, String, Thread, and the classes and interfaces in package java.lang.reflect, among others. This specification constrains the behavior of such classes and interfaces, but does not provide a complete specification for them. The reader is referred to the Java SE platform API documentation.

[emphasis mine], but you get the idea. The Java SE API reference is the language spec as far as the behavior of the methods of Object is concerned.

As an aside, you will probably want to stay away from something like TreeSet, because that will require you to add a bunch of machinery to your implementation. As a minimum, MyClass instances will have to be orderable, either by implementing Comparable, or by assigning a custom Comparator to the Set.

TL;DR

The language specification states that you have at least the following two options available to you with no additional effort on your part:

  1. Make myClsList an ArrayList and use the appropriate add()/remove() methods as you see fit.
  2. Make myClsList a HashSet and use the appropriate add()/remove() methods.

I recommend the second option. In fact, instead of containment, you may consider extending HashSet so you don't have to bother implementing your own add/remove methods.

Final Note

All this works as long as MyClass overrides neither Object.equals nor Object.hashCode. The moment you do that, you put the burden of satisfying contractual requirements entirely on yourself.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Thanks a lot for the detailed answer. I see that I'm being a bit paranoid. Especially the memory address point was bugging me, if it was just the current memory address, everything would feel safe but this way I felt really unsure. Well, thanks a lot for the time spent answering me :) – meow Aug 21 '17 at 11:50
  • @meow. No problem. This sort of thing used to bother me to no end as well, so I can relate. – Mad Physicist Aug 21 '17 at 13:11