Welcome to Lesson 10!


Learning Objectives for Today's Lesson

By the end of today's lesson, you should know...
  • How to use methods of the Object class
  • How to override methods of the Object superclass
  • The difference between this() and super() method calls
  • The difference among public, private, protected, and package access
  • What is a final class

Announcements

  • Midterm 1 one returned at the end of class
  • Assignment 10 posted - due Wednesday
  • Quiz 5 posted - due Friday
  • Lab 6 posted - due Monday
  • Wednesday online office hour temporarily changed to 3:00-4:15pm due to Halloween


Lesson 9 Practice Exam Questions, Part 2

 
  • Given the below class:
public class Dog {
    private String name;
    private double weight;
 
    public Dog(String name, double weight) {
        this.name = name;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

    public double getWeight() {
        return weight;
    }

    @Override public String toString() {
        return "Name: " + name + "\nWeight: " + weight;
    }

}
  • Write a class, as follows, that inherits from the Dog class.
    • The name of the class is Pug
    • It has a private boolean member variable called kidFriendly
    • It has a three argument constructor to take in name, weight and kidFriendly parameters
    • It has a getter method for the kidFriendly variable
    • It overrides the toString() method of the Dog class

Working With the Object Class

Methods of 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
  • 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
  • The following is a summary of some of the methods of the Object class
  • 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
  • In the following sections we will look at how to make use of the other methods

Object Class Methods

Method 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.

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
  • 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
  • How would you convert martina to a String?


Overriding the equals() Method

  • To test if two objects refer to the same location, you use the equals() method of the Object class
  • However, this may not be the behavior that we want for this method.
  • Instead, a good equals() method should compare attributes of the class, such as name, age, gender to determine whether two objects are equal.
  • What we want to know is whether two objects are storing the same data - not whether they are located at the same address in memory.
  • For example, to compare two Person objects, the equals() method will return false below unless it has been overridden:
1
2
3
4
5
6
7
8
9
10
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("true");
        else
            System.out.println("false");
    }
}
  • This is often not the behavior you want and then you will need to override the equals() method
  • 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

Example Person Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

public class Person {
private String name;

public Person() {
name = "No name yet";
}

public Person(String initialName) {
name = initialName;
}

public String getName() {
return name;
}

public void setName(String newName) {
name = newName;
}

public boolean samePerson(Person other) {
return (name.equalsIgnoreCase(other.name));
}

public void printAttributes() {
System.out.println("Name: " + name);
}

public void special() {
System.out.println("I am special");
}

    @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);     }  
    }

}
  • Here is an explanation of the overriden method equals::
@Override public boolean equals(Object o)
  • 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) o;
    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 10.1: Overriding Equals for the Address, Person and Employee Classes (10 pts)

  • Find a partner for pair programming.
  • Open up one partner's 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: calls the superclass's equals method to compare name, age, gender and address, 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
 * @author
 * CIS 36B, Activity 10.1
 */

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("\nYour Summary:\n" + employee1);
       
        System.out.println("\nCurrently you are equal to null: " + employee1.equals(employee2));
       
        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();
       
        employee2 = new Employee(name, age, gender, address, salary, title);
       
        System.out.println("\nSecond Employee's Summary:\n" + employee2);
       
        System.out.println("\nYou live together: " + address.equals(address2));
        System.out.println("You are the same employee: " + employee1.equals(employee2));
      
        input.close();

    }
}
  • 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

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

Currently you are equal to null: false

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

Second Employee's Summary:
Name: Rafiq Sardar
Age: 32
Gender: M
Address: 445 Howard St
Salary: $65000.0
Title: Consultant

You live together: true
You are the same employee: false


Wrapping Up Basic Inheritance

More About Subclass 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()

Using Access Modifiers

  • You use the keywords public, private and protected to control access to class members
  • Controls access to class instance variables and methods

public Access: Interface Access

  • Least restrictive of all access modifiers
  • public fields and methods can be accessed from anywhere the class is accessible:
    public class MyClass {
        public double x;
        public void foo() {}
    }
    

private Access: Don't Touch That!

  • Most restrictive of all access modifiers
  • private fields and methods cannot be accessed from outside of the class
  • Good design practice to make all variables private and to provide accessor methods where needed
  • Auxiliary ("helper") methods are declared private often as well
  • Generally do not declare a class private -- how would you use it?
    public class MyClass {
        private double x;
        private void foo() {}
    }
    

protected Access: Inheritance Access

  • Accessible in the package of this class and any subclass of this class (which may be in other packages)
  • More restrictive than public accessibility but less restrictive than default
    public class MyClass {
        protected double x;
        protected void foo() {}
    }
    

Package Access: the Default

  • No access modifier: known as default or package accessibility
  • Only accessible to other classes in the same package (directory)
  • Less restrictive than private accessibility, but more restrictive than public or protected access
    class MyClass {
        double x;
        void foo() {}
    }
  • In our course, we will be using private and public access modifiers only as these are the most common 

Final Classes, Methods and Parameters

  • The keyword final is used in many ways

Final Classes

  • Use final to prevent a class from being extended
  • For example:
    public final class String {}
    
  • String is an example of a class which cannot be extended from the Java API

Final Methods

  • Specifies that a method definition cannot be overridden with a new definition in a subclass
  • For example:
    private final void special() {
        System.out.println("I am special");
    }
    
  • Since you would never want the method to do anything else, it makes sense to make it final
  • Allows the compiler to generate more efficient code

Final Parameters

  • Use final in a parameter list to prevent a method from assigning a new value
  • For example:
    public void doSomething(final int i, final int j) {
        //...
    }
    
  • This can prevent a bug where you might set new values to the parameter variables in error
  • The keyword final is not part of the method signature and does not affect how a method is overridden



Wrap Up: 

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


Upcoming Assignments:

  • Assignment 10 due Wednesday
  • Quiz 5 due Friday
  • Lab 6 due Monday
~ See You Wednesday! ~