6. Inheritance
Programming Project 2021/22

6.8. The Object Class

Object and its methods

Object is the root class of all other Java classes (including arrays).

Object is located in the java.lang package.

Object declares the following methods, which all other classes inherit:

  • protected Object clone()
  • boolean equals(Object obj)
  • protected void finalize()
  • Class<?> getClass()
  • int hashCode()
  • String toString()
  • void notify()
  • void notifyAll()
  • void wait()
  • void wait(long timeout)
  • void wait(long timeout, int nanos)

These methods allow you to perform special tasks in your Java classes.

Extending Object

You can extend Object explicitly:

public class Employee extends Object {
  String name;

  public Employee(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public static void main(String[] args) {
    Employee e = new Employee("John");
    System.out.println(e.getName());
  }
}

Or implicitly:

public class Employee {
   private String name;
   // ...
}

Less is more in this case, so pick the implicit alternative.

getClass()

The getClass() method returns the runtime class of the object on which this method is called.

Someclass s = new Someclass();
Class c = s.getClass();

The runtime class is represented by a Class object (from the java.lang package).

A Class object is sort of a meta object describing the class of an object.

It contains the following data about the class:

  • name
  • package
  • methods
  • fields
  • constructors
  • annotations

See the documentation here.

Exercise 8

Create the class Something:

  • int a + getter/setter

Create a class Runner with a main method that:

  • create an instance of Something
  • create the runtime object using getClass()
  • print the name of the instance
  • print the name of the superclass
  • print the first field member using getDeclaredFields()

You can find the solution to this exercise here.

Object duplication

How do we achieve a true distinct copy of an object?

primitive-ref

clone()

The clone() method creates and returns a copy of the object on which it is called.

It always returns an Object, hence it must be cast to the object's actual type

To create a clone, write:

aCloneableObject.clone();

The Object's implementation of this method checks to see whether the object on which clone() was invoked implements the Cloneable interface. If it does not, the method throws a CloneNotSupportedException.

The default implementation of clone():

  • Creates an object of the same class as the original object
  • Initializes the new object's member variables to have the same values as those of the original object

The simplest way to make your class cloneable is to

  • implement the Cloneable interface and
  • implement clone() by invoking super.clone().
@Override
protected Object clone() throws CloneNotSupportedException {
  return super.clone();
}

You can also write you own implementation of clone().

Exercise 9

  1. Reuse the class Something from the last exercise
    • add implements Cloneable
    • override public Object clone()
  2. Make a Runner class that
    • makes an instance s1 of Something
      • set a=10
      • print the value of s1.a
    • makes an instance s2 of Something
      • set a=20
      • print the value of s1.a, s2.a
    • makes a variable s3 and assign it to s1
      • set a = 30
      • print the value of s1.a, s2.a, s3.a
    • makes a variable s4 and assign it to a clone of s2
      • set a = 40
      • print the value of s1.a, s2.a, s3.a, s4.a

You can find the solution to this exercise here.

Comparing objects

We can compare reference variables using:

  • Referential equality: compares two variables to determine if they refer to the same object.
  • Content equality: compares two objects to determine if they have the same contents (i.e. instance variables).

When you use == on primitive variables, it performs a content equality check.

int x = 10;
int y = 10;
System.out.println("x==y: " + (x == y));
// => x==y: true

When you use == on reference variables, it performs a referential equality check.

Person a = new Person("John", 30);
Person b = new Person("John", 30);
System.out.println("a==b: " + (a == b));
// => a==b: false

What if we want to compare objects by their content?

equals()

The equals() method lets you compare the contents of two objects to see if they are equal.

Object's default implementation of equals() performs a referential equality check.

To perform content equality, we need to override equals().

The rules for overriding this method are stated in Oracle's official documentation for the Object class.

  • Reflexive: For any non-null reference value x, x.equals(x) should return true.
  • Symmetric: For any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • Transitive: For any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

When overriding equals(), it's common practice to also override the hashCode() method.

Content equality: Example

Here is how you can compare the contents of two objects:

class Employee {
  private String name;
  private int age;

  public Employee(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Employee)) return false;

    Employee e = (Employee) obj;
    return e.getName().equals(name) && e.getAge() == age;
  }

}

The Java instanceof operator is used to test whether the object is an instance of the specified type (class or subclass or interface).

public class Equals {
  public static void main(String[] args) {
    Employee e1 = new Employee("John", 30);
    Employee e2 = new Employee("John", 30);

    System.out.println("Is e1 equals to e1: " + e1.equals(e1));

    System.out.println("Is e1 equals to e2: " + e1.equals(e2));
    System.out.println("Is e2 equals to e1: " + e2.equals(e1));

    Employee e3 = new Employee("John", 35);

    System.out.println("Is e1 equals to e3: " + e1.equals(e3));
    System.out.println("Is e3 equals to e1: " + e3.equals(e1));

    Employee e4 = new Employee("Jane", 30);

    System.out.println("Is e1 equals to e4: " + e1.equals(e4));
    System.out.println("Is e4 equals to e1: " + e4.equals(e1));
  }
}
Is e1 equals to e1: true
Is e1 equals to e2: true
Is e2 equals to e1: true
Is e1 equals to e3: false
Is e3 equals to e1: false
Is e1 equals to e4: false
Is e4 equals to e1: false

Exercise 10

Write a class Land, containing:

  • int width
  • int length
  • getArea()

Override the equals() method in such a way that returns true if the area (width*length) is equal in both objects, e.g. Land(10,10) = Land(20,5)

You can find the solution to this exercise here.

toString()

The toString() method returns a string representation of the object on which the method is called.

It is quite useful for debugging purposes.

By default, toString() return a value in the format classname@hashcode, where hashcode is shown in hexadecimal notation.

Employee me = new Employee("John", 30);
System.out.println(me);
Employee@424058530

To get more meaningful messages, we can override this method.

public class Employee {
  private String name;
  private int age;

  public Employee(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "Employee{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
  }
}
Employee{name='John', age=30}