Welcome to Lesson 12!

Learning Objectives
By the end of today's class, you should know...
  • What is the difference between calling this() and super()
  • What is the Object class?
  • What are the methods of the object class?
  • What is the instanceof operator that can be used with the Object class?
  • How do you cast objects up and down the inheritance chain?
  • How do you properly override the equals method from the Object class?


1. Wrapping Up Constructors

  • Constructors cannot be overridden because they are not inherited
  • However, as we have seen before, constructors can be overloaded within the same class
  • Another capability of constructors is that they can call other constructors -- known as chaining
  • To call another constructor, you use the construct this()
  • Like a call to super(), Java will call the constructor (within the same class) that matches the types of arguments with the constructor parameters
  • Here is an example using this() and super() together

Example of this() and super() Constructor Calls

  • Class Student has a constructor with two parameters:
    • String for the name attribute
    • int for the studentNumber attribute
    public Student(String name, int number) {
        super(name);
        studentNumber = number;
    }
    
  • Another constructor within Student takes just a String argument
  • Initializes the studentNumber attribute to a value of 0:
    public Student(String initialName) {
       this(initialName, 0);
    }
    
  • Calls the constructor having two arguments within the same class
  • Note that you can call either this() or super() in a constructor, but not both
  • Whichever is used, it must be the first action taken by the constructor
    • "For subtle reasons" beyond the scope of this course
    • Oracle considering lifting this restriction
    • See conversation here
  • Only one of them can be first, so if you want to invoke both:
    • Use a call with this() to call a constructor using super()


Activity 12.1: An Employee Subclass with this() and super() (10 pts)

  • Open up your Employee.java file from the last activity.
  • We are going to alter the Employee constructors so that one constructor calls this() and one constructor calls super()
  • Recall that this() calls the constructor of the same class
  • When you call super(), you are calling the constructor of the parent class.
  • Specifically, we are going to alter the default constructor for the Employee class to call this() instead of super()
  • Open up this method, remove the call to super() and replace it with a call to this() to call the multi-argument constructor.
/**
     * Default constructor for the
     * Employee class. Calls the
     * multi-argument constructor of the
     * this class
     */
    public Employee() {
       this(//fill in here);
    }
  • Note that your call to this(....) will call the below constructor.
   
    public Employee(String name, int age, String gender, Address a, double salary, String title)        
  • You will need to pass in 6 arguments:
  1. "Name unknown"
  2. 0
  3. "Gender unknown"
  4. new Address()
  5. 0.0
  6. "Title unknown"
  • Now, copy and paste the below code into EmployeeTest.java to verify that your default constructor works properly:
/**
 * EmployeeTest.java
 * @author
 * CIS 36B, Activity 12.1
 */

import java.util.Scanner;

public class EmployeeTest {
    public static void main(String[] args) {
        System.out.println("***Calling the Employee default constructor***");
        Employee E = new Employee();
        System.out.println(E);

    }
}

Required Output:

***Calling the Employee default constructor***
Name: Name unknown
Age: 0
Gender: Gender unknown
Address: 0 Street unknown
Salary: $0.0
Title: Title unknown
  • When your program is giving the required output as shown above, upload Employee.java to Canvas.

2. Working With the Object Class

Introducing the Object class

  • Every class implicitly extends java.lang.Object
  • This makes the Object class the ancestor of all Java classes
    • Including all programmer-defined classes

    Inheritance diagram - Student inherits from Person inherits from Object

  • Thus, every class you write has access to the fields and methods of the Object class
  • That means you need to know how to work with the Object class
    • This lesson will cover the basics of methods, operators and casting involving Object
  • The following is a summary of some of the methods of the Object class
Method/Operator Description
clone() Returns a copy of this object as an Object object. You must implement the Cloneable interface to use this method. Better to use a copy constructor than to override clone.
equals(Object obj) Returns true if this object refers to the same space in memory as another object. Otherwise, returns false even if the other object contains the same data.
getClass() Returns a Class object that represents the class of this object.
hashCode() Returns a hash code value for the object, which is supported for the benefit of hashtables.
toString()
Returns a String object containing the class name followed by an @ symbol and the memory location (in hexadecimal) for the object.
==
Returns true if this object refers to the same space in memory as another object. Otherwise, returns false even if the other object contains the same data.
instanceof
Operator that returns true if this object belongs to a specified class. Otherwise, returns false.

  • Method clone() is complicated, and it is recommended to write a copy constructor rather than overriding
    clone()
  • We already looked at overriding toString(), which you should do for almost every class you write
  • We will cover hashCode() in CIS 22C.
  • In this lesson, we will briefly look at getClass()
  • However, the main focus of this lesson will be on overriding the equals() method.
    • We will also learn about == and instanceof, which will both be useful when overriding equals()

Determining an Object's Type

  • To work with methods of the Object class, we need to know how to check the type of an object
  • Many times you have a parameter of type Object
  • For instance, when you use the equals() method to compare two objects
    public boolean equals(Object obj)
  • Also, some methods, like clone(), return an Object type
    protected Object clone()
  • To work with these methods, you need to know the actual type of the object
  • Java has two main ways of determining this information:
    • instanceof operator
    • Run-time type identification (Reflection API)

Using the instanceof Operator

  • The instanceof operator checks if a test object is in the inheritance hierarchy of a class type
  • Syntax:
    testObject instanceof ClassName
    
  • Where:
    • testObject: the object to test
    • ClassName: the class to compare again
  • For example:
    Person bao = new Person("Bao", 25);
    System.out.println(bao instanceof Person);
    
  • instanceof returns true if the testObject is a descendant of any ClassName

Using Reflection

  • You can use reflection to determine the exact type of an object
  • When Java runs an application, it keeps track of all the objects it loads using a class named Class
  • For each object it loads, Java creates a Class object that has information about the object
  • The getClass() method of Object returns the run-time class of an object
  • For example:
    Person bao = new Person("Bao", 25);
    Class info = bao.getClass();
    System.out.println(info);
    
  • Thus, you can get the Class information about any object
  • Among other methods, the Class object has a method named getName() that returns the name of the type
  • You can use these methods to determine the exact type of an object:
    System.out.println(info.getName());
    

More Information

Casting Objects

  • Once you know what type of object you have, you may need to cast objects to their actual type
  • This allows you to use the methods of the more specialized object
  • Following is the inheritance chain for Student
    Inheritance diagram - Student inherits from Person inherits from Object

  • Java will implicitly cast an object up the inheritance chain (upcasting)
  • However, you must explicitly cast an object down the inheritance chain (downcasting)
Student martina = new Student("Martina", 32, 19556);
Object obj = martina;             // cast Student to Object
Student martina2 = (Student) obj; // cast Object to Student
  • Why? As you move down the inheritance chain, the classes get more complex - more variables and methods.
  • You can always assign to a simpler type and be certain that the simpler type will share the variables and methods of your more complex type.
  • However, as you move down the inheritance chain you will see that an Object, for example, will not have all the same variables and methods as a Student.
  • Thus, you must actively convert (by casting) the simpler type to the more complex type.
  • Following shows how casting affects the methods you can call
Object obj = new Student("Martina", 32, 19556);
// String name = obj.getName();  // does not compile
Student martina = (Student) obj; // cast Object to Student
String name = martina.getName(); // OK
  • Once we cast obj to Student, we can use the getName() method
  • Note that if you try to cast to an incompatible type you will get a compiler error
    String str = (String) martina; // compiler error
  • By the way: How would you convert martina to a String?

Group Activity:

Which of the following will compile, given the below inheritance hierarchy:

Object<-Car<-Tesla inheritance hierarchy
  • Object o = new Car("Toyota", "Prius");
  • Tesla tessy = new Object();
  • Object o = new Object();
Tesla tessy = (Tesla) o;
  • Car car = new Tesla("Model 3", 310, true); //range of 310 miles per charge
    //Auto pilot feature enabled (true)
  • Tesla tessy = new Car("Tesla", "Model 3");

3. Overriding the equals() Method

  • To test if two objects refer to the same memory address you use == (true if address is the same)
    • In other words, == will tell you if two Objects are the exact same Object stored at the exact same memory address
  • Sometimes, == is too restrictive. Instead, you might want to compare two Objects to determine if they store the same data.
    • A better option is to compare two Objects using the .equals method
Student s1 = new Student("Dombi", 1234, 4.0);
Student s2 = new Student("Dombi", 1234, 4.0);
s1 == s2; //false
s1.equals(s2); //true
  • Just like toString() all Java classes inherit an equals() method from the Object class.
  • By default, equals() compares two Object variables to see if they share the same memory address - in other words, the default behavior is no different from using ==.
public boolean equals(Object o) {
    return this == o;
}
  • This is often not the behavior you want. Therefore, you will need to override the equals() method to compare the attributes of two Objects rather than their memory addresses.
  • For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class EqualsTestApp {
    public static void main(String[] args) {
        Person p1 = new Person("Ed");
        Person p2 = new Person("Ed");
        if (p1.equals(p2)) {
            System.out.println("Attribute(s) of p1 and p2 are the same");
        } else {
            System.out.println("Attribute(s) of p1 and p2 are not the same");
        }

        if(p1 == p2) {
            
System.out.println("p1 and p2 stored at the same memory address");
    
    } else {
            
System.out.println("p1 and p2 not stored at the same memory address");
        }

 } }

Review Question: Which of the following is true when you are overriding a method?

  • Both methods are written in the same class? (True or False)
  • The parameters of the two methods must differ? (True or False)
  • The signatures of the two methods must be the same? (True or False)
  • The method is written in the parent class, and then is redefined in the child class? (True or False)

Overriding the Equals Method - A Formula!

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is 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.
  • It is 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.
  • It is 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.
  • Therefore, we must write equals() with care to ensure that we do not violate the above aspects of the contract.
  • According to Joshua Bloch in Effective Java, all overriden equals() methods should do the following:
  • Use the == operator to check if the argument is a reference to this object
  • Use the instanceof operator to check if the argument has the correct type.
  • Cast the argument to the correct type.
  • For each significant field in the class, check if that field of the argument matches the corresponding field of this object.
  • As an example, we build a correct equals() method for Person:
@Override public boolean equals(Object obj)
  • Use the == operator to check if the argument is a reference to this object:
    if (obj == this) { //will also check if o is null
        return true;
    }
    
  • We also want our equals() to work for any kind of object reference
  • Thus, if the passed object is not the same kind, we return false
  • To test the exact type, we use the instanceOf operator:
    if (!(o instanceof Person)) {
        return false;
    }
    
  • If our test object passes these cases, then we compare all the instance variables of our class for equality
  • To do this, we must first cast our object to the actual type and then make the comparisons:
    Person p = (Person) obj;
    if (name.equals(p.getName())) {
        return true;
    }
    return false;
    
  • Putting all this together we have:
    @Override public boolean equals(Object o) {
        if (o == this) {
            return true;
        } else if (!(o instanceof Person)) {
            return false;
        } else { // now safe to cast
            Person p = (Person) o;
            return name.equals(p.name);         
        }
        
    }
    

Overriding the hashCode() Method

  • If you override equals(), then good practice states that you should override the hashCode() method as well.
  • Otherwise, your code will not work correctly with the hash-based classes of the Java Collection like HashTable
  • You will learn more about HashTable in CIS 22C and will practice overriding this method at that time.



Activity 12.2: Overriding Equals for the Address, Person and Employee Classes (10 pts)


  • Open up Person.java, Address.java and Employee.java classes from the previous activity.
  • Add a method with the same signature to each of these classes:
@Override public boolean equals(Object o) {
  • For each of the methods, follow the formula shown above. However, the else clause for each method should be different.
    • For Address: compare number and street
    • For Person: compare name, age, gender and address (you should call the equals method you just wrote for Address)
    • For Employee: compare name, age, gender and address by calling super class's public accessor methods (you cannot call equals for Person here - do you see why?), but also compare salary and title.
  • Copy and paste the new test file below into EmployeeTest.java (you can erase the old contents of the file):
/**
 * EmployeeTest.java
 * @author
 * CIS 36B, Activity 12.2
 */

import java.util.Scanner;

public class EmployeeTest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String name, gender, street, title;
        int age, number;
        double salary;
        Employee employee1 = null;
        Employee employee2 = null;
     
        System.out.print("Welcome!\n\nEnter your name: ");
        name = input.nextLine();
       
        System.out.print("Enter your age: ");
        age = input.nextInt();
       
        System.out.print("Enter your gender: ");
        gender = input.next();
        
        System.out.print("Enter the street number of your address: ");
        number = input.nextInt();
       
        System.out.print("Enter the name of your street: ");
        input.nextLine();
        street = input.nextLine();
        
        Address address = new Address(number, street);
        
        System.out.print("Enter your title in the company: ");
        title = input.nextLine();
        
        System.out.print("Enter your annual salary: ");
        salary = input.nextDouble();
        
       
        employee1 = new Employee(name, age, gender, address, salary, title);
        
        System.out.println("\nCurrently you are equal to null: " + employee1.equals(employee2));
        
        System.out.println("\nYour Summary:\n" + employee1);
        
        System.out.print("\nNow, enter the information about"
                + " the next employee:\nName: ");
        input.nextLine();
        name = input.nextLine();
       
        System.out.print("Age: ");
        age = input.nextInt();
       
        System.out.print("Gender: ");
        gender = input.next();
        
        System.out.print("Street Number: ");
        number = input.nextInt();
       
        System.out.print("Address: ");
        input.nextLine();
        street = input.nextLine();
        
        Address address2 = new Address(number, street);
        
        System.out.print("Title: ");
        title = input.nextLine();
        
        System.out.print("Annual Salary: ");
        salary = input.nextDouble();

   
        System.out.println("\nYou live together: " + address.equals(address2));
        
        System.out.println("\nYou are the same employee: " + employee1.equals(employee2));
       
        input.close();

    }
}
  • Add a call to the Employee constructor to construct a second Employee object in the above program. Then, test it by calling equals on the two employees and their addresses.
  • When you get the correct output (as shown in the example below), both partners should upload Address.java, Person.java, and Employee.java to Canvas

Sample Output (Note that user input may vary):

Welcome!

Enter your name: Sanjiv Sardar
Enter your age: 65
Enter your gender: M
Enter the street number of your address: 445
Enter the name of your street: Howard St
Enter your title in the company: General Manager
Enter your annual salary: 75000

Currently you are equal to null: false

Your Summary:
Name: Sanjiv Sardar
Age: 65
Gender: M
Address: 445 Howard St
Salary: $75000.0
Title: General Manager

Now, enter the information about the next employee:
Name: Rafiq Sardar
Age: 32
Gender: M
Street Number: 445
Address: Howard St
Title: Consultant
Annual Salary: 65000

You live together: true

You are the same employee: false

Wrap Up: 
  • With a partner, answer the questions from our learning objectives.


Upcoming Assignments:

  • Activities 12.1 and 12.2 due Thursday at 11:59pm
  • Lesson 12 Practice Exam Questions answers due Thursday at 11:59pm
  • Quiz 5 due Friday at 11:59pm
  • Peer Reviews of Lesson 11 and 12 Practice Exam Questions due Saturday at 11:59pm
  • Lab 6 due Monday at 11:59pm


~ Have a Great Weekend! ~