Inheritance
Seoul National University of Science and Technology
Information Technology Management
Lecture slides index
June 2, 2025
extends
and super
protected
Scanner
objects.Object
, which means that every class we create has at its disposal all the methods defined in the Object
class.Object
function, they must be overriden by defining a new implementation for them in the newly created class.equals
and hashCode
, among others, from the Object
class.Note
Object
, but it’s also possible to derive from other classes.ArrayList
has the superclass AbstractList
.AbstractList
, in turn, has the class Object
as its superclass.java.lang.Object │ └── java.util.AbstractCollection<E> │ └── java.util.AbstractList<E> │ └── java.util.ArrayList<E>
ArrayList
class derives from the class AbstractList
, and indirectly derives from the classes AbstractCollection
and Object
.ArrayList
has at its disposal all the variables and methods of the classes AbstractList
, AbstractCollection
, and Object
.extends
to inherit the properties of a class.Part
, which defines the identifier, the manufacturer, and the description.public class Part {
private String identifier;
private String manufacturer;
private String description;
public Part(String identifier, String manufacturer, String description) {
this.identifier = identifier;
this.manufacturer = manufacturer;
this.description = description;
}
public String getIdentifier() {
return identifier;
}
public String getDescription() {
return description;
}
public String getManufacturer() {
return manufacturer;
}
}
Engine
, without using inheritance, would be this.public class Engine {
private String engineType;
private String identifier;
private String manufacturer;
private String description;
public Engine(String engineType, String identifier, String manufacturer, String description) {
this.engineType = engineType;
this.identifier = identifier;
this.manufacturer = manufacturer;
this.description = description;
}
public String getEngineType() {
return engineType;
}
public String getIdentifier() {
return identifier;
}
public String getDescription() {
return description;
}
public String getManufacturer() {
return manufacturer;
}
}
Engine
and Part
.Engine
is a special case of Part
.Part
does not have, which in this case means the engine type.Engine
and, this time, use inheritance in our implementation.Engine
which inherits the class Part
: an engine is a special case of a part.public class Engine extends Part {
private String engineType;
public Engine(String engineType, String identifier, String manufacturer, String description) {
super(identifier, manufacturer, description);
this.engineType = engineType;
}
public String getEngineType() {
return engineType;
}
}
public class Engine extends Part
indicates that the class Engine
inherits the functionality of the class Part
.engineType
in the class Engine
.super
to call the constructor of the superclass.super(identifier, manufacturer, description)
calls the constructor public Part(String identifier, String manufacturer, String description)
which is defined in the class Part.engineType
.Note
super
call bears some resemblance to the this
call in a constructorthis
is used to call a constructor of this class, while super
is used to call a constructor of the superclass.super
in it, the super
call must be on the first line of the constructor.this
(must also be the first line of the constructor).Engine
extends the class Part
, it has at its disposal all the methods that the class Part
offers.Engine
the same way you can of any other class.Engine engine = new Engine("combustion", "hz", "volkswagen", "VW GOLF 1L 86-91");
System.out.println(engine.getEngineType());
System.out.println(engine.getManufacturer());
Sample Output
Create the following three classes:
Class A
. Class should have no object variables nor should you specify a constructor for it. It only has the method public void a()
, which prints a string “A”.
Class B
. Class should have no object variables nor should you specify a constructor for it. It only has the method public void b()
, which prints a string “B”.
Class C
. Class should have no object variables nor should you specify a constructor for it. It only has the method public void c()
, which prints a string “C”.
If a method or variable has the access modifier private, it is visible only to the internal methods of that class.
Subclasses will not see it, and a subclass has no direct means to access it.
So, from the Engine
class there is no way to directly access the variables identifier, manufacturer, and description, which are defined in the superclass Part
.
The programmer cannot access the variables of the superclass that have been defined with the access modifier private.
A subclass sees everything that is defined with the public modifier in the superclass.
If we want to define some variables or methods that are visible to the subclasses but invisible to everything else, we can use the access modifier protected to achieve this.
You use the keyword super to call the constructor of the superclass.
The call receives as parameters the types of values that the superclass constructor requires.
If there are multiple constructors in the superclass, the parameters of the super call dictate which of them is used.
When the constructor (of the subclass) is called, the variables defined in the superclass are initialized.
The events that occur during the constructor call are practically identical to what happens with a normal constructor call.
If the superclass doesn’t provide a non-parameterized constructor, there must always be an explicit call to the constructor of the superclass in the constructors of the subclass.
this
and super
public class Superclass {
private String objectVariable;
public Superclass() {
this("Example");
}
public Superclass(String value) {
this.objectVariable = value;
}
public String toString() {
return this.objectVariable;
}
}
this
and super
.Superclass
includes an object variable and two constructors.this
keyword.Subclass
includes a parameterized constructor, but it has no object variables.Subclass
calls the parameterized constructor of the Superclass
.Superclass sup = new Superclass();
Subclass sub = new Subclass();
System.out.println(sup);
System.out.println(sub);
Sample Output
super
, just as you can call the methods defined in this class by prefixing the call with this
.toString
method, you can call the superclass’s definition of that method in the following manner:Person
.Person minji = new Person("Kim Minji", "123 Gangnam-daero, Gangnam-gu, Seoul 06035");
Person david = new Person("David Miller", "22 Itaewon-ro, Yongsan-gu, Seoul 04391");
System.out.println(minji);
System.out.println(david);
Sample Output
Student
, which inherits the class Person
.Student ollie = new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
System.out.println(ollie);
System.out.println("Study credits " + ollie.credits());
ollie.study();
System.out.println("Study credits " + ollie.credits());
Sample Output
Student
inherits the toString
method from the class Person
.toString
method specifically for the Student
class.Student ollie = new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
System.out.println(ollie);
ollie.study();
System.out.println(ollie);
Sample Output
Teacher
, which inherits the class Person
.Teacher ada = new Teacher("Ada Lovelace", "12 Teheran-ro 5-gil, Gangnam-gu, Seoul 06134", 3000000);
Teacher michael = new Teacher("Michael Smith", "77 Mapo-daero, Mapo-gu, Seoul 04158", 6200000);
System.out.println(ada);
System.out.println(michael);
Student minji = new Student("Kim Minji", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
int i = 0;
while (i < 25) {
minji.study();
i = i + 1;
}
System.out.println(minji);
Sample Output
public static void printPersons(ArrayList<Person> persons)
in the Main class.main
method:public static void main(String[] args) {
ArrayList<Person> persons = new ArrayList<Person>();
persons.add(new Teacher("Ada Lovelace", "12 Teheran-ro 5-gil, Gangnam-gu, Seoul 06134", 3000000));
persons.add(new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982"));
printPersons(persons);
}
Sample Output
Student
earlier.Student
type object is stored in a Person
type variable, only the methods defined in the Person
class (and its superclass and interfaces) are available:Person ollie = new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
ollie.credits(); // DOESN'T WORK!
ollie.study(); // DOESN'T WORK!
System.out.println(ollie); // ollie.toString() WORKS
In the last exercise we wrote a new toString
implementation for Student
to override the method that it inherits from Person
.
The class Person
had already overriden the toString
method it inherited from the class Object
.
If we handle an object by some other type than its actual type, which version of the object’s method is called?
In the following example, we’ll have two students that we refer to by variables of different types.
Which version of the toString method will be executed: the one defined in Object
, Person
, or Student
?
Student ollie = new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
System.out.println(ollie);
Person olliePerson = new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
System.out.println(olliePerson);
Object ollieObject = new Student("Ollie", "45 Donggyo-ro, Mapo-gu, Seoul 03982");
System.out.println(ollieObject);
Object alice = new Student("Alice", "101 Yeoksam-ro, Gangnam-gu, Seoul 06235");
System.out.println(alice);
Sample Output
Ollie
45 Donggyo-ro, Mapo-gu, Seoul 03982
credits 0
Ollie
45 Donggyo-ro, Mapo-gu, Seoul 03982
credits 0
Ollie
45 Donggyo-ro, Mapo-gu, Seoul 03982
credits 0
Alice
101 Yeoksam-ro, Gangnam-gu, Seoul 06235
credits 0
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int manhattanDistanceFromOrigin() {
return Math.abs(x) + Math.abs(y);
}
protected String location(){
return x + ", " + y;
}
@Override
public String toString() {
return "(" + this.location() + ") distance " + this.manhattanDistanceFromOrigin();
}
}
Tip
location
method is not meant for external use, which is why it is defined as protected.Point
.toString
method calls the toString
method of the superclass and adds to it the color of the point.Point
and ColorPoint
objectstoString
to be executed is determined by the actual type of the point, even though the list knows all the points by the Point
type.public class Main {
public static void main(String[] args) {
ArrayList<Point> points = new ArrayList<>();
points.add(new Point(4, 8));
points.add(new ColorPoint(1, 1, "green"));
points.add(new ColorPoint(2, 5, "blue"));
points.add(new Point(0, 0));
for (Point p: points) {
System.out.println(p);
}
}
}
Point
.public class Point3D extends Point {
private int z;
public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
protected String location() {
return super.location() + ", " + z; // the resulting string has the form "x, y, z"
}
@Override
public int manhattanDistanceFromOrigin() {
// first ask the superclass for the distance based on x and y
// and add the effect of the z coordinate to that result
return super.manhattanDistanceFromOrigin() + Math.abs(z);
}
@Override
public String toString() {
return "(" + this.location() + ") distance " + this.manhattanDistanceFromOrigin();
}
}
location
, manhattanDistanceFromOrigin
, and toString
so that they also account for the third dimension.Point
public class Main {
public static void main(String[] args) {
ArrayList<Point> points = new ArrayList<>();
points.add(new Point(4, 8));
points.add(new ColorPoint(1, 1, "green"));
points.add(new ColorPoint(2, 5, "blue"));
points.add(new Point3D(5, 2, 8));
points.add(new Point(0, 0));
for (Point p: points) {
System.out.println(p);
}
}
}
toString
method in Point3D
is exactly the same as the toString
of Point
.toString
?Point3D
Point3D
class is refined into this:public class Point3D extends Point {
private int z;
public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
protected String location() {
return super.location() + ", " + z;
}
@Override
public int manhattanDistanceFromOrigin() {
return super.manhattanDistanceFromOrigin() + Math.abs(z);
}
}
toString
?toString
method of a three-dimensional point?toString
in the class Point3D
. It does not exist, so the superclass is next to be examined.toString
in the superclass Point
.It can be found, so the code inside the implementation of the method is executed
return "("+this.location()+") distance "+this.manhattanDistanceFromOrigin();
location
is executed firstlocation
in the class Point3D. It can be found, so its code is executed.location
calls the location
of the superclass to calculate the resultmanhattanDistanceFromOrigin
in the Point3D class. It’s found and its code is then executedCar
with the class Part
(or Engine
) would be incorrect.Important
Customer
includes the information related to a customer, and the class Order
that inherits from the Customer
class and includes the information about the ordered item.public class Customer {
private String name;
private String address;
public Customer(String name, String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Customer
Customer
includes the information related to a customer, and the class Order
that inherits from the Customer
class and includes the information about the ordered item.public class Order extends Customer {
private String product;
private String count;
public Order(String product, String count, String name, String address) {
super(name, address);
this.product = product;
this.count = count;
}
public String getProduct() {
return product;
}
public String getCount() {
return count;
}
public String postalAddress() {
return this.getName() + "\n" + this.getAddress();
}
}
Order
class is responsible both for maintaining the customer information and the order information.Order
class.Order
Order
class so that it includes a reference to a Customer
object.public class Order {
private Customer customer;
private String product;
private String count;
public Order(Customer customer, String product, String count) {
this.customer = customer;
this.product = product;
this.count = count;
}
public String getProduct() {
return product;
}
public String getCount() {
return count;
}
public String postalAddress() {
return this.customer.getName() + "\n" + this.customer.getAddress();
}
}
Order
class is better.postalAddress
uses the customer reference to obtain the postal address instead of inheriting the class Customer
.The exercise template contains a class Warehouse
, which has the following constructors and methods:
public Warehouse(double capacity) - Creates an empty warehouse, which has the capacity provided as a parameter; an invalid capacity (<=0) creates a useless warehouse, with the the capacity 0.
public double getBalance() - Returns the balance of the warehouse, i.e. the capacity which is taken up by the items in the warehouse.
public double getCapacity() - Returns the total capacity of the warehouse (i.e. the one that was provided in the constructor).
public double howMuchSpaceLeft() - Returns a value telling how much space is left in the warehouse.
public void addToWarehouse(double amount) - Adds the desired amount to the warehouse; if the amount is negative, nothing changes, and if everything doesn’t fit, then the warehouse is filled up and the rest is “thrown away” / “overflows”.
public double takeFromWarehouse(double amount) - Take the desired amount from the warehouse. The method returns much we actually get. If the desired amount is negative, nothing changes and we return 0. If the desired amount is greater than the amount the warehouse contains, we get all there is to take and the warehouse is emptied.
public String toString() - Returns the state of the object represented as a string like this balance = 64.5, space left 123.5
public class Warehouse {
private double capacity;
private double balance;
public Warehouse(double capacity) {
if (capacity > 0.0) {
this.capacity = capacity;
} else {
this.capacity = 0.0;
}
this.balance = 0.0;
}
public double getBalance() {
return this.balance;
}
public double getCapacity() {
return this.capacity;
}
public double howMuchSpaceLeft() {
return this.capacity - this.balance;
}
public void addToWarehouse(double amount) {
if (amount < 0) {
return;
}
if (amount <= howMuchSpaceLeft()) {
this.balance = this.balance + amount;
} else {
this.balance = this.capacity;
}
}
public double takeFromWarehouse(double amount) {
if (amount < 0) {
return 0.0;
}
if (amount > this.balance) {
double allThatWeCan = this.balance;
this.balance = 0.0;
return allThatWeCan;
}
this.balance = this.balance - amount;
return amount;
}
public String toString() {
return "balance = " + this.balance + ", space left " + howMuchSpaceLeft();
}
}
The class Warehouse
handles the functions related to the amount of a product.
Now we want a product name for the product and a way to handle the name.
Let’s write ProductWarehouse as a subclass of Warehouse!
First, we’ll just create a private object variable for the product name, a constructor, and a getter for the name
field:
public ProductWarehouse(String productName, double capacity) - Creates an empty product warehouse. The name of the product and the capacity of the warehouse are provided as parameters.
public String getName() - Returns the name of the product.
Important
Remind yourself of how a constructor can run the constructor of the superclass as its first action!
ProductWarehouse juice = new ProductWarehouse("Juice", 1000.0);
juice.addToWarehouse(1000.0);
juice.takeFromWarehouse(11.3);
System.out.println(juice.getName()); // Juice
System.out.println(juice); // balance = 988.7, space left 11.3
Sample Output
toString()
inherited by the ProductWarehouse
object doesn’t (obviously!) know anything about the product name.Juice: balance = 64.5, space left 123.5
toString()
method could be written using the getters inherited from the superclass, which would give access to values of inherited, but still hidden fields.toString()
.Important
Remind yourself of how to call an overridden method in a subclass!
ProductWarehouse juice = new ProductWarehouse("Juice", 1000.0);
juice.addToWarehouse(1000.0);
juice.takeFromWarehouse(11.3);
System.out.println(juice.getName()); // Juice
juice.addToWarehouse(1.0);
System.out.println(juice); // Juice: balance = 989.7, space left 10.299999999999955
Sample Output
Sometimes it might be useful to know how the inventory of a product changes over time: Is the inventory often low?
Thus we should give the ProductWarehouse
class the ability to remember the changes in the amount of a product.
Let’s begin by creating a tool that aids in the desired functionality.
The storing of the change history could of course have been done using an ArrayList<Double>
object in the class ProductWarehouse
, however, we want our own specialized tool for this purpose.
The tool should be implemented by encapsulating the ArrayList<Double>
object.
Public constructors and methods of the ChangeHistory
class:
ChangeHistory
object.ChangeHistory
class by adding analysis methods:
ProductWarehouseWithHistory
as a subclass of ProductWarehouse
.ChangeHistory
object.[0.0, 119.2, 21.2]
. Use the string representation of the ChangeHistory object as is.// the usual:
ProductWarehouseWithHistory juice = new ProductWarehouseWithHistory("Juice", 1000.0, 1000.0);
juice.takeFromWarehouse(11.3);
System.out.println(juice.getName()); // Juice
juice.addToWarehouse(1.0);
System.out.println(juice); // Juice: balance = 989.7, space left 10.3
// etc
// however, history() still doesn't work properly:
System.out.println(juice.history()); // [1000.0]
// so we only get the initial state of the history set by the constructor...
Sample Output
Warehouse
class, but we also record the changed state to the history. NB: the value recorded in the history should be the warehouse’s balance after adding, not the amount added!Warehouse
class, but we also record the changed state to the history. NB: the value recorded in the history should be the warehouse’s balance after removing, not the amount removed!Important
Remember how an overriding method can take advantage of the overridden method!
// the usual:
ProductWarehouseWithHistory juice = new ProductWarehouseWithHistory("Juice", 1000.0, 1000.0);
juice.takeFromWarehouse(11.3);
System.out.println(juice.getName()); // Juice
juice.addToWarehouse(1.0);
System.out.println(juice); // Juice: balance = 989.7, space left 10.3
// etc
// and now we have the history:
System.out.println(juice.history()); // [1000.0, 988.7, 989.7]
ProductWarehouseWithHistory juice = new ProductWarehouseWithHistory("Juice", 1000.0, 1000.0);
juice.takeFromWarehouse(11.3);
juice.addToWarehouse(1.0);
//System.out.println(juice.history()); // [1000.0, 988.7, 989.7]
juice.printAnalysis();
Product
warehouse exercise.ProductWarehouseWithHistory
class inherits the ProductWarehouse
class, which, in turn, inherits the Warehouse
class.ChangeHistory
is a separate class connected to the ProductWarehouse
.ProductWarehouseWithHistory
knows about the ChangeHistory
but the ChangeHistory
does not know about the ProductWarehouseWithHistory
.public abstract class *NameOfClass*
; an abstract method is defined by public abstract returnType nameOfMethod
.Operation
, which offers a structure for operations and executing them.Operation
Operation
works as a basis for implementing different actions.Operation
class in the following manner.public class PlusOperation extends Operation {
public PlusOperation() {
super("PlusOperation");
}
@Override
public void execute(Scanner scanner) {
System.out.print("First number: ");
int first = Integer.valueOf(scanner.nextLine());
System.out.print("Second number: ");
int second = Integer.valueOf(scanner.nextLine());
System.out.println("The sum of the numbers is " + (first + second));
}
}
Operation
classOperation
have also the type Operation
, we can create a user interface by using Operation
type variables.UserInterface
that contains a list of operations and a scanner.public class UserInterface {
private Scanner scanner;
private ArrayList<Operation> operations;
public UserInterface(Scanner scanner) {
this.scanner = scanner;
this.operations = new ArrayList<>();
}
public void addOperation(Operation operation) {
this.operations.add(operation);
}
public void start() {
while (true) {
printOperations();
System.out.println("Choice: ");
String choice = this.scanner.nextLine();
if (choice.equals("0")) {
break;
}
executeOperation(choice);
System.out.println();
}
}
private void printOperations() {
System.out.println("\t0: Stop");
int i = 0;
while (i < this.operations.size()) {
String operationName = this.operations.get(i).getName();
System.out.println("\t" + (i + 1) + ": " + operationName);
i = i + 1;
}
}
private void executeOperation(String choice) {
int operation = Integer.valueOf(choice);
Operation chosen = this.operations.get(operation - 1);
chosen.execute(scanner);
}
}
UserInterface userInterface = new UserInterface(new Scanner(System.in));
userInterface.addOperation(new PlusOperation());
userInterface.start();
Computer Language