Week 5
Interfaces, polymorphism
Topics
Interfaces, polymorphism
Upon completion of this practical lesson, students will be able to explain the following concepts:
Study new commands:
interface
implements
new
And solve exercises:
- Exercise 1: Make a trip with a bus
- Exercise 2: Implementing GPS system for multiple vehicles
- Exercise 3: Enhancing vehicle identification in GPS system
- Exercise 4: Extending the GPS system with a new method
All exercise solutions must be uploaded to a page in Moodle.
Definition
Polymorphism
Polymorphism in Java means using the same method name to do different tasks depending on the actual object/class.
For example, both a Printer and a Scanner can have a start() method — but the printer starts printing, while the scanner starts scanning.
Official java documentation definition
Reminder
Method – topic of the second week (link)
Class – topic of the third week (link)
Polymorphism is a broad concept in object-oriented programming that can happen in several ways. Throughout this and the following week, different types of polymorphism will be explored. The most common types include:
- Through Interfaces (Implementing an Interface)
- Different classes implement the same interface but each class writes its own version of the method.
- Through Class Inheritance (Method Overriding)
- Child classes override methods from a parent class to provide unique behavior.
- Through Method Overloading (Ad-hoc Polymorphism)
- Having multiple methods with the same name but different parameters within the same class.
Today we will focus on the Polymorphism through Interfaces. Let's start with code that builds on the knowledge we learned from previous weeks.
Users want to know if their vehicle can cover the distance for their trip. The costs(l/100km) and quantity(l) of fuel and the number of passengers are known. To find the answer to the question “will the vehicle travel the declared distance?”, the formula is used. The car's consumption increases for each passenger (100 ml per passenger).
public class Car { private int passengerCapacity; private double fuelCapacity; private double consumptionPer100km; public Car(int passengerCapacity, double fuelCapacity, double consumptionPer100km) { this.passengerCapacity = passengerCapacity; this.fuelCapacity = fuelCapacity; this.consumptionPer100km = consumptionPer100km; } public boolean canMakeTrip(double distance) { double extraConsumption = 0.1 * passengerCapacity; double totalConsumptionPer100 = consumptionPer100km + extraConsumption; double fuelNeeded = (distance / 100.0) * totalConsumptionPer100; return fuelNeeded <= fuelCapacity; } }
public class Motorcycle { private double fuelCapacity; private double consumptionPer100km; public Motorcycle (double fuelCapacity, double consumptionPer100km) { this.fuelCapacity = fuelCapacity; this.consumptionPer100km = consumptionPer100km; } public boolean canMakeTrip(double distance) { double fuelNeeded = (distance / 100.0) * consumptionPer100km; return fuelNeeded <= fuelCapacity; } }
Exercise 1: Completing a Trip with a Bus
Create a new class named Bus
. The constructor should accept parameters for passengerCapacity
, fuelCapacity
and consumptionPer100km
. Within this class, define a method named canMakeTrip(double distance)
. This method must calculate additional fuel consumption based on the number of passengers—specifically, an increase of 100 ml per 100 km for every passenger. After adding this additional consumption and the base consumptionPer100km
, determine whether the total fuel required for the specified distance does not exceed the fuelCapacity
.
Reminder
Types and arrays – topic of the second week (link)
Types of objects with a similar purpose
When developing software, we might come across a challenge: creating a method in a separate class that takes an array of different types of vehicles and checks whether they can travel a given distance. However, implementing this method can be tricky because an array can’t hold more than one type of variables. This means we would constantly have to modify the method to accommodate new types. Even though all vehicles share the same canMakeTrip()
method, we can’t simply store them in a single array and run a unified check on all of them at once.
The reason for this limitation lies in Java’s syntax. Arrays in Java must have a specific type—whether it's Car
, Motorcycle
, Bus
, or any other class—but not multiple types at the same time. This restriction makes it impossible to create a single array containing different vehicle types and process them together in a straightforward way.
This is where interfaces come in. Interfaces allow us to group different classes based on shared functionality rather than specific types. By defining a common interface for all vehicle types that includes the canMakeTrip()
method, we can work with arrays of objects that implement this interface, eliminating the need to modify the method every time a new vehicle type is introduced.
Definition
Interface
An interface in Java is like a blueprint or rule list that only describes what methods a class should have, but not how those methods do their task. Think of it as a list of method names (like instructions) that any class agreeing to this interface must provide an actual implementation for. Because all classes implementing the same interface share the same set of methods, they can be used in the same way (for example, stored together in the same array).
Official java documentation definition
Remember:
- Absence of Method Bodies: An interface typically contains only method signatures—comprising method names, parameters, and return types—without any detailed implementation or method bodies.
- Contracts for Classes: When a class implements an interface, it promises to write the methods (both the definition and the body) described in the interface.
- Grouping of Similar Methods: Interfaces commonly group related methods, enabling different classes to adhere to a standardized set of method declarations. This ensures consistency in how these classes can be utilized.
Interface creation is similar to creating Classes. For example in Intellij IDEA:
New --> Java Class --> Interface
Let's explore the concept of interfaces using a common problem that many young students encounter: banking operations. In a banking system, various operations—such as tax calculations, interest on deposits — both involve some form of financial computation.
Although these operations differ in nature, they can be unified under a common method, such as calculate()
, which will handle the necessary computations for each specific case.
New commands: interface
, implements
, new
interface FinancialOperation { double calculate(); }
If a class has all the methods described in an interface, it is called „implements“ an interface.
Implementation for tax (20% of the amount):
class Tax implements FinancialOperation { private int ammoutToBeTaxed; public Tax(int ammoutToBeTaxed){ this.ammoutToBeTaxed = ammoutToBeTaxed; } public double calculate() { return ammoutToBeTaxed * 0.2; } public void showWhatTax(){ System.out.println("The tax is 20%"); } }
Implementation for interest on deposit (5% per year):
class DepositInterest implements FinancialOperation { private int amountToDeposit; public DepositInterest(int amountToDeposit) { this.amountToDeposit = amountToDeposit; } public double calculate() { return amountToDeposit * 0.05; } }
When we write class Tax implements FinancialOperation
, we are telling Java that Tax
will include all the methods that are listed in the FinancialOperation
interface. The creator of the class promises that the class created will have all the methods described in the interface. If this is not the case, the compiler refuses to compile the class. In return, the compiler permits the use of instances of the created class where the variable type is expected to be the interface type instead
public class Main { public static void main(String[] args) { Tax myTax = new Tax(100); DepositInterest myDepositInterest = new DepositInterest(10000); FinancialOperation[] allOperationsToDo = {myTax, myDepositInterest}; for (FinancialOperation financialOperation : allOperationsToDo) { System.out.println(financialOperation.calculate()); } } }
Try it yourself!
When a class implements an interface, the instance of that class is essentially of several types at the same time - the type of the class itself and the type of the implemented interfaces. This phenomenon is called polymorphism. (The use of interfaces is not the only form of polymorphism.)
Definition
Object identity
In Java, every object is stored in its own memory space.
Imagine two identical keys that open the same kind of lock. They look the same, but they were made separately. They are not the same key, just two identical ones.
This means that even if two objects (like two String
instances) have the same content, they might exist in different memory locations and be considered separate entities. The easiest way to understand this is through an example:
When we write new String("Hello")
, we're creating an entirely new object, even if an identical String with the same text already exists somewhere in memory.
To check whether two variables reference the same object, we use the ==
operator.
To check if their values (contents) are equivalent, we use the .equals()
method.
So, an object's "identity" refers to its unique memory location, which distinguishes it from other objects—even if they "look" identical.
Official java documentation definition
It’s also worth remembering that in Java, we distinguish between a type (the “view” that the compiler sees, such as an interface) and an instance (the actual object created with the new keyword).
For example, considering the code we looked at earlier:
FinancialOperation operation = new Tax(1000); // 'operation' is of type 'FinancialOperation' // but the instance is of class 'Tax' which we can check with command // System.out.println(operation.getClass()); // class Tax
An instance of Tax
is obtained, but its type, as recognized by the compiler, is FinancialOperation
. Consequently, only methods declared within the FinancialOperation
interface can be invoked on the operation instance. Although the underlying object is a Tax
, the compiler restricts method calls exclusively to those methods specified by FinancialOperation
. Methods specific to the Tax
class are inaccessible unless they have also been defined within FinancialOperation
. This indicates:
operation.showWhatTax(); // compile error: cannot resolve method Tax sameTax = operation; // compile error: incompatible types
Why do it this way (use a parent class or an interface as the type of the variable) instead of just writing:
Tax operation = new Tax(1000);
Utilizing interfaces enables the creation of more flexible code. For example, multiple classes (such as Tax
, Discount
, LoanCalculation
, etc.) can implement the FinancialOperation
interface. A single variable declared with the type FinancialOperation
can reference any of these objects, enhancing modularity and making it easier to add new code later. However, if access to methods unique to the Tax class is required, the variable must be explicitly declared as type Tax to access those specific features. Consequently, multiple class instances can be stored within a single array.
Remember:
- An Interface is a Type: An interface may serve as a local variable type, instance field type, method parameter type, and more, similar to a class.
- Instantiation of Interfaces is Not Possible: We can't create an object directly from an interface (e.g.,
new FinancialOperation()
is not compilable), as interfaces contain no method implementations. Instances must instead be created from classes that implement the interface. - Multiple Classes Implementing the Same Interface: Different classes can implement the same interface (e.g.,
Tax
andDepositInterest
). - A Single Class Can Implement Multiple Interfaces: Classes can implement several interfaces simultaneously by listing the interfaces separated by commas. For instance: class TextFile implements Input, Output { ... }.
- Implicitly Public Methods: If an interface method is declared without an explicit access modifier (e.g.,
public
/private
), it is implicitly considered public. Therefore, implementing classes must explicitly declare these methods as public, even if the keywordpublic
is omitted in the interface definition.
Examples of Built-in Java Interfaces
1. java.lang.Comparable Documentation
The Comparable interface defines the compareTo
method for comparing two objects. It is used by Java’s sorting algorithms. Different implementations allow specifying the property and order in which objects should be sorted. This is useful when sorting instances of a custom class.
For example, to make students comparable by age, we can implement the Comparable interface in the Student class:
public class Student implements Comparable<Student> { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public int compareTo(Student other) { if (this.age < other.age) return -1; // Negative value means 'this' is smaller if (this.age > other.age) return 1; // Positive value means 'this' is larger return 0; // Zero means they are equal } }
Now, if we put Student objects in a list and sort them, they will be ordered by age.
2. java.util.List Documentation
The List
interface describes familiar functionality, with methods such as add
, get
, and size
. It is an interface because it can be implemented in different ways depending on which operations need to be optimized.
For example:
ArrayList
allows fast access to elements by index.LinkedList
enables fast insertions and deletions at any position.
Example usage:
import java.util.*; public class ListExample { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("Alice"); names.add("Bob"); names.add("Charlie"); System.out.println(names.get(1)); // Bob names.remove("Alice"); System.out.println(names); // [Bob, Charlie] } }
A similar interface is java.util.Map, which represents key-value pairs.
3. java.lang.Iterable Documentation
The Iterable
interface defines the iterator method, which allows objects to be iterated over. Iterators help navigate through object collections. For example, ArrayList
implements the Iterable
interface.
The for (T element : elements)
syntax in Java does not actually check whether elements is a list or an array but rather whether its type implements Iterable
. This means that any class can be made iterable by implementing this interface.
Exercise 2: Implementing GPS system for multiple vehicles
Developers have decided to create a GPS system that will be installed in every vehicle and will perform calculations for all vehicles simultaneously. To achieve this, follow these steps:
- Create an interface named
GpsCalculator
and define a methodcanMakeTrip(double distance)
inside it. - Modify the
Car
,Motorcycle
, andBus
classes to implement theGpsCalculator
interface. - Create a separate Calculator.java file with
public class Calculator
. In this file create method namedcalculateAll(GpsCalculator[] vehicles, int distance)
that:- Takes an array of vehicles as an argument.
- Prints the calculation results for each vehicle by calling
canMakeTrip(int distance)
. - Returns the total number of vehicles used in the calculation.
Expected Outcome
When calling calculateAll()
, the system should display whether each vehicle can complete the trip and return the total number of vehicles in the computation.
Possible print value:
- Vehicle 1 can make a trip of 10 km
- Vehicle 2 can not make a trip of 10 km
Where 1 and 2 are indexes of the car in the array and 10 is the value of int distance
. To complete automatic check in moodle print values have to contain index of vehicle (which always starts with 1), word can or cannot and value of distance
.
The created method can be tested within the main method in the same file.
Exercise 3: Enhancing vehicle identification in GPS system
Developers encountered a new problem: it is not visually clear which vehicle is being calculated. To solve this, each vehicle should have a brand (manufacturer name) and a license plate (formatted as 123ABC with exactly 3 digits and 3 uppercase letters).
Task:
1. Create a new constructor with the same name in each vehicle class (Car
, Motorcycle
, and Bus
) that includes the following additional parameters:
- A brand (type
String
, for example, "Audi") namedbrand
- A license plate (type
String
, for example, "123ABC") namedlicensePlate
Remember that creating a constructor with the same name but different parameters is an example of compile-time (static) polymorphism. In other words, the Java compiler decides which constructor to use based on the parameters you pass.
Note: Since the attributes are declared as private
, it is necessary to implement corresponding getter methods (e.g.,
String getBrand()
and String getLicensePlate()
). Additionally, because the method uses an array parameter of type GpsCalculator
, these methods must also be declared within the GpsCalculator
interface.
2. Modify the calculateAll(GpsCalculator[] vehicles, int distance)
method:
- Update the print statements to include brand and license plate.
- Expected output should be:
- Vehicle 1: Audi with license plate 123ABC can make a trip of 500km
- Vehicle 2: Scania with license plate 456DEF cannot make a trip of 50km
To complete automatic check in moodle this time print values in addition to index of vehicle (which always starts with 1), word can or cannot and value of distance
also have to contain values of brand
and licensePlate
.
Exercise 4: Extending the GPS system with a new method
Developers have decided to extend the GpsCalculator
interface by adding a new method that performs an additional calculation for each vehicle. Each vehicle type will have its own unique way of calculating the maximum possible distance.
Task:
1. Modify the GpsCalculator interface by adding a new method:
calculateMaxDistance()
that returnsdouble
number- This method should return the maximum possible distance the vehicle can travel based on its fuel capacity and consumption.
2. Implement this method in each class (Car
, Motorcycle
, Bus
):
Car
: Consider extra consumption per passenger when computing max distance.Motorcycle
: Uses a simpler calculation as there are no passengers.Bus
: Similar to a car, but with higher passenger impact- For
Car
andBus
extra consumption is the same 0.1 liter per passenger
3. Modify the calculateAll()
method in GpsSystem.java
:
- Display the maximum travelable distance for each vehicle alongside the existing trip check.
- Example output:
- Vehicle Audi with license plate 123ABC can make a trip of 500km and the maximum possible distance on the remaining tank is 620km