
Inheritance
Seoul National University of Science and Technology
Information Technology Management
Lecture slides index
June 2, 2025
extends and superprotectedScanner 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 superpublic 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
Studentinherits 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() WORKSIn 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 0public 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.Pointpublic 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?Point3DPoint3D 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 executed
Car 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;
}
}CustomerCustomer 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.OrderOrder 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.3Sample Output
toString() inherited by the ProductWarehouse object doesn’t (obviously!) know anything about the product name.Juice: balance = 64.5, space left 123.5toString() 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.299999999999955Sample 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.OperationOperation 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