Welcome to Lesson 10!
|
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 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
- 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
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 theObject
class - 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("true");
else
System.out.println("false");
}
}
|
- This is often not the behavior you want and then you will need to override the
equals()
method
- 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 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("\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();
}
}
* 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("\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();
}
}
- 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
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()
andsuper()
together
Example of this()
and super()
Constructor Calls
- Class
Student
has a constructor with two parameters:String
for the name attributeint
for thestudentNumber
attribute
public Student(String name, int number) { super(name); studentNumber = number; }
- Another constructor within
Student
takes just aString
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()
orsuper()
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:
Using Access Modifiers
- You use the keywords
public
,private
andprotected
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 defaultpublic 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 thanpublic
orprotected
accessclass MyClass { double x; void foo() {} }
In our course, we will be using
private
andpublic
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
Abstract Classes
Abstract Classes and Methods
- When designing a program using inheritance, you often create a superclass that you do not want instantiated
- Instead, you only want subclasses of this superclass to be instantiated
- The superclass contains code common to all the subclasses but should not be instantiated
- For example:
Shape
- You cannot draw a generic shape -- what would it look like?
- Instead, you create subclasses that implement specific shapes
- Here is an example of an inheritance hierarchy for drawing various shapes
Inheritance Hierarchy
Creating Abstract Classes
- One solution to preventing a superclass from being instantiated is to make it abstract
- Abstract classes provide common code for all its subclasses
- A subclass inherits all non-private variables and methods from the abstract class
- An abstract class often defines a set of operations for subclasses, providing a common interface for the subclasses
Coding an Abstract Class
- Any class can become abstract using the keyword
abstract
- Declaring a class
abstract
means that you cannot instantiate an object of the class - Instead, you must subclass the abstract superclass to make use of its operations
- The following example shows a class defined as abstract
Example Abstract Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public abstract class Shape {
// regular instance variable declarations
private int x1, x2, y1, y2;
// regular constructor and method declarations
public Shape() { }
public Shape(int newX1, int newY1, int newX2,
int newY2) {
x1 = newX1;
x2 = newX2;
y1 = newY1;
y2 = newY2;
}
public int getX1() { return x1; }
public int getX2() { return x2; }
public int getY1() { return y1; }
public int getY2() { return y2; }
// Abstract method declaration
public abstract void draw();
}
|
Abstract Methods
- An abstract class may or may not contain abstract methods
- An abstract method is a method declaration without a method body:
public abstract void draw();
- Any abstract methods must be implemented in any non-abstract subclass
- You must declare a class abstract if it contains an abstract method
Activity 10.2: An Abstract Account Class (10 pts)
- Find a partner for pair programming.
- Create a new project folder in Eclipse called Account.
- In this folder, create two new classes - Account.java and Savings.java and put both your names in block comments at the top.
- Make the Account class an abstract class by adding the abstract keyword before the class keyword
- Inside the Account class, add the following variables and methods:
- A private variable called name (String)
- A private variable called balance (double)
- A two-argument constructor which takes in String and double parameters
- A no argument constructor, which calls the two-argument constructor, passing in "name unknown" and 0.0 as arguments.
- Getter and setter methods for the two variables
- An abstract method called updateBalance
- This method is void and has no parameters
- A toString() method
- Make the Savings class final by adding the final keyword before the class keyword - now this class cannot be inherited from.
- Also make Savings a subclass of account
- Inside the Savings class, add the following variables and methods:
- A private variable called interestRate (double)
- A three-argument constructor which takes in a String and two double parameters, and calls the two argument constructor of the Account class
- A no argument constructor, which calls the three-argument constructor of Savings, passing in "name unknown", 0.0, and 0.0 as arguments.
- Getter and setter methods for the interestRate variable
- A method called updateBalance that overrides the abstract method of the super class
- Note that it is required to override this method or your class will have an error
- This method is void and has no parameters
- It multiplies balance by 1 + interestRate and assigns the result to balance
- A toString() method that overrides toString from the superclass.
- A private variable called interestRate (double)
- Add a final class to your project folder called SavingsTest.java. Copy and paste the below code into this class.
/**
* SavingsTest.java
* @author
* @author
* CIS 36B, Activity 10.2
*/
import java.util.Scanner;
public class SavingsTest {
public static void main(String args[]) {
Scanner input = new Scanner(System.in);
System.out.println("Welcome!\n");
System.out.print("Enter your name: ");
String name = input.nextLine();
System.out.print("Enter the balance to invest: $");
double balance = input.nextDouble();
System.out.print("Enter the interest rate on your target account: ");
double interest = input.nextDouble();
Savings savings = new Savings(name, balance, interest);
System.out.println("\nHere is your account summary: \n" + savings);
savings.updateBalance();
System.out.printf("In one year, your balance"
+ " will be: $%.2f", savings.getBalance());
input.close();
}
}
* SavingsTest.java
* @author
* @author
* CIS 36B, Activity 10.2
*/
import java.util.Scanner;
public class SavingsTest {
public static void main(String args[]) {
Scanner input = new Scanner(System.in);
System.out.println("Welcome!\n");
System.out.print("Enter your name: ");
String name = input.nextLine();
System.out.print("Enter the balance to invest: $");
double balance = input.nextDouble();
System.out.print("Enter the interest rate on your target account: ");
double interest = input.nextDouble();
Savings savings = new Savings(name, balance, interest);
System.out.println("\nHere is your account summary: \n" + savings);
savings.updateBalance();
System.out.printf("In one year, your balance"
+ " will be: $%.2f", savings.getBalance());
input.close();
}
}
- When your program works as shown in the sample output, upload Account.java and Savings.java to Canvas.
- Both partners need to submit for full credit.
Sample Output:
Enter your name: Grace Kang
Enter the balance to invest: $50000
Enter the interest rate on your target account: .025
Here is your account summary:
Name: Grace Kang
Balance: $50000.0
Interest Rate: 0.025
In one year, your balance will be: $51250.00