Java,
Week 3
Programming Concepts using Java, Week 3: object oriented design, subclasses and inheritance, dynamic dispatch and polymorphism, the Java class hierarchy and Object, subtyping versus inheritance, and the modifiers public, private, static and final. Every Java example in these notes was really compiled and run.
Chapters
The Philosophy of OO Programming
Why object oriented design puts data first, how to find objects in a problem, what to decide when designing them, and how classes relate.
1.1Algorithms + Data Structures = Programs
This is the title of Niklaus Wirth's introduction to Pascal. Traditionally, algorithms come first and the data representation comes later: you write procedures, and they share whatever data is lying around.
- The claim: this works better for large systems.
- Example, a simple web browser: 2000 procedures maintaining global data, versus 100 classes each with about 20 methods. The second is much easier to grasp.
- Debugging: if an object is in an incorrect state, you search among 20 methods rather than 2000 procedures.
1.2An example: an order processing system
An order processing system typically involves these things:
| Objects (nouns) | What happens to them (verbs) |
|---|---|
| Items | are added to orders |
| Orders | are shipped, cancelled |
| Shipping addresses | are attached to orders |
| Payments | are accepted, rejected |
| Accounts | are checked for credit |
- Nouns signify objects, verbs denote the methods that operate on objects. So we give each
Ordera method to add an item.
1.3Designing objects: behaviour, state, identity
| Aspect | Question it answers | In Java |
|---|---|---|
| Behaviour | What methods do we need to operate on the objects? | the public methods |
| State | How does the object react when methods are invoked? State is the information in the instance variables. | the fields |
| Encapsulation | State should not change unless a method operates on it. | private |
| Identity | How do we distinguish between different objects of the same class? | the object itself (its reference) |
Identity is separate from state. Two orders may contain the same item and so have the same state, yet they are two different objects. And the features interact: state affects behaviour. You cannot add an item to an order that has been shipped, and you cannot ship an empty order.
public class Item {
private String name;
private double price;
public Item(String name, double price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public double getPrice() { return price; }
}public class Account {
private double credit;
public Account(double credit) {
this.credit = credit;
}
public boolean creditOK(double amount) { // Order depends on Account for this
return amount <= credit;
}
}import java.util.ArrayList;
public class Order {
private ArrayList<Item> items = new ArrayList<>(); // aggregation: an Order contains Items
private boolean shipped = false;
private boolean cancelled = false;
public boolean addItem(Item i) {
if (shipped || cancelled) {
return false; // state affects behaviour
}
items.add(i);
return true;
}
public double total() {
double t = 0;
for (Item i : items) {
t += i.getPrice();
}
return t;
}
public boolean ship(Account a) { // dependence: Order needs Account
if (items.isEmpty() || shipped || cancelled) {
return false; // cannot ship an empty order
}
if (!a.creditOK(total())) {
return false;
}
shipped = true;
return true;
}
public boolean cancel() {
if (shipped) {
return false;
}
cancelled = true;
return true;
}
}ArrayList is a growable list from java.util (arrays have a fixed size); we use it here without fuss and meet it properly later. Now watch state, behaviour and identity at work:
public class Main {
public static void main(String[] args) {
Account acct = new Account(1000);
Item pen = new Item("pen", 10);
Order o1 = new Order();
Order o2 = new Order();
System.out.println("ship empty order: " + o1.ship(acct));
o1.addItem(pen);
o2.addItem(pen);
System.out.println("o1 == o2 (identity): " + (o1 == o2));
System.out.println("same total (state): " + (o1.total() == o2.total()));
System.out.println("ship o1: " + o1.ship(acct));
System.out.println("add item after ship: " + o1.addItem(pen));
System.out.println("cancel after ship: " + o1.cancel());
System.out.println("cancel o2: " + o2.cancel());
}
}ship empty order: false
o1 == o2 (identity): false
same total (state): true
ship o1: true
add item after ship: false
cancel after ship: false
cancel o2: true1.4Relationships between classes
| Relationship | Meaning | Example |
|---|---|---|
| Dependence | One class needs another to do its job. Robust design minimises dependencies, also called coupling, between classes. | Order needs Account to check credit; Item does not depend on Account |
| Aggregation | One object contains other objects. | Order contains Item objects |
| Inheritance | One object is a specialised version of another. | ExpressOrder inherits from Order, adding shipping charges and priority handling |
public class ExpressOrder extends Order { // inheritance: a specialised Order
public double shippingCharge() {
return 50.0 + 0.1 * total(); // extra behaviour, reuses total() from Order
}
}public class Main {
public static void main(String[] args) {
ExpressOrder x = new ExpressOrder();
x.addItem(new Item("book", 200)); // inherited from Order
x.addItem(new Item("pen", 10));
System.out.println(x.total()); // inherited
System.out.println(x.shippingCharge()); // new in ExpressOrder
Order o = x; // an ExpressOrder can be used as an Order
System.out.println(o.addItem(new Item("bag", 90)));
}
}210.0
71.0
true1.5Exam tips and common mistakes
- Compare in one line: traditional = algorithms first, then data; OO = data first, then algorithms.
- Learn the four design aspects: behaviour, state, encapsulation, identity, and the three relationships: dependence, aggregation, inheritance.
- "Same state" does not mean "same object": identity is separate.
- Mixing up aggregation (has-a, contains) with inheritance (is-a).
- Thinking dependence is bad per se. The aim is to minimise it, not to remove it.
- Designing with
publicfields so that anyone can change an object's state behind its back.
1.6Interview corner: what they may ask
| Likely question | Chance | Crisp answer |
|---|---|---|
| What is the difference between procedural and object-oriented design? | Very likely | Procedural starts from algorithms (procedures sharing data); OO starts from the data, grouped with the methods that operate on it into classes. |
| What is the difference between state, behaviour and identity? | Very likely | State: the values in the instance variables. Behaviour: what the methods do. Identity: which object it is; two objects can have equal state and still be different objects. |
| Dependence vs aggregation vs inheritance? | Very likely | Dependence: uses another class. Aggregation: contains objects of another class (has-a). Inheritance: is a specialised version of another class (is-a). |
| What is coupling, and why do we want it low? | Likely | Coupling is how much classes depend on each other. Low coupling means a change in one class rarely forces changes in others. |
| How do you find the classes and methods when designing? | Likely | Read the requirements: nouns are candidate classes, verbs are candidate methods. |
Subclasses and Inheritance
Extending a class, what a subclass inherits, why it cannot see private data, constructing the parent part with super, and using a subclass in place of its parent.
2.1A typical Java class: Employee
public class Employee {
private String name;
private double salary;
// Constructors
public Employee(String n, double s) {
name = n;
salary = s;
}
public Employee(String n) {
this(n, 500.00);
}
// Mutator methods
public boolean setName(String s) {
name = s;
return true;
}
public boolean setSalary(double x) {
if (x < 0) {
return false;
}
salary = x;
return true;
}
// Accessor methods
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
// Other methods
public double bonus(float percent) {
return (percent / 100.0) * salary;
}
}- An
Employeeclass with two private instance variables,nameandsalary. - Some constructors to set up the object, and accessor and mutator methods for the instance variables.
- A public method,
bonus, that computes a bonus.
public String getSalary(), but a salary is a double, so the return type must be double. And the mutators return boolean: that is the lecture's way of reporting whether the update was accepted (here setSalary refuses a negative salary).public class Main {
public static void main(String[] args) {
Employee e = new Employee("Asha", 1000);
System.out.println(e.getName() + " earns " + e.getSalary());
System.out.println(e.setSalary(-5)); // refused
System.out.println(e.setSalary(1200)); // accepted
System.out.println(e.getSalary());
System.out.println(e.bonus(5)); // 5 percent of 1200
Employee n = new Employee("Ravi"); // one-argument constructor: default salary
System.out.println(n.getSalary());
}
}Asha earns 1000.0
false
true
1200.0
60.0
500.02.2Subclasses: Manager extends Employee
Managers are special types of employees with extra features. A manager has a secretary, for example. Instead of copying Employee, we write a subclass that extends it:
public class Manager extends Employee {
private String secretary;
public Manager(String n, double s, String sn) {
super(n, s);
secretary = sn;
}
public boolean setSecretary(String s) {
secretary = s;
return true;
}
public String getSecretary() {
return secretary;
}
}- A
Managerobject inherits the other fields and methods fromEmployee. Every manager has a name, a salary, and the methods to access and manipulate them. - We say
Manageris a subclass ofEmployee, andEmployeeis the parent (or super) class. - In general a subclass has more features than its parent: it can add instance variables and methods (and, in the next lecture, override methods).
public class Main {
public static void main(String[] args) {
Manager m = new Manager("Ravi", 2000, "Meena");
System.out.println(m.getName()); // inherited from Employee
System.out.println(m.getSalary()); // inherited
System.out.println(m.getSecretary()); // defined in Manager
m.setSalary(2500); // inherited mutator
System.out.println(m.getSalary());
}
}Ravi
2000.0
Meena
2500.0| Python | Java | |
|---|---|---|
| Declare a subclass | class Manager(Employee): | class Manager extends Employee |
| Call the parent constructor | super().__init__(n, s) | super(n, s); |
| Parents | several allowed | exactly one (next lecture) |
2.3Private data and the constructor: super
A Manager object does not automatically get access to the private data of its parent class. This matters because it is common to extend a parent class written by someone else, whose private details you must not need to know:
public class Manager extends Employee {
public Manager(String n, double s) {
super(n, s);
}
public String peek() {
return name; // name is private to Employee
}
}Manager.java:7: error: name has private access in Employee
return name; // name is private to Employee
^
1 errorSo how can the constructor of Manager set the instance variables that are private to Employee? It uses the parent class's constructor, through super:
public class Employee {
...
public Employee(String n, double s) {
name = n;
salary = s;
}
public Employee(String n) {
this(n, 500.00);
}
}
public class Manager extends Employee {
...
public Manager(String n, double s, String sn) {
super(n, s); // hand the name and salary to Employee
secretary = sn;
}
}super(...)must be the first statement of the subclass constructor. The parent part of the object is built first.- If you do not write it, Java inserts an implicit
super(). That fails to compile when the parent has no no-argument constructor:
public class Main {
public static void main(String[] args) {
Manager m = new Manager("A");
}
}Manager.java:2: error: constructor Employee in class Employee cannot be applied to given types;
public Manager(String n) {
^
required: String,double
found: no arguments
reason: actual and formal argument lists differ in length
1 errorpublic class Manager {, without extends Employee. Without extends there is no inheritance at all.2.4Inheritance rules: what can stand in for what
- Every
Manageris anEmployee, but not everyEmployeeis aManager. - So we can use a subclass in place of a superclass:
Employee e = new Manager(...);. This is the subtyping of Week 1, Lecture 5. - But the other direction will not work:
Manager m = new Employee(...);.
public class Main {
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena"); // fine
System.out.println(e.getName());
}
}Ravipublic class Main {
public static void main(String[] args) {
Manager m = new Employee("Asha", 1000);
}
}Main.java:3: error: incompatible types: Employee cannot be converted to Manager
Manager m = new Employee("Asha", 1000);
^
1 error- A subclass extends a parent class and inherits its instance variables and methods.
- It can add more instance variables and methods, and can also override methods (next lecture).
- Subclasses cannot see private components of the parent. Use
superto reach the parent's constructor.
2.5Exam tips and common mistakes
extends= inherit. The subclass inherits everything non-private and can add more, but cannot see the parent'sprivatemembers.- Constructors are not inherited: a subclass writes its own and calls
super(...)first. - Direction of assignment:
Parent p = new Child();compiles;Child c = new Parent();does not.
- Forgetting
extends, or forgetting that the subclass needs to call the parent constructor. - Trying to use
namedirectly in the subclass: it is private, usegetName(). - Putting
super(...)after another statement. - Assuming constructors are inherited, so a subclass has the same constructors as its parent.
2.6Interview corner: what they may ask
| Likely question | Chance | Crisp answer |
|---|---|---|
What does extends do? What does a subclass inherit? | Very likely | It makes the class a subclass: it inherits the parent's non-private fields and methods and can add its own. Private members are inherited as part of the object but are not directly accessible. |
| Are constructors inherited? | Very likely | No. Each class defines its own; a subclass constructor starts by calling a parent constructor with super(...), explicitly or implicitly. |
What does super mean? | Very likely | A reference to the parent part of the current object: super(...) calls a parent constructor; super.method() calls the parent's version of a method. |
| Why can't a subclass use the parent's private fields? | Very likely | Encapsulation: the parent may change its representation, and a subclass written by someone else must not break. It uses the parent's public or protected methods instead. |
| Can a child object be assigned to a parent variable, and the reverse? | Very likely | Child to parent: yes, it is automatic (every Manager is an Employee). Parent to child variable: no, not without a cast, and the cast fails at run time unless the object really is a child. |
Dynamic Dispatch and Polymorphism
Overriding methods, static type versus run-time dispatch, polymorphism, signatures and overloading, and type casting.
3.1Overriding a method
- A subclass inherits instance variables and methods from its parent, cannot see the parent's private parts, can add more, and can also override methods.
- A
Managercan redefinebonus(), using the parent's version throughsuper:
public double bonus(float percent) {
return 1.5 * super.bonus(percent);
}- It uses the parent class's
bonus()viasuperand overrides the definition in the parent class: a manager gets 1.5 times the normal bonus.
public class Main {
public static void main(String[] args) {
Employee e = new Employee("Asha", 1000);
Manager m = new Manager("Ravi", 2000, "Meena");
System.out.println(e.bonus(5)); // percent / 100 * salary
System.out.println(m.bonus(5)); // 1.5 * (5 / 100 * 2000)
}
}50.0
150.0Two things the compiler checks about an override
The lecture's fragment omits public. An overriding method may not be less visible than the method it replaces:
public class Main {
public static void main(String[] args) {
Manager m = new Manager("A", 1, "B");
}
}Manager.java:18: error: bonus(float) in Manager cannot override bonus(float) in Employee
double bonus(float percent) { // overrides Employee.bonus
^
attempting to assign weaker access privileges; was public
1 errorAnd the annotation @Override (an extra, but a habit worth having) makes the compiler confirm that you really are overriding. A typo in the name or the parameter list would silently create a new method otherwise:
public class Main {
public static void main(String[] args) {
Manager m = new Manager("A", 1, "B");
}
}Manager.java:18: error: method does not override or implement a method from a supertype
@Override
^
1 error3.2Static type versus dynamic dispatch
Consider Employee e = new Manager(...);. Two questions:
| Question | Who answers | Answer |
|---|---|---|
Can we invoke e.setSecretary(...)? | Static type checking: e is declared an Employee, so it can only refer to methods in Employee. | No: compile error |
Which bonus() does e.bonus(p) use? | Static would say Employee.bonus(); dynamic says Manager.bonus(), the real object's version. | Java uses the dynamic one |
public class Main {
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena");
e.setSecretary("C");
}
}Main.java:4: error: cannot find symbol
e.setSecretary("C");
^
symbol: method setSecretary(String)
location: variable e of type Employee
1 errorpublic class Main {
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena");
System.out.println(e.bonus(5)); // Manager's version runs
}
}150.0- Dynamic dispatch (also called dynamic binding, late method binding) turns out to be far more useful, and it is the default in Java. In C++ it is optional: you must mark a method
virtual.
3.3Polymorphism
An array of Employee can hold both kinds of employee. Every one of them “knows” how to calculate its bonus correctly:
public class Main {
public static void main(String[] args) {
Employee[] emparray = new Employee[2];
Employee e = new Employee("Asha", 1000);
Manager m = new Manager("Ravi", 2000, "Meena");
emparray[0] = e;
emparray[1] = m;
for (int i = 0; i < emparray.length; ++i) {
System.out.println(emparray[i].bonus(5.0f));
}
}
}50.0
150.0bonus(5.0), but the parameter is a float and 5.0 is a double, so it does not compile. Write 5.0f (Week 2: a double will not squeeze into a float without a cast).public class Main {
public static void main(String[] args) {
Employee e = new Employee("A", 1000);
System.out.println(e.bonus(5.0));
}
}Main.java:4: error: incompatible types: possible lossy conversion from double to float
System.out.println(e.bonus(5.0));
^
1 error- Recall the event simulation loop that motivated Simula to introduce objects (Week 1, Lecture 5): one generic call, many behaviours.
- This is called runtime polymorphism, or inheritance polymorphism.
3.4Signatures and overloading
- The signature of a function is its name and the list of argument types. We can have different functions with the same name and different signatures, for example multiple constructors.
- Example: the Java class
Arrayshas a methodsortthat sorts arrays of any scalar type. It works becauseArraysdefines overloaded versions:sort(double[] a),sort(int[] a), and so on.
public class Main {
public static void main(String[] args) {
double[] darr = {3.5, 1.25, 2.0};
int[] iarr = {5, 3, 9, 1};
java.util.Arrays.sort(darr); // calls sort(double[])
java.util.Arrays.sort(iarr); // calls sort(int[])
System.out.println(java.util.Arrays.toString(darr));
System.out.println(java.util.Arrays.toString(iarr));
}
}[1.25, 2.0, 3.5]
[1, 3, 5, 9]| Overloading | Overriding + dynamic dispatch | |
|---|---|---|
| Methods | same name, different signatures, in one class | same name, same signature, in parent and subclass |
| Choice made | at compile time (static), from the argument types | at run time (dynamic), from the object's real class |
| Example | Arrays.sort(int[]) / Arrays.sort(double[]) | Employee.bonus() / Manager.bonus() |
public class Main {
static void describe(Employee x) { System.out.println("describe(Employee)"); }
static void describe(Manager x) { System.out.println("describe(Manager)"); }
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena");
describe(e); // overloads: picked from the STATIC type Employee
System.out.println(e.bonus(5)); // override: picked from the RUNTIME type Manager
}
}describe(Employee)
150.03.5Type casting
- With
Employee e = new Manager(...), can we gete.getSecretary()to work? Static type checking disallows this. - Type casting converts
etoManager:((Manager) e).setSecretary(s). - The cast fails (an error at run time) if
eis not aManager. You can test first withinstanceof.
public class Main {
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena");
((Manager) e).setSecretary("Sam"); // legal: e really is a Manager
System.out.println(((Manager) e).getSecretary());
Employee f = new Employee("Asha", 1000);
if (f instanceof Manager) {
System.out.println("a manager");
} else {
System.out.println("not a manager"); // the test protects us
}
Manager bad = (Manager) f; // no test: fails at run time
}
}Sam
not a manager
Exception in thread "main" java.lang.ClassCastException: class Employee cannot be cast to class Manager (Employee and Manager are in unnamed module of loader 'app')
at Main.main(Main.java:13)Casting also works for the basic data types, where it converts the value (Week 2):
public class Main {
public static void main(String[] args) {
double d = 29.98;
int nd = (int) d;
System.out.println(nd);
}
}29if (f instanceof Manager mgr) { mgr.setSecretary("Sam"); } tests and casts in one step, so you cannot cast wrongly.- A subclass can override a method from its parent.
- Dynamic dispatch calls the most appropriate method, based on the run-time identity of the object. It differs from overloading.
- Later we will see another kind, structural polymorphism: one sorting function for an array of any type that supports comparison. Java calls it generics.
- Use type casting (and reflection) to overcome static type restrictions.
3.6Exam tips and common mistakes
- Two checks happen: the compiler uses the declared (static) type to decide what may be called; the JVM uses the real (runtime) type to decide which version runs.
- Overloading = same name, different parameters, static. Overriding = same name, same parameters, dynamic.
- A cast never changes the object; it only changes how the compiler lets you see it, and it is verified at run time.
- Expecting
e.setSecretary()to work because the object is aManager: the compiler looks at the variable's type. - Changing the parameter type in the subclass, which creates an overload instead of an override. Use
@Override. - Making the overriding method less visible (dropping
public). - Casting without checking with
instanceof:ClassCastException.
3.7Interview corner: what they may ask
| Likely question | Chance | Crisp answer |
|---|---|---|
| What is polymorphism? Runtime vs compile-time? | Very likely | One call, many behaviours. Runtime polymorphism is overriding with dynamic dispatch, chosen by the object's real class. Compile-time polymorphism is overloading, chosen by the argument types. |
| Overloading vs overriding? | Very likely | Overloading: same name, different parameter lists, resolved at compile time. Overriding: a subclass redefines an inherited method with the same signature, resolved at run time. |
| What is dynamic dispatch? | Very likely | The JVM picks the method implementation at run time from the object's actual class, not from the variable's declared type. It is the default in Java. |
What does @Override do? | Likely | It asks the compiler to check that the method really overrides a parent method, catching typos in names or parameter types. |
What is a ClassCastException? How do you avoid it? | Very likely | A bad cast discovered at run time: the object is not an instance of the target class. Test with instanceof first, or avoid the cast by using polymorphism. |
| Can an overriding method have a different return type or weaker access? | Sometimes | Access cannot be weaker (compile error). The return type must be the same, or a subtype of the original (covariant return). |
The Java Class Hierarchy
Why Java has no multiple inheritance, the tree rooted at Object, toString(), equals() versus ==, and overriding with the right signature.
4.1Multiple inheritance
Can a subclass extend several parent classes? Suppose C1 and C2 both define public int f(); and C3 extends both. If C3 does not override f(), which one do we use?
- Java does NOT allow multiple inheritance of classes.
- C++ allows it, but only if
C1andC2have no conflict.
public class Main {
public static void main(String[] args) {
C3 c = new C3();
}
}C3.java:1: error: '{' expected
public class C3 extends C1, C2 {
^
1 errorInterfaces: several parents without the conflict (extra)
A class can implement many interfaces. If two of them supply a default f(), Java still refuses to guess: the class must say which one it wants.
public class Main {
interface I1 { default int f() { return 1; } }
interface I2 { default int f() { return 2; } }
static class C3 implements I1, I2 {
}
public static void main(String[] args) { }
}Main.java:5: error: types I1 and I2 are incompatible;
static class C3 implements I1, I2 {
^
class C3 inherits unrelated defaults for f() from types I1 and I2
1 errorpublic class Main {
interface I1 { default int f() { return 1; } }
interface I2 { default int f() { return 2; } }
static class C3 implements I1, I2 {
public int f() {
return I1.super.f() + I2.super.f(); // the class decides
}
}
public static void main(String[] args) {
System.out.println(new C3().f());
}
}34.2The Java class hierarchy and Object
- No multiple inheritance, so the hierarchy is tree-like.
- In fact there is a universal superclass
Object. A class with noextendsclause silently extends it. - Useful methods defined in
Object, inherited by every class we write:public boolean equals(Object o)(defaults to pointer equality) andpublic String toString().
public class Main {
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena");
System.out.println(e.getClass().getName());
System.out.println(e instanceof Employee);
System.out.println(e instanceof Object);
System.out.println(Manager.class.getSuperclass().getName());
System.out.println(Employee.class.getSuperclass().getName());
System.out.println(Object.class.getSuperclass());
}
}Manager
true
true
Employee
java.lang.Object
null4.3toString() and printing an object
toString()converts an object to aString. The default one is not helpful: it gives the class name and a hash code.- To print an object
o, useSystem.out.println(o + ""). Concatenating with a string implicitly invokeso.toString(). (println(o)does the same.) - Override
toString()to describe the state; dynamic dispatch makes the print use your version.
public class Main {
public static void main(String[] args) {
Date d = new Date(13, 8, 2015);
System.out.println(d); // println(o) calls d.toString()
System.out.println(d + ""); // so does concatenation
System.out.println("due: " + d);
Object plain = new Object();
String s = plain.toString(); // the default: class name @ hash code
System.out.println(s.startsWith("java.lang.Object@"));
}
}13/8/2015
13/8/2015
due: 13/8/2015
true4.4equals() versus ==
Exploit the tree to write generic functions. A function that takes Object[] works for an array of any class. The lecture's example is a search:
public class Main {
public static int find(Object[] objarr, Object o) {
int i;
for (i = 0; i < objarr.length(); ++i) {
if (objarr[i] == o) {
return i;
}
}
return -1;
}
}Main.java:4: error: cannot find symbol
for (i = 0; i < objarr.length(); ++i) {
^
symbol: method length()
location: variable objarr of type Object[]
1 errorfind (extra). (1) An array's size is the field objarr.length, with no brackets; length() does not compile (Week 2). (2) == on objects is always pointer equality. The lecture says that "x == y invokes x.equals(y)" and that an overridden equals would be picked by objarr[i] == o. That is not true in Java: == never calls equals. To use the redefined equals, call it: objarr[i].equals(o).public class Date {
private int day, month, year;
public Date(int d, int m, int y) {
day = d;
month = m;
year = y;
}
@Override
public boolean equals(Object d) {
if (d instanceof Date) {
Date myd = (Date) d;
return this.day == myd.day && this.month == myd.month && this.year == myd.year;
}
return false;
}
@Override
public int hashCode() { // equal dates must have equal hash codes
return java.util.Objects.hash(day, month, year);
}
@Override
public String toString() {
return day + "/" + month + "/" + year;
}
}public class Main {
static int find(Object[] objarr, Object o) {
for (int i = 0; i < objarr.length; ++i) {
if (objarr[i].equals(o)) {
return i;
}
}
return -1;
}
static int findSame(Object[] objarr, Object o) {
for (int i = 0; i < objarr.length; ++i) {
if (objarr[i] == o) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
Object[] dates = { new Date(9, 9, 2009), new Date(1, 2, 2000) };
Date target = new Date(1, 2, 2000); // equal in state, a different object
System.out.println(find(dates, target)); // equals(): compares state
System.out.println(findSame(dates, target)); // ==: compares identity
System.out.println(target.equals(dates[1]));
System.out.println(target == dates[1]);
}
}1
-1
true
false4.5Overriding equals() correctly
For a class Date with instance variables day, month and year, we may wish to override equals() to compare the object's state. The obvious attempt:
public boolean equals(Date d) {
return this.day == d.day && this.month == d.month && this.year == d.year;
}Unfortunately boolean equals(Date d) does not override boolean equals(Object o). The parameter types differ, so it is a new overload sitting beside the inherited method. Code that only knows Object (a collection, the find above) never sees it:
public class Main {
static int find(Object[] objarr, Object o) {
for (int i = 0; i < objarr.length; ++i) {
if (objarr[i].equals(o)) {
return i;
}
}
return -1;
}
static int findSame(Object[] objarr, Object o) {
for (int i = 0; i < objarr.length; ++i) {
if (objarr[i] == o) {
return i;
}
}
return -1;
}
public static void main(String[] args) {
Date d1 = new Date(1, 2, 2000);
Date d2 = new Date(1, 2, 2000);
Object o2 = d2;
System.out.println(d1.equals(d2)); // static type Date: equals(Date), compares state
System.out.println(d1.equals(o2)); // static type Object: equals(Object), the inherited ==
System.out.println(find(new Object[] { d1 }, d2));
}
}true
false
-1@Override exposes the mistake immediately, and the fix is to take an Object, test with instanceof, cast, and compare. The lecture's correct version is the one shown earlier:
public class Date {
private int day, month, year;
@Override
public boolean equals(Date d) {
return this.day == d.day && this.month == d.month && this.year == d.year;
}
}Date.java:4: error: method does not override or implement a method from a supertype
@Override
^
1 errorequals and hashCode go together (extra)
If two objects are equal, they must have the same hash code, or hash-based collections such as HashSet will treat them as different. That is why the correct Date above also overrides hashCode().
public class Main {
static class Bad {
int v;
Bad(int v) { this.v = v; }
@Override public boolean equals(Object o) { return o instanceof Bad && ((Bad) o).v == v; }
}
public static void main(String[] args) {
java.util.Set<Date> good = new java.util.HashSet<>();
good.add(new Date(1, 2, 2000));
good.add(new Date(1, 2, 2000));
System.out.println(good.size()); // equals + hashCode agree: one date
java.util.Set<Bad> bad = new java.util.HashSet<>();
bad.add(new Bad(1));
bad.add(new Bad(1));
System.out.println(bad.size()); // equals but no hashCode: two
}
}1
24.6Overriding looks for the "closest" match
Suppose Employee defines public boolean equals(Employee e) and Manager defines no equals. Then consider two managers and the call m1.equals(m2). Which method is used? equals(Employee e) and equals(Object o) both accept a Manager; Java picks the most specific, closest one: equals(Employee e).
public class Main {
static class Employee {
public boolean equals(Employee e) {
System.out.println("equals(Employee) chosen");
return true;
}
}
static class Manager extends Employee { }
public static void main(String[] args) {
Manager m1 = new Manager();
Manager m2 = new Manager();
m1.equals(m2); // Employee is closer than Object
Object o = m2;
System.out.println(m1.equals(o)); // static type Object: only equals(Object) fits
}
}equals(Employee) chosen
false- Java does not allow multiple inheritance; a subclass extends exactly one parent, so the hierarchy is a tree rooted at
Object. Objectsupplies defaults such asequals()andtoString(), implicitly inherited by every class.- When we override, check the signature carefully.
4.7Exam tips and common mistakes
==on objects = same object.equals()= same state, if the class overrides it. Defaultequalsis just==.- The signature to override is exactly
public boolean equals(Object o); write@Override. - A class has one parent but may implement many interfaces.
- Writing
equals(Date d)instead ofequals(Object o): an overload, not an override. - Using
==and expecting it to callequals. - Overriding
equalsbut nothashCode. - Forgetting that every class already has
toString()andequals()fromObject.
4.8Interview corner: what they may ask
| Likely question | Chance | Crisp answer |
|---|---|---|
== vs equals() for objects? | Very likely | == compares references (same object). equals() compares whatever the class defines, usually state; by default it is the same as ==. |
How do you override equals correctly? What else must you override? | Very likely | Take an Object parameter, check instanceof, cast, compare the fields. Also override hashCode so equal objects have equal hash codes. |
| Why doesn't Java support multiple inheritance of classes? | Very likely | To avoid the diamond problem: two parents supplying the same method with no good rule for choosing. Interfaces give the useful part, several subtypes, without inheriting conflicting state. |
What is Object? | Very likely | The root of the class hierarchy. Every class extends it, directly or indirectly, so every object has equals, hashCode, toString and getClass. |
What does the default toString() print? Why override it? | Likely | The class name, @ and a hash code, which is unhelpful. Override it to describe the state; println and string concatenation use it automatically. |
Why does HashSet need hashCode to agree with equals? | Likely | It first uses the hash code to pick a bucket, then equals inside the bucket. Equal objects with different hash codes land in different buckets and are never compared. |
Subtyping versus Inheritance
Why one class hierarchy gives two different things, the deque, stack and queue example in real Java, and how composition re-uses code without subtyping.
5.1Two jobs done by one class hierarchy
The class hierarchy provides both subtyping and inheritance. They are different ideas that happen to share one mechanism, so it pays to keep them apart.
- The capabilities of the subtype are a superset of those of the main type.
- If
Bis a subtype ofA, wherever an object of typeAis needed, an object of typeBcan be used. Employee e = new Manager(...);is legal.
- The subtype can re-use the code of the main type.
Binherits fromAif some functions forBare written in terms of functions ofA.Manager.bonus()usesEmployee.bonus().
public class Main {
public static void main(String[] args) {
Employee e = new Manager("Ravi", 2000, "Meena"); // subtyping: a Manager used as an Employee
System.out.println(e.bonus(5)); // inheritance: Manager.bonus reuses Employee.bonus
}
}150.05.2The deque example in Java
Recall the example from Week 1. A queue has insert-rear and delete-front. A stack has insert-front and delete-front. A deque has all four: insert-front, delete-front, insert-rear, delete-rear.
| Relationship | Why | |
|---|---|---|
| Subtyping | Deque is a subtype of both Stack and Queue | a deque has more functionality than a queue or a stack |
| Inheritance | Stack and Queue can inherit from Deque | suppress two of its functions and a deque works as a queue or a stack: the code is re-used |
Subtyping in Java: interfaces. Each interface lists what a stack or a queue can do. Deque implements both, so a Deque can be used wherever a Stack or a Queue is needed.
public interface Stack {
void insertFront(int x);
int deleteFront();
}public interface Queue {
void insertRear(int x);
int deleteFront();
}public class Deque implements Stack, Queue {
private java.util.ArrayDeque<Integer> items = new java.util.ArrayDeque<>();
public void insertFront(int x) { items.addFirst(x); }
public int deleteFront() { return items.removeFirst(); }
public void insertRear(int x) { items.addLast(x); }
public int deleteRear() { return items.removeLast(); }
}public class Main {
public static void main(String[] args) {
Stack s = new Deque(); // a Deque used as a Stack
s.insertFront(1);
s.insertFront(2);
System.out.println(s.deleteFront()); // last in, first out
Queue q = new Deque(); // a Deque used as a Queue
q.insertRear(1);
q.insertRear(2);
System.out.println(q.deleteFront()); // first in, first out
}
}2
1Inheritance in Java: extends. It is tempting to build the stack by extending Deque and re-using all its code. But extends also makes the stack a subtype of Deque. The lecture's point appears: the interfaces are not compatible, yet the compiler lets it through.
public class LeakyStack extends Deque {
// reuses all of Deque: but it is also a Deque
}public class Main {
public static void main(String[] args) {
LeakyStack s = new LeakyStack();
s.insertFront(1);
s.insertFront(2);
s.insertRear(99); // a "stack" that accepts rear insertion!
Deque d = s; // and a LeakyStack is a Deque
System.out.println(d.deleteRear());
}
}99Re-use without subtyping: composition. Keep a Deque as a private field and expose only what a stack should offer.
public class SafeStack {
private Deque deque = new Deque(); // reuse by composition
public void push(int x) { deque.insertFront(x); }
public int pop() { return deque.deleteFront(); }
}public class Main {
public static void main(String[] args) {
SafeStack s = new SafeStack();
s.push(1);
s.push(2);
System.out.println(s.pop());
s.insertRear(99); // not available
}
}Main.java:7: error: cannot find symbol
s.insertRear(99); // not available
^
symbol: method insertRear(int)
location: variable s of type SafeStack
1 error5.3Which tool for which job
| You want | Use | You get |
|---|---|---|
| Subtyping only: others can rely on the capabilities | interface + implements | substitutability, no code |
| Subtyping and code re-use, for a real is-a (Manager is an Employee) | class B extends A | both, together |
| Re-use only, no subtype (Stack from Deque) | composition: a private field plus delegation | the code, with a small interface |
5.4Exam tips and common mistakes
- Subtyping is about capabilities (interfaces); inheritance is about re-using code (implementations).
- The subtype supports at least what the main type supports:
Deque(4 operations) is the subtype ofStack(2). - In Java
extendsalways gives both. To re-use without subtyping, use composition.
- Saying a
Stackis a subtype ofDequebecause it inherits from it. It can useDeque's code but cannot stand in for it. - Using
extendspurely to save typing, when the relationship is not is-a. - Mixing up the direction: the subtype is the one with more capabilities.
5.5Interview corner: what they may ask
| Likely question | Chance | Crisp answer |
|---|---|---|
| Subtyping vs inheritance? | Very likely | Subtyping: a type can be used wherever another is expected (superset of capabilities). Inheritance: re-use of code. In Java extends gives both; implements gives subtyping only. |
| Inheritance vs composition? Which do you prefer? | Very likely | Inheritance for a true is-a where the child can always replace the parent. Otherwise composition: hold the object in a private field and delegate. Composition does not leak the parent's whole interface. |
| Why should a Stack not extend a Deque? | Very likely | It would inherit insertRear and deleteRear, so a "stack" could be used (or misused) as a deque, breaking last-in first-out. It is a code-reuse, not an is-a, relationship. |
| What is the Liskov substitution principle? | Likely | Wherever a parent is expected, any subtype must work without surprising the caller. A subclass that removes or breaks behaviour violates it. |
| When do you use an interface instead of a class? | Likely | When you want to describe capabilities that unrelated classes can share, without sharing code or state. |
Java Modifiers
public and private for encapsulation, static for things that exist without objects, and final for things that cannot change.
6.1Modifiers in Java
Java uses many modifiers in declarations, to cover different features of object-oriented programming. Three families do most of the work:
| Modifier | Supports | Meaning |
|---|---|---|
public / private | encapsulation of data | who is allowed to use it |
static | entities defined inside classes that exist without creating objects | belongs to the class itself |
final | values that cannot be changed | assigned once |
These modifiers can be applied to classes, instance variables and methods. Let us look at situations where the different combinations make sense.
6.2public versus private
- A faithful implementation of encapsulation needs
publicandprivate. - Typically instance variables are private; the methods that query (accessor) and update (mutator) the state are public.
- Sometimes we also use private methods: helpers that do some work which should not be directly accessible to the public.
Example: a Stack class
The data is stored in a private array, with public methods to push, pop and query isEmpty. push() must check whether there is space. To deal gracefully with stack overflow, it calls two private methods: one to check if the stack is full and one to extend the storage.
public class Stack {
private int[] values = new int[2];
private int size = 2;
private int top = 0;
public void push(int i) {
if (stack_full()) {
extend_stack(); // deal gracefully with overflow
}
values[top] = i;
top += 1;
}
public int pop() {
if (isEmpty()) {
throw new IllegalStateException("stack is empty");
}
top -= 1;
return values[top];
}
public boolean isEmpty() {
return top == 0;
}
private boolean stack_full() {
return top == size;
}
private void extend_stack() {
values = java.util.Arrays.copyOf(values, 2 * size); // allocate more space
size = 2 * size;
}
}public class Main {
public static void main(String[] args) {
Stack s = new Stack(); // capacity starts at 2
for (int i = 1; i <= 5; i++) {
s.push(i); // the 3rd and 5th push make the stack grow, unseen
}
while (!s.isEmpty()) {
System.out.print(s.pop() + " ");
}
System.out.println();
s.pop(); // popping an empty stack
}
}5 4 3 2 1
Exception in thread "main" java.lang.IllegalStateException: stack is empty
at Stack.pop(Stack.java:16)Callers never learn that the array doubled. And they cannot reach inside:
public class Main {
public static void main(String[] args) {
Stack s = new Stack();
s.extend_stack();
System.out.println(s.top);
}
}Main.java:4: error: extend_stack() has private access in Stack
s.extend_stack();
^
Main.java:5: error: top has private access in Stack
System.out.println(s.top);
^
2 errors6.3Accessor and mutator: keeping state consistent
Public methods to query and update private instance variables are good, but they can be too generous. The Date class with one setter per instance variable allows inconsistent updates: nothing stops us from separately setting an invalid combination of day and month.
public class Date {
private int day, month, year;
public int getDay() { return day; }
public int getMonth() { return month; }
public int getYear() { return year; }
public void setDay(int d) { day = d; }
public void setMonth(int m) { month = m; }
public void setYear(int y) { year = y; }
}public class Main {
public static void main(String[] args) {
Date d = new Date();
d.setMonth(2);
d.setDay(31); // February 31st
System.out.println(d.getDay() + "/" + d.getMonth());
}
}31/2Instead, only allow a combined update, and validate the combination inside it:
public class Date {
private int day, month, year;
public int getDay() { return day; }
public int getMonth() { return month; }
public int getYear() { return year; }
public boolean setDate(int d, int m, int y) {
if (m < 1 || m > 12 || d < 1 || d > daysIn(m, y)) {
return false; // validate d-m-y combinations
}
day = d;
month = m;
year = y;
return true;
}
private static int daysIn(int m, int y) { // a private helper
if (m == 2) {
boolean leap = (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
return leap ? 29 : 28;
}
return (m == 4 || m == 6 || m == 9 || m == 11) ? 30 : 31;
}
}public class Main {
public static void main(String[] args) {
Date d = new Date();
System.out.println(d.setDate(31, 2, 2023)); // rejected
System.out.println(d.setDate(29, 2, 2024)); // 2024 is a leap year
System.out.println(d.setDate(31, 4, 2024)); // April has 30 days
System.out.println(d.getDay() + "/" + d.getMonth() + "/" + d.getYear());
}
}false
true
false
29/2/20246.4static components
- Use
staticfor components that exist without creating objects: library functions,main(), and useful constants likeMath.PIandInteger.MAX_VALUE. These static components are alsopublic. - We do use
private staticsometimes, for internal bookkeeping. Example: the constructor gives each order a unique id.
public class Order {
private static int lastorderid = 0; // one copy, shared by every Order
private int orderid; // one copy per Order
public Order() {
lastorderid++;
orderid = lastorderid;
}
public int getOrderId() {
return orderid;
}
}public class Main {
public static void main(String[] args) {
Order a = new Order();
Order b = new Order();
Order c = new Order();
System.out.println(a.getOrderId() + " " + b.getOrderId() + " " + c.getOrderId());
System.out.println(Math.PI); // a static constant: no object needed
System.out.println(Integer.MAX_VALUE);
}
}1 2 3
3.141592653589793
2147483647lastorderidis a private static field: common to all objects in the class, hidden from outside.- Concurrent updates need some care: if two threads create orders at the same moment,
lastorderid++can give both the same id. (We meet threads later.) - A
staticmethod belongs to the class, so it has nothisand cannot touch instance variables directly:
public class Main {
int count = 0; // an instance variable
public static void main(String[] args) {
count = count + 1; // main is static: which object's count?
}
}Main.java:5: error: non-static variable count cannot be referenced from a static context
count = count + 1; // main is static: which object's count?
^
Main.java:5: error: non-static variable count cannot be referenced from a static context
count = count + 1; // main is static: which object's count?
^
2 errors6.5final components
finaldenotes that a value cannot be updated. It is usually used for constants:public static finalinstance variables such asMath.PIandInteger.MAX_VALUE.- What would
finalmean for a method? It cannot be redefined by a subclass. The lecture's summary says the same: a final method cannot be overridden.
public class Main {
public static final double RATE = 0.05; // a constant
public static void main(String[] args) {
System.out.println(RATE * 1000);
RATE = 0.10; // not allowed
}
}Main.java:6: error: cannot assign a value to static final variable RATE
RATE = 0.10; // not allowed
^
1 errorpublic class Main {
public static void main(String[] args) {
Manager m = new Manager("A", 1, "B");
}
}Manager.java:18: error: bonus(float) in Manager cannot override bonus(float) in Employee
public double bonus(float percent) { // overrides Employee.bonus
^
overridden method is final
1 errorpublic class Main {
public static void main(String[] args) {
Manager m = new Manager("A", 1, "B");
}
}Manager.java:1: error: cannot inherit from final Employee
public class Manager extends Employee {
^
1 error- A
finalclass cannot be extended at all.Stringis afinalclass, which is part of why strings are safe to share. finalon a variable fixes which object it refers to, not the object's contents (extra):
public class Main {
public static void main(String[] args) {
final int[] a = {1, 2, 3};
a[0] = 99; // allowed: the contents can change
System.out.println(a[0]);
a = new int[3]; // not allowed: the reference cannot
}
}Main.java:6: error: cannot assign a value to final variable a
a = new int[3]; // not allowed: the reference cannot
^
1 error6.6Putting the modifiers together
| Combination | Typical use | Example |
|---|---|---|
private instance variable | hidden state | private double salary; |
public method | the interface | public double bonus(float p) |
private method | internal helper | private boolean stack_full() |
public static | library function, main | public static void main(String[] args) |
private static | bookkeeping shared by all objects | private static int lastorderid; |
public static final | a constant | Math.PI, Integer.MAX_VALUE |
final method | cannot be overridden | public final void audit() |
The lecture ends: we can also have private classes. A top-level class cannot be private, but a class nested inside another can, so it stays an internal detail:
private class Helper {
}Helper.java:1: error: modifier private not allowed here
private class Helper {
^
1 errorpublic class Main {
private static class Helper { // nested, and private to Main
int twice(int x) { return 2 * x; }
}
public static void main(String[] args) {
System.out.println(new Helper().twice(21));
}
}42privateandpublicare natural artefacts of encapsulation. Usually instance variables are private and methods are public, but private methods also make sense.staticandfinalare orthogonal to public/private: you can combine them freely.- Use
private staticvariables to keep bookkeeping across the objects of a class: a global serial number, a count of objects created, profiling method calls. finalis usually used for constants. A final method cannot be overridden by a subclass.
6.7Exam tips and common mistakes
- Default recipe: private fields, public methods. A private method is a helper that outsiders should not call.
static= one per class; non-static = one per object.finalvariable = assigned once;finalmethod = not overridable;finalclass = not extendable.
- Using an instance variable from a
staticmethod (main) without an object. - Believing a
finalarray or object cannot change inside: only the reference is fixed. - Making every field public "for convenience", or giving every field a setter.
- Forgetting that a
staticcounter is shared across all objects (and is not thread-safe by itself).
6.8Interview corner: what they may ask
| Likely question | Chance | Crisp answer |
|---|---|---|
static vs non-static? | Very likely | static members belong to the class and exist once, without any object; non-static (instance) members belong to each object. A static method has no this. |
What does final mean on a variable, a method, a class? | Very likely | Variable: assigned once (a constant). Method: cannot be overridden. Class: cannot be extended. |
public, private, protected and default? | Very likely | private: same class only. Default (none): same package. protected: same package and subclasses. public: everywhere. |
Why is main static? | Very likely | The JVM must call it before any object exists, so it belongs to the class. |
| How would you give every object a unique id? | Likely | A private static counter that every constructor increments and copies into an instance field. Add synchronisation or an AtomicInteger if threads create objects. |
Is a final list or array immutable? | Likely | No. final fixes the reference, not the contents. Immutability needs a class with no mutators. |
| Why use private methods? | Likely | For internal helpers (like stack_full()) that are part of the implementation, not the interface, so they can change freely. |
Interview Question Bank
What is likely to be asked from Week 3, with 30-second answers, deeper explanations, a lecture-or-extra tag on each question, and code-reading practice.
Week 3 is where interviewers test whether you really understand inheritance and polymorphism. Every answer starts with a 30-second version. Each question is tagged From the lecture or Extra, so you know which answers go beyond what the lecture said.
One clear sentence: what it is.
Three or four lines of code.
What goes wrong without it.
7.1Interview heat-map: what is most likely
Employee e = new Manager()?"Notes 3.2, 3.3== vs equals(), overriding equals and hashCode"How do you write a correct equals?"Notes 4.4, 4.5super and constructors in inheritance"In what order are constructors called?"Notes 2.3static vs instance, final, access modifiers"What does final mean on a method? A class?"Notes 6.4, 6.5Object class: toString, equals, hashCode"What does every class inherit?"Notes 4.2, 4.3instanceof, ClassCastException"When does a cast fail?"Notes 3.5@Override and the rules of overriding"Can you weaken access when overriding?"Notes 3.1static counters and threads"Is lastorderid++ thread safe?"Notes 6.47.2Design and inheritance
Start from Wirth's title, Algorithms + Data Structures = Programs.
Procedural design puts algorithms first and lets procedures share data. OO design puts the data first, groups it with the methods that operate on it into classes, and finds those classes by reading the requirements: nouns are objects, verbs are methods.
Go deeper
- It scales: 100 classes of about 20 methods is easier to grasp, and an object in a bad state is debugged by searching 20 methods, not 2000 procedures.
Use Order, Item, Account, ExpressOrder.
Dependence: one class uses another (Order needs Account for credit). Aggregation: one object contains others (Order has Items). Inheritance: one class is a specialised version of another (ExpressOrder is an Order).
Go deeper
- A robust design minimises dependencies (coupling) so a change stays local.
Constructors are not inherited; they are chained.
class A { A() { System.out.println("A"); } }
class B extends A { B() { System.out.println("B"); } }
new B();A
BA subclass inherits the parent's non-private fields and methods and adds its own, but it cannot see the parent's private members. A constructor always starts by calling a parent constructor with super(...), so the parent is built first.
Go deeper
- Without an explicit
super(...)Java addssuper(); if the parent has no no-argument constructor, it does not compile.
this(...) do, and can you use both this(...) and super(...)?7.3Polymorphism and dispatch
Employee e = new Manager(...); e.bonus(5)? Why can't you call e.setSecretary()?Static type versus runtime type.
The compiler checks the declared (static) type: Employee has no setSecretary, so that call does not compile. The JVM picks bonus from the real (runtime) type, Manager: dynamic dispatch, the default in Java.
Go deeper
- In C++ it is optional and needs
virtual. In Java every non-static, non-private, non-final method is dispatched dynamically.
One table, one example each.
static void describe(Employee x) { ... }
static void describe(Manager x) { ... }
Employee e = new Manager(...);
describe(e); // describe(Employee): picked from the static typeOverloading: same name, different parameter lists, in one class, chosen at compile time from the argument types (Arrays.sort(int[]) vs sort(double[])). Overriding: a subclass redefines an inherited method with the same signature, chosen at run time from the object's class.
Go deeper
@Overrideturns a silent overload (a typo in the parameter type) into a compile error.
ClassCastException, and how do instanceof and casting work?Say when it happens, then how to avoid it.
A cast to a subclass is checked at run time. If the object is not really an instance of that class the JVM throws ClassCastException. Guard it with instanceof, or avoid the cast by using polymorphism.
Go deeper
- Casting an
intlike(int) 29.98is different: it converts the value (to 29). - Java 16+:
if (e instanceof Manager m) { ... }tests and casts in one step (extra).
Signature, access, return type, @Override.
Same name and parameter types; access no weaker than the original (a missing public is a compile error); return type the same or a subtype. Add @Override so the compiler checks it. final methods cannot be overridden.
Go deeper
privateandstaticmethods are not overridden (a same-named method in the subclass just hides or is unrelated).
7.4The Object class
== versus equals(): and is it true that x == y calls x.equals(y)?The lecture's slide says it does. Say what is really true.
System.out.println(find(dates, target)); // equals(): found
System.out.println(findSame(dates, target)); // ==: not found1
-1No. == on objects always compares references (are they the same object?) and never calls equals. equals compares whatever the class defines, usually state; the default Object.equals is just ==.
Go deeper
- A generic search must call
objarr[i].equals(o); with==it finds only the identical object.
"hi" == "hi" sometimes give true?equals correctly? What else must you override?The lecture's trap: equals(Date d) does not override.
Take an Object parameter, check instanceof, cast, compare the fields, and add @Override. Also override hashCode so equal objects have equal hash codes.
Go deeper
equals(Date d)is an overload: code that only knowsObjectnever reaches it.- Without
hashCode, aHashSetcan keep two "equal" objects.
equals (reflexive, symmetric, transitive).Draw the diamond.
If C1 and C2 both define f() and C3 extends both, which f() does C3 use? To avoid that ambiguity, a Java class extends exactly one class. A class may implement several interfaces, and if two supply the same default method, it must override it and choose.
Go deeper
- So the class hierarchy is a tree rooted at
Object.
Object? What does the default toString() print?equals, toString, hashCode, getClass.
Every class extends Object, so it has equals (pointer equality by default), toString, hashCode and getClass. The default toString prints the class name, @ and a hash code, so override it to show the state; println and string concatenation then use your version.
Go deeper
- This works because of dynamic dispatch:
printlnonly knowsObject.
7.5Subtyping and composition
Which one is the subtype of which?
Subtyping is about capabilities: a deque (4 operations) is a subtype of both stack (2) and queue (2) because it can do everything they can. Inheritance is code re-use: a stack and a queue can inherit from a deque by suppressing two functions.
Go deeper
- In Java
extendsgives both,implementsgives subtyping only.
Run the thought experiment: what could a caller do with a Deque variable holding a stack?
No. extends would make the stack a subtype of Deque, so it inherits insertRear and deleteRear and can be misused as a deque. Hold a Deque in a private field and delegate push and pop: the code is re-used and the interface stays small.
Go deeper
- Rule of thumb: favour composition over inheritance; inherit only for a true is-a that can always replace the parent.
7.6Modifiers
static versus instance members. How do you give every object a unique id?The lecture's lastorderid example.
Order a = new Order();
Order b = new Order();
Order c = new Order();
System.out.println(a.getOrderId() + " " + b.getOrderId() + " " + c.getOrderId());1 2 3A static member belongs to the class: one copy, no object needed. An instance member belongs to each object. For ids, keep a private static counter that every constructor increments and copies into the object's own orderid.
Go deeper
- A static method has no
this, so it cannot use instance variables directly. lastorderid++is not atomic: concurrent threads need care (synchronizedorAtomicInteger).
final mean on a variable, a method and a class?Three uses, one idea: "cannot change".
A final variable can be assigned only once (a constant, e.g. Math.PI). A final method cannot be overridden. A final class cannot be extended (String is one).
Go deeper
finalon a reference fixes which object it points to, not the object's contents.final int[] astill allowsa[0] = 99.
public, private, protected, default: and why private methods?Give the four levels and a reason for private helpers.
private: same class only. Default (no modifier): same package. protected: same package plus subclasses. public: everyone. Private methods hold implementation helpers like stack_full() that are not part of the interface, so they can change freely.
Go deeper
- A setter per field can still leave an object inconsistent (31 February). Offer one validating
setDate(d, m, y)instead.
7.7Code-reading questions to expect
All outputs below come from really compiling and running the code. Try each before opening the answer.
Employee e = new Manager("Ravi", 2000, "Meena");
Employee f = new Employee("Asha", 2000);
System.out.println(e.bonus(5) + " " + f.bonus(5));
System.out.println(e instanceof Manager);
System.out.println(f instanceof Manager);150.0 100.0
true
false- Same declared type, same salary, different objects:
e.bonus(5)runsManager.bonus(1.5 times),f.bonus(5)runsEmployee.bonus. instanceoflooks at the runtime object.
Date d1 = new Date(1, 2, 2000);
Date d2 = new Date(1, 2, 2000);
Object o2 = d2;
System.out.println(d1.equals(d2));
System.out.println(d1.equals(o2));true
falseequals(Date)is chosen when the static type of the argument isDate:true.- With an
Object-typed argument only the inheritedequals(Object), which is==, fits:false.
Counter a = new Counter();
Counter b = new Counter();
a.hit();
a.hit();
b.hit();
System.out.println(a.mine + " " + b.mine + " " + Counter.total);2 1 3mineis per object: 2 and 1.totalis shared by the class: 3.
LeakyStack s = new LeakyStack();
s.insertFront(1);
s.insertFront(2);
s.insertRear(99);
Deque d = s;
System.out.println(d.deleteRear());99LeakyStackinherits all four operations, so the "stack" acceptsinsertRear, anddeleteRearreturns the99that a real stack could never have put at the bottom. It is a subtype ofDequewhether we wanted that or not.
Week 3 Summary Sheet
Week 3 on one page: a lecture-by-lecture glance, key terms, the pairs to tell apart, the errors you can now read, a checklist and corrections to the source notes.
8.1Week 3 at a glance
Data first, then algorithms. Nouns are objects, verbs are methods. Design behaviour, state, encapsulation, identity.
extends inherits fields and methods; private stays hidden; super(...) builds the parent part.
Overriding picks the method from the runtime type; overloading from the static types. Cast with instanceof.
No multiple inheritance, a tree under Object. equals(Object), toString(), hashCode().
Subtyping = capabilities, inheritance = code. implements gives the first, extends both, composition only re-use.
public/private encapsulate, static belongs to the class, final cannot change.
8.2Key terms, lecture by lecture
- Behaviour
- The methods that operate on the objects.
- State
- The information in the instance variables.
- Identity
- What distinguishes two objects of the same class.
- Dependence / coupling
- One class needs another; keep it low.
- Aggregation
- One object contains others.
- extends
- Makes a class a subclass of another.
- super
- The parent part:
super(...)constructor,super.m()method. - Subclass / parent
- The specialised class / the class it extends.
- Override
- Redefine an inherited method, same signature.
- Dynamic dispatch
- The runtime type picks the method.
- Signature
- Name plus argument types.
- Overload
- Same name, different signatures, chosen at compile time.
- instanceof
- Tests the runtime class of an object.
- Object
- The root class of every class.
- equals()
- Pointer equality by default; override with an
Objectparameter. - toString()
- Describes the state; used by
printlnand+. - hashCode()
- Must agree with
equals.
- Subtyping
- Capabilities are a superset: usable wherever the main type is.
- Inheritance
- Re-use of code.
- Composition
- Hold an object in a private field and delegate.
- public / private
- Visible to all / only to the class.
- static
- Belongs to the class; exists without objects.
- final
- Cannot be reassigned / overridden / extended.
8.3The pairs you must tell apart
8.4Messages and errors you can now read
| What went wrong | What you will see | When |
|---|---|---|
subclass constructor without a usable super(...) | error: constructor Employee in class Employee cannot be applied to given types; | compile time |
| subclass touching the parent's private field | error: name has private access in Employee | compile time |
| child variable given a parent object | error: incompatible types: Employee cannot be converted to Manager | compile time |
| calling a subclass-only method through a parent variable | error: cannot find symbol | compile time |
| override with weaker access | error: bonus(float) in Manager cannot override bonus(float) in Employee | compile time |
@Override on something that overrides nothing | error: method does not override or implement a method from a supertype | compile time |
5.0 passed to a float parameter | error: incompatible types: possible lossy conversion from double to float | compile time |
| extending two classes | error: '{' expected | compile time |
| two interfaces with the same default method | error: types I1 and I2 are incompatible; | compile time |
overriding a final method | overridden method is final | compile time |
extending a final class | error: cannot inherit from final Employee | compile time |
assigning a final variable again | error: cannot assign a value to final variable a | compile time |
| instance variable used in a static method | error: non-static variable count cannot be referenced from a static context | compile time |
| top-level class marked private | error: modifier private not allowed here | compile time |
| cast to a class the object is not | java.lang.ClassCastException: class Employee cannot be cast to class Manager | run time |
| popping an empty stack | java.lang.IllegalStateException: stack is empty | run time |
8.5Last-minute checklist
- Algorithms first vs data first
- Nouns are objects, verbs are methods
- Behaviour, state, encapsulation, identity
- Dependence, aggregation, inheritance
extendsand what is inherited- Private is hidden from the subclass
super(...)first in a constructor- Parent variable can hold a child, not vice versa
- Overriding rules,
@Override - Static type checked, runtime type dispatched
- Overloading vs overriding
- Casting and
instanceof
- No multiple inheritance; the diamond
- Everything extends
Object toString()equals(Object)vs==, andhashCode
- Subtyping is capabilities, inheritance is code
- Deque is the subtype of Stack and Queue
implementsvsextendsvs composition
- Private fields, public methods, private helpers
- One validating setter beats many setters
static: one per class,lastorderidfinalon variable, method, class
8.6Corrections made to the source notes
| Where | The source says | Correct |
|---|---|---|
Lecture 2, Employee | public String getSalary() | a salary is a double: public double getSalary() |
Lecture 3, Manager | public class Manager { | needs extends Employee |
| Lecture 3, the loop | emparray[i].bonus(5.0) | does not compile for a float parameter: use 5.0f (checked by running) |
| Lecture 3, overriding | "choice is static" | overriding is resolved at run time (dynamic dispatch) |
Lecture 4, find | objarr.length() | objarr.length: a field (checked by running) |
| Lecture 4, equality | "x == y invokes x.equals(y)" | false: == always compares references. Call equals yourself (checked by running) |
| Lecture 4, typos | m1.equals()m2 | m1.equals(m2) |
Lecture 6, push | top += 1 | missing semicolon: top += 1; |
nouns and verbs
coupling, aggregation
private stays hidden
Parent p = new Child()
overload: compile time
cast + instanceof
equals, toString
no multiple inheritance
implements / extends
composition
static: per class
final: no change