Arvutiteaduse instituut
  1. Kursused
  2. 2024/25 kevad
  3. Objektorienteeritud programmeerimine (uus) (P2NC.01.083)
EN
Logi sisse

Objektorienteeritud programmeerimine (uus) 2024/25 kevad

Intro

Week 5

Week 6

Week 5

Interfaces, polymorphism

Topics

Interfaces, polymorphism

Upon completion of this practical lesson, students will be able to explain the following concepts:

  • Polymorphism
  • Interface
  • Object identity
  • Examples of Built-in Java Interfaces

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:

  1. 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.
  2. 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.
  3. 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:

  1. 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.
  2. 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.
  3. Multiple Classes Implementing the Same Interface: Different classes can implement the same interface (e.g., Tax and DepositInterest).
  4. 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 { ... }.
  5. 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 keyword public 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:

  1. Create an interface named GpsCalculator and define a method canMakeTrip(double distance) inside it.
  2. Modify the Car, Motorcycle, and Bus classes to implement the GpsCalculator interface.
  3. Create a separate Calculator.java file with public class Calculator. In this file create method named calculateAll(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") named brand
  • A license plate (type String, for example, "123ABC") named licensePlate

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 returns double 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 and Bus 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

Please leave your feedback on the materials

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused