Object Class - 2020
All classes extend the Object class, either directly or indirectly. A class declaration without the extends clause implicitly extends the Object class. Thus, the Object class is always at the root of any inheritance hierarchy. The Object class defines the basic functionality that all objects exhibit and all classes inherit.
The Object class has the following methods.
- boolean equals (Object obj)
Decides whether two objects are meaningfully equivalent. - void finalize()
Called by garbage collector when the garbage collector sees that the object cannot be referenced.
The equals() method in the Object class returns true only if the two references compared denote the same object. The equals() method is usually overridden to provide the semantics of object value equality, as is the case for the wrapper classes and the String class. - final void notify()
Wakes up a thread that is waiting for this object's lock. - final void notifyAll()
Wakes up all threads that are waiting for this object's lock. - final void wait()
Causes the current thread to wait until another thread calls notify() or notifyAll() on this object. - final Class getClass()
Returns the runtime class of the object, which is represented by an object of the class java.lang.Class at runtime. - protected Object clone()
New objects that are exactly the same as the current object can be created by using this method, i.e., primitive values and reference values are copied. This is called shallow copying. A class can override this method to provide its own notion of cloning. For example, cloning a composite object by recursively cloning the constituent objects is called deep copying.
When overridden, the method in the subclass is usually declared public to allow any client to clone objects of the class. If the overriding clone() method is the subclass relies on the clone() method in the object class, the subclass must implement the Cloneable market interface to indicate that its objects can be safely cloned. Otherwise, the clone() method in the Object class will throw a checked CloneNotSupportedException. - hashCode()
Returns a hashcode int value for an object, so that the object can be used in Collection classes that use hashing, including Hashtable, HashMap, and HashSet. - toString()
If a subclass does not override this method, it returns a "text representation" of the object, which has the following format:<name of the class>@<hash code value of object >
Since the default hash value of an object is its memory address, this value is printed as a hexadecimal number. This method is usually overridden and used for debugging purpose. The method callSystem.out.println(objRef)
will implicitly convert its argument to a textual representation by calling the toString() method on the argument.
Comparing two object references refer to the same object using == operator evaluates to true only when both references refer to the same object because == simply looks at the bits in the variable. What those bits represent doesn't matter. The bits are either identical or they're not.
We can have overridden the equals() method inherited from class Object so that we could compare two different objects of the same type to see if their contents are meaningfully equivalent. If two different Integer instances both hold the int value 24, as far as we're concerned they are equal. The fact that the value 24 lives in two separate objects doesn't matter.
- Reference equality
Two references, one object on the heap
Two references that refer to the same object on the heap are equal. If we call the hashCode() method on both references, we'll get the same result. If we don't override the hashCode() method, the default behavior is that each object will get a unique number since Java assign a hashcode based on the object's memory address on the heap and no two objects will have the same hashcode.
If we want to know if two references are really referring to the same object, we should use == operator, which compares the bits in the variable. - Object equality
Two references, two objects on the heap, but the objects are considered meaningfully equivalent
If we want to treat two different objects as equal, we must override both the hashCode() and equals() methods inherited from class object.
If we don't override hashCode(), the default behavior is to give each object a unique hashcode value. So, we must override hashCode() to be sure that two equivalent objects return the same hashcode. But we must also override equals() method so that if we call it on either object, passing in the other object, always returns true.
If we don't override equals() method, we won't be able to use those objects as a key in a hashtable. The equals() method in Object class uses only the == operator for comparisons, so unless we override equals(), two object are considered equal only if the two references refer to the same object.
What does it mean that we are not able to use an object as a hashtable key?
Imagine we you have a car, Hyundai Genesis that you want to put in a HashMap, so that you can search on a particular car and retrieve the corresponding Person object that represents the owner. So you add the car instance as the key to the HashMap along with a corresponding Person object as the value. But now what happens when you want to do a search? You want to say to the HashMap collection," Here's the car, now give me the Person object that goes with this car." But now you're in trouble unless you still have a reference to the exact object you used as the key when you added it to the Collection. In other words, you can't make an identical Car object and use it for the search.
If you want objects of your class to be used as keys for a hashtable, then you must override equals() method so that two different instances can be considered the same. So, how would we fix the car?
We might override the equals() method so that it compares the unique VIN as the basis of comparison. That way, you can use one instance when you add it to a Collection, and essentially re-create an identical instance when you want to do a search based on that object as the key.
The String and wrapper classes work well as keys in hashtables and they override the equals() method. So rather than using the actual car instance as the key into the car/owner pair, we could simply use a String that represents the unique identifier for the car. That way, we'll never have more than one instance representing a specific car, but we can still use the car as the search key.
public class TestingEquals { public static void main(String[] args) { Genesis mozart = new Genesis("Coupe"); Genesis beethoven = new Genesis("Coupe"); if (mozart.equals(beethoven)) { System.out.println("mozart and beethoven are equal"); } } } class Genesis { private String genesisType; Genesis(String typ) { genesisType = typ; } public String getGenesisType() { return genesisType; } public boolean equals(Object o) { if( (o instanceof Genesis) && ((Genesis)o).getGenesisType() == this.genesisType) { return true; } else return false; } }
Output is:
mozart and beethoven are equal
We create two Genesis instances, passing the same type "Coupe" to the Genesis constructor. We'll consider two Genesis objects are the same if their genesisType is equal.
We override the equals() method and compare the two genesisTypes.
Let's look at the equals() method in detail.
if( (o instanceof Genesis) && ((Genesis)o).getGenesisType() == this.genesisType)
We want make sure that the object being tested is of the correct type. It comes in polymorphically as type Object, so we need to do an instanceof test on it. Then we compare the attributes, here, genesisType. We need a cast
(Genesis)oto call a method that's in the Genesis class but not in Object. Without this cast, we can't compile because the compiler would see the object referenced by o as an Object. Because Object class doesn't have a getGenesisType() method, the compiler would squawk. But even with the case, the code fails at runtime if the object referenced by o isn't the object that's castable to a Genesis. So, we absolutely need the instanceof test.
A correct implementation of the equals() method has the following attributes, and the associated statements must always be true:
- reflexive
x.equals(x) - symmetric
x.equals(y) == y.equals(x) - transitive
(x.equals(y) && y.equals(z)) == x.equals(z) - consistent
if x.equals(y) is true at any point in the life of a program, it is always true unless x and y do change.
Hashcodes are usually used to increase the performance of large collections of data. The hashcode value of an object is used by some of the collection classes. Although we can think of it as kind of an object ID number, it isn't necessarily unique.
When we put an object into a HashSet, it uses the object's hashcode value to determine where to put the object in the Set. But is also compares the object's hashcode to the hashcode of all the other objects in the HashSet, and if there's no matching hashcode, the HashSet assumes that this new object is not a duplicate.
In other words, if the hashcodes are different, the HashSet assumes there's no way the object can be equal.
So we must override
But two objects with the same hashCode() might not be equal, so if the HashSet finds a matching hashcode for two object-one we're inserting and one already in the set-the HashSet will then call one of the object's equals() methods to see if these hashcode-matched objects really are equal.
And if they're equal, the HashSet knows that the object we're attempting to add is a duplicate of something in the Set, so the add doesn't happen.
We don't get an exception, but the HashSet's add() method returns a boolean to tell us whether the new object was added. So if the add() method returns false, we know the new object was a duplicate of something already in the set.
HashSets use hashcodes to store the element in a way that makes it much faster to access. If we try to find an object in an ArrayList by giving the ArrayList a copy of the object, the ArrayList has to start searching from the beginning , looking at each element in the list until it finds the match. But a HashSet can find an object quickly since it uses the hashcode as a label on the bucket where it stored the element. It's a kind of a label on the bucket.
p>Hashcode can be the same without necessarily guaranteeing that the objects are equal, because the hashing algorithm used in the hashCode() method might happen to return the same value for multiple objects. That means that multiple objects would all land in the same bucket in the HashSet because each bucket represents a single hashcode value. In this case, Hashcode values are sometimes used to narrow down the search, and to find the exact match, the HashSet still has to take all the objects in that one bucker and then call equals() on them to see if the object it's looking for is in that bucket.- If two objects are equal, they must have matching hashcodes.
- If two object are equal, calling equals() on either object must return true
- If two objects have the same hashcode value, they are not necessarily equal. But they're equal, they must have the same hashcode value.
- So, if we override equals(), we must override hashCode().
- The default behavior of hashCode() is to generate a unique integer for each object on the heap. So, if we don't override hashCode() in a class,
no two objects of that type can ever be considered equal. - The default behavior of equals() is to do an == comparison. In other words, to test whether the two references refer to a single object on the heap. So if we don't override equals() in a class, no two objects can ever be considered equal since references to two different objects will always contain a different bit pattern.
a.equals(b)
must also mean thata.hashCode() == b.hashCode()
But
a.hashCode() == b.hashCode()
doles not have to meana.equals(b)
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization