Learning Objectives
By the end of today's class, you should know...
|
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
==
andinstanceof
, which will both be useful when overridingequals()
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 objectspublic boolean equals(Object obj)
- Also, some methods, like
clone()
, return anObject
typeprotected 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 ofObject
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 namedgetName()
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
- 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 thegetName()
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 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
- 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 anequals()
method from theObject
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
|
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"); |
- 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)
- Per the Oracle documentation for the equals method for Object, when you override
equals()
, you must ensure that your method adheres to what is known as the "equals contract:"
- 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 thehashCode()
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();
* 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):
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! ~