Object-Oriented Programming (OOP) is the cornerstone of Java development. In this codelab, you'll master all essential OOP concepts by building real-world applications that demonstrate the power of objects, inheritance, and polymorphism.
this keywordextends and the super keywordimplements keywordEverything in Java is an object (except primitives). Let's understand the building blocks.
Class: A blueprint/template for creating objects Object: An instance of a class with actual data
Think of it like:
Create a file Person.java:
public class Person {
// Fields (instance variables)
String name;
int age;
String email;
// Method
void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Email: " + email);
}
}
Create PersonDemo.java:
public class PersonDemo {
public static void main(String[] args) {
// Creating objects
Person person1 = new Person();
person1.name = "Alice";
person1.age = 25;
person1.email = "alice@example.com";
Person person2 = new Person();
person2.name = "Bob";
person2.age = 30;
person2.email = "bob@example.com";
// Using objects
person1.displayInfo();
System.out.println();
person2.displayInfo();
}
}
Run it:
$ javac Person.java PersonDemo.java $ java PersonDemo Name: Alice Age: 25 Email: alice@example.com Name: Bob Age: 30 Email: bob@example.com
Constructors initialize objects. They have the same name as the class and no return type.
Update Person.java:
public class Person {
String name;
int age;
String email;
// Default constructor
public Person() {
this.name = "Unknown";
this.age = 0;
this.email = "not.provided@example.com";
}
// Parameterized constructor
public Person(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Constructor with partial parameters
public Person(String name, int age) {
this.name = name;
this.age = age;
this.email = "not.provided@example.com";
}
void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Email: " + email);
}
}
Using constructors:
public class PersonDemo {
public static void main(String[] args) {
Person p1 = new Person(); // Default constructor
Person p2 = new Person("Charlie", 28, "charlie@example.com");
Person p3 = new Person("Diana", 32);
p1.displayInfo();
System.out.println();
p2.displayInfo();
System.out.println();
p3.displayInfo();
}
}
this Keywordthis refers to the current object instance:
public Person(String name, int age, String email) {
this.name = name; // this.name = instance variable
this.age = age; // age = parameter
this.email = email;
}
Constructor chaining with
this()
:
public Person(String name, int age) {
this(name, age, "not.provided@example.com"); // Calls 3-param constructor
}
Control who can access your class members:
Modifier | Class | Package | Subclass | World |
| ✅ | ✅ | ✅ | ✅ |
| ✅ | ✅ | ✅ | ❌ |
default (no modifier) | ✅ | ✅ | ❌ | ❌ |
| ✅ | ❌ | ❌ | ❌ |
Encapsulation with private fields:
public class Person {
private String name;
private int age;
private String email;
public Person(String name, int age, String email) {
this.name = name;
setAge(age); // Use setter for validation
this.email = email;
}
// Getters
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getEmail() {
return email;
}
// Setters with validation
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
}
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
}
}
public void setEmail(String email) {
this.email = email;
}
}
Method overloading allows multiple methods with the same name but different parameters.
Methods can be overloaded by:
NOT by return type alone!
Create Calculator.java:
public class Calculator {
// Add two integers
public int add(int a, int b) {
return a + b;
}
// Add three integers
public int add(int a, int b, int c) {
return a + b + c;
}
// Add two doubles
public double add(double a, double b) {
return a + b;
}
// Add array of integers
public int add(int[] numbers) {
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
// Concatenate strings (still called "add")
public String add(String a, String b) {
return a + b;
}
}
Using overloaded methods:
public class CalculatorDemo {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 3)); // 8
System.out.println(calc.add(5, 3, 2)); // 10
System.out.println(calc.add(5.5, 3.2)); // 8.7
System.out.println(calc.add(new int[]{1, 2, 3, 4})); // 10
System.out.println(calc.add("Hello", " World")); // Hello World
}
}
You've already seen this with Person class constructors!
public Person() { ... }
public Person(String name, int age) { ... }
public Person(String name, int age, String email) { ... }
Inheritance allows a class to inherit properties and methods from another class, promoting code reuse.
extendsCreate the banking system foundation:
Account.java (Parent/Superclass):
public class Account {
protected String accountNumber;
protected String accountHolder;
protected double balance;
public Account(String accountNumber, String accountHolder, double initialBalance) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("Deposited: $" + amount);
System.out.println("New balance: $" + balance);
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
System.out.println("New balance: $" + balance);
} else {
System.out.println("Insufficient funds!");
}
}
public void displayInfo() {
System.out.println("Account Number: " + accountNumber);
System.out.println("Account Holder: " + accountHolder);
System.out.println("Balance: $" + balance);
}
public double getBalance() {
return balance;
}
}
SavingsAccount.java (Child/Subclass):
public class SavingsAccount extends Account {
private double interestRate;
private int withdrawalLimit;
private int withdrawalCount;
public SavingsAccount(String accountNumber, String accountHolder,
double initialBalance, double interestRate) {
super(accountNumber, accountHolder, initialBalance); // Call parent constructor
this.interestRate = interestRate;
this.withdrawalLimit = 3;
this.withdrawalCount = 0;
}
// Method overriding
@Override
public void withdraw(double amount) {
if (withdrawalCount >= withdrawalLimit) {
System.out.println("Withdrawal limit reached for this period!");
return;
}
super.withdraw(amount); // Call parent method
withdrawalCount++;
}
// New method specific to SavingsAccount
public void applyInterest() {
double interest = balance * (interestRate / 100);
balance += interest;
System.out.println("Interest applied: $" + interest);
System.out.println("New balance: $" + balance);
}
public void resetWithdrawalCount() {
withdrawalCount = 0;
System.out.println("Withdrawal count reset.");
}
@Override
public void displayInfo() {
super.displayInfo(); // Call parent method
System.out.println("Interest Rate: " + interestRate + "%");
System.out.println("Withdrawals: " + withdrawalCount + "/" + withdrawalLimit);
}
}
CheckingAccount.java:
public class CheckingAccount extends Account {
private double overdraftLimit;
private double overdraftFee;
public CheckingAccount(String accountNumber, String accountHolder,
double initialBalance, double overdraftLimit) {
super(accountNumber, accountHolder, initialBalance);
this.overdraftLimit = overdraftLimit;
this.overdraftFee = 35.0;
}
@Override
public void withdraw(double amount) {
if (amount > 0 && amount <= (balance + overdraftLimit)) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
if (balance < 0) {
balance -= overdraftFee;
System.out.println("Overdraft fee charged: $" + overdraftFee);
System.out.println("You are in overdraft!");
}
System.out.println("New balance: $" + balance);
} else {
System.out.println("Transaction denied - exceeds overdraft limit!");
}
}
public void displayOverdraftInfo() {
System.out.println("Overdraft Limit: $" + overdraftLimit);
System.out.println("Overdraft Fee: $" + overdraftFee);
}
@Override
public void displayInfo() {
super.displayInfo();
displayOverdraftInfo();
}
}
BankDemo.java:
public class BankDemo {
public static void main(String[] args) {
// Create accounts
SavingsAccount savings = new SavingsAccount("SAV-001", "Alice", 1000.0, 3.5);
CheckingAccount checking = new CheckingAccount("CHK-001", "Bob", 500.0, 200.0);
System.out.println("=== SAVINGS ACCOUNT ===");
savings.displayInfo();
System.out.println();
savings.deposit(500);
System.out.println();
savings.withdraw(200);
savings.withdraw(100);
savings.withdraw(50);
savings.withdraw(25); // Should exceed limit
System.out.println();
savings.applyInterest();
System.out.println();
System.out.println("=== CHECKING ACCOUNT ===");
checking.displayInfo();
System.out.println();
checking.withdraw(600); // Goes into overdraft
System.out.println();
checking.displayInfo();
}
}
super Keywordsuper refers to the parent class:
super(accountNumber, accountHolder, initialBalance);super.withdraw(amount);super.balance (if not private)Rules for overriding:
@Override annotation (recommended)final, static, or private methods@Override
public void withdraw(double amount) {
// Custom implementation
}
Abstract classes provide a template for subclasses but cannot be instantiated themselves.
Use abstract classes when:
Animal.java (Abstract Base Class):
public abstract class Animal {
protected String name;
protected int age;
protected double weight;
public Animal(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
// Abstract method - no implementation
public abstract void makeSound();
// Abstract method
public abstract void move();
// Concrete method - has implementation
public void eat(String food) {
System.out.println(name + " is eating " + food);
weight += 0.1;
}
public void sleep() {
System.out.println(name + " is sleeping... Zzz");
}
public void displayInfo() {
System.out.println("Name: " + name);
System.out.println("Age: " + age + " years");
System.out.println("Weight: " + weight + " kg");
}
// Getters
public String getName() { return name; }
public int getAge() { return age; }
public double getWeight() { return weight; }
}
Dog.java:
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, double weight, String breed) {
super(name, age, weight);
this.breed = breed;
}
@Override
public void makeSound() {
System.out.println(name + " says: Woof! Woof!");
}
@Override
public void move() {
System.out.println(name + " is running on four legs!");
}
public void fetch() {
System.out.println(name + " is fetching the ball!");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Breed: " + breed);
}
}
Cat.java:
public class Cat extends Animal {
private boolean isIndoor;
public Cat(String name, int age, double weight, boolean isIndoor) {
super(name, age, weight);
this.isIndoor = isIndoor;
}
@Override
public void makeSound() {
System.out.println(name + " says: Meow! Meow!");
}
@Override
public void move() {
System.out.println(name + " is walking gracefully!");
}
public void scratch() {
System.out.println(name + " is scratching the furniture!");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Indoor cat: " + (isIndoor ? "Yes" : "No"));
}
}
Bird.java:
public class Bird extends Animal {
private double wingSpan;
public Bird(String name, int age, double weight, double wingSpan) {
super(name, age, weight);
this.wingSpan = wingSpan;
}
@Override
public void makeSound() {
System.out.println(name + " says: Tweet! Tweet!");
}
@Override
public void move() {
System.out.println(name + " is flying through the sky!");
}
public void fly() {
System.out.println(name + " spreads wings (" + wingSpan + "m) and takes off!");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Wing Span: " + wingSpan + " meters");
}
}
AnimalDemo.java:
public class AnimalDemo {
public static void main(String[] args) {
// Cannot do: Animal animal = new Animal(...); // ERROR!
Dog dog = new Dog("Buddy", 3, 25.5, "Golden Retriever");
Cat cat = new Cat("Whiskers", 2, 4.2, true);
Bird bird = new Bird("Tweety", 1, 0.05, 0.3);
System.out.println("=== DOG ===");
dog.displayInfo();
dog.makeSound();
dog.move();
dog.fetch();
dog.eat("dog food");
dog.sleep();
System.out.println();
System.out.println("=== CAT ===");
cat.displayInfo();
cat.makeSound();
cat.move();
cat.scratch();
cat.eat("fish");
System.out.println();
System.out.println("=== BIRD ===");
bird.displayInfo();
bird.makeSound();
bird.move();
bird.fly();
bird.eat("seeds");
}
}
Interfaces define a contract - what a class can do, not how it does it.
Syntax:
public interface InterfaceName {
// Abstract methods (public abstract by default)
void method1();
int method2(String param);
// Default methods (Java 8+)
default void defaultMethod() {
System.out.println("Default implementation");
}
// Static methods (Java 8+)
static void staticMethod() {
System.out.println("Static method");
}
// Constants (public static final by default)
int CONSTANT = 100;
}
Flyable.java:
public interface Flyable {
void fly();
void land();
default void displayFlyingStatus() {
System.out.println("This creature can fly!");
}
}
Swimmable.java:
public interface Swimmable {
void swim();
void dive();
default void displaySwimmingStatus() {
System.out.println("This creature can swim!");
}
}
Walkable.java:
public interface Walkable {
void walk();
void run();
}
Duck.java (implements multiple interfaces):
public class Duck extends Animal implements Flyable, Swimmable, Walkable {
public Duck(String name, int age, double weight) {
super(name, age, weight);
}
@Override
public void makeSound() {
System.out.println(name + " says: Quack! Quack!");
}
@Override
public void move() {
System.out.println(name + " can walk, swim, and fly!");
}
// Flyable interface methods
@Override
public void fly() {
System.out.println(name + " is flying!");
}
@Override
public void land() {
System.out.println(name + " is landing on water!");
}
// Swimmable interface methods
@Override
public void swim() {
System.out.println(name + " is swimming gracefully!");
}
@Override
public void dive() {
System.out.println(name + " is diving for fish!");
}
// Walkable interface methods
@Override
public void walk() {
System.out.println(name + " is waddling on land!");
}
@Override
public void run() {
System.out.println(name + " is running (waddling quickly)!");
}
}
Fish.java:
public class Fish extends Animal implements Swimmable {
private String species;
public Fish(String name, int age, double weight, String species) {
super(name, age, weight);
this.species = species;
}
@Override
public void makeSound() {
System.out.println(name + " makes bubbles... glub glub!");
}
@Override
public void move() {
swim();
}
@Override
public void swim() {
System.out.println(name + " is swimming through water!");
}
@Override
public void dive() {
System.out.println(name + " is diving deeper!");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Species: " + species);
}
}
Penguin.java:
public class Penguin extends Animal implements Swimmable, Walkable {
public Penguin(String name, int age, double weight) {
super(name, age, weight);
}
@Override
public void makeSound() {
System.out.println(name + " says: Honk! Honk!");
}
@Override
public void move() {
System.out.println(name + " waddles and swims!");
}
@Override
public void swim() {
System.out.println(name + " is swimming super fast underwater!");
}
@Override
public void dive() {
System.out.println(name + " is diving deep for fish!");
}
@Override
public void walk() {
System.out.println(name + " is waddling on ice!");
}
@Override
public void run() {
System.out.println(name + " is sliding on its belly!");
}
}
InterfaceDemo.java:
public class InterfaceDemo {
public static void main(String[] args) {
Duck duck = new Duck("Donald", 2, 1.5);
Fish fish = new Fish("Nemo", 1, 0.2, "Clownfish");
Penguin penguin = new Penguin("Pingu", 3, 15.0);
System.out.println("=== DUCK (Flies, Swims, Walks) ===");
duck.displayInfo();
duck.makeSound();
duck.fly();
duck.swim();
duck.walk();
duck.displayFlyingStatus();
duck.displaySwimmingStatus();
System.out.println();
System.out.println("=== FISH (Swims only) ===");
fish.displayInfo();
fish.makeSound();
fish.swim();
fish.dive();
fish.displaySwimmingStatus();
System.out.println();
System.out.println("=== PENGUIN (Swims, Walks, no flying) ===");
penguin.displayInfo();
penguin.makeSound();
penguin.swim();
penguin.walk();
penguin.run();
System.out.println();
// Polymorphism with interfaces
System.out.println("=== POLYMORPHISM DEMO ===");
Swimmable[] swimmers = {duck, fish, penguin};
for (Swimmable swimmer : swimmers) {
if (swimmer instanceof Animal) {
System.out.println(((Animal) swimmer).getName() + " can swim:");
}
swimmer.swim();
}
}
}
Feature | Abstract Class | Interface |
Multiple inheritance | No (single) | Yes (multiple) |
Constructor | Yes | No |
Fields | All types | Only public static final |
Method types | Abstract + Concrete | Abstract + Default + Static |
Access modifiers | All | Public only (methods) |
When to use | IS-A relationship | CAN-DO behavior |
Polymorphism means "many forms" - the ability of objects to take multiple forms.
Create a Zoo Management System:
public class Zoo {
public static void main(String[] args) {
// Polymorphism: Parent reference, child objects
Animal[] animals = {
new Dog("Max", 4, 30.0, "Labrador"),
new Cat("Luna", 3, 5.0, true),
new Bird("Sky", 1, 0.1, 0.4),
new Duck("Daffy", 2, 1.8),
new Fish("Bubbles", 1, 0.3, "Goldfish"),
new Penguin("Skipper", 5, 18.0)
};
System.out.println("=== ZOO FEEDING TIME ===\n");
for (Animal animal : animals) {
System.out.println("--- " + animal.getName() + " ---");
animal.displayInfo();
animal.makeSound(); // Polymorphic call
animal.move(); // Polymorphic call
animal.eat("food");
// Type checking and casting
if (animal instanceof Flyable) {
System.out.println(animal.getName() + " is a flyer!");
((Flyable) animal).fly();
}
if (animal instanceof Swimmable) {
System.out.println(animal.getName() + " is a swimmer!");
((Swimmable) animal).swim();
}
System.out.println();
}
// Demonstrate polymorphic behavior
System.out.println("=== POLYMORPHIC METHOD CALLS ===\n");
feedAnimal(new Dog("Rover", 2, 20.0, "Beagle"));
feedAnimal(new Cat("Mittens", 1, 4.0, false));
feedAnimal(new Bird("Chirpy", 1, 0.08, 0.25));
}
// Polymorphic method - accepts any Animal
public static void feedAnimal(Animal animal) {
System.out.println("Feeding " + animal.getName() + "...");
animal.eat("nutritious food");
System.out.println();
}
}
instanceof OperatorCheck object type at runtime:
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.fetch();
}
// Java 16+ pattern matching
if (animal instanceof Dog dog) {
dog.fetch(); // Automatically cast!
}
Packages organize classes into namespaces and prevent naming conflicts.
Project structure:
banking-system/
├── com/
│ └── bank/
│ ├── accounts/
│ │ ├── Account.java
│ │ ├── SavingsAccount.java
│ │ └── CheckingAccount.java
│ ├── services/
│ │ ├── BankingService.java
│ │ └── TransactionService.java
│ └── models/
│ ├── Customer.java
│ └── Transaction.java
└── Main.java
com/bank/accounts/Account.java:
package com.bank.accounts;
public class Account {
protected String accountNumber;
protected String accountHolder;
protected double balance;
public Account(String accountNumber, String accountHolder, double initialBalance) {
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
return true;
}
return false;
}
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
public String getAccountHolder() {
return accountHolder;
}
}
com/bank/models/Customer.java:
package com.bank.models;
import com.bank.accounts.Account;
import java.util.ArrayList;
import java.util.List;
public class Customer {
private String customerId;
private String name;
private String email;
private List<Account> accounts;
public Customer(String customerId, String name, String email) {
this.customerId = customerId;
this.name = name;
this.email = email;
this.accounts = new ArrayList<>();
}
public void addAccount(Account account) {
accounts.add(account);
}
public List<Account> getAccounts() {
return accounts;
}
public double getTotalBalance() {
double total = 0;
for (Account account : accounts) {
total += account.getBalance();
}
return total;
}
// Getters
public String getCustomerId() { return customerId; }
public String getName() { return name; }
public String getEmail() { return email; }
}
Main.java:
import com.bank.accounts.*;
import com.bank.models.Customer;
public class Main {
public static void main(String[] args) {
Customer customer = new Customer("CUST-001", "John Doe", "john@example.com");
SavingsAccount savings = new SavingsAccount("SAV-001", "John Doe", 5000.0, 3.5);
CheckingAccount checking = new CheckingAccount("CHK-001", "John Doe", 2000.0, 500.0);
customer.addAccount(savings);
customer.addAccount(checking);
System.out.println("Customer: " + customer.getName());
System.out.println("Total Balance: $" + customer.getTotalBalance());
}
}
javac com/bank/accounts/*.java javac com/bank/models/*.java javac Main.java java Main
// Import specific class
import com.bank.accounts.Account;
// Import all classes from package
import com.bank.accounts.*;
// Import static members
import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
Let's integrate everything into a complete, professional banking system.
com/bank/services/BankingService.java:
package com.bank.services;
import com.bank.accounts.*;
import com.bank.models.Customer;
import java.util.HashMap;
import java.util.Map;
public class BankingService {
private Map<String, Customer> customers;
private Map<String, Account> accounts;
public BankingService() {
this.customers = new HashMap<>();
this.accounts = new HashMap<>();
}
public void registerCustomer(Customer customer) {
customers.put(customer.getCustomerId(), customer);
System.out.println("Customer registered: " + customer.getName());
}
public void openAccount(String customerId, Account account) {
Customer customer = customers.get(customerId);
if (customer != null) {
customer.addAccount(account);
accounts.put(account.getAccountNumber(), account);
System.out.println("Account opened: " + account.getAccountNumber());
}
}
public void transfer(String fromAccountNum, String toAccountNum, double amount) {
Account fromAccount = accounts.get(fromAccountNum);
Account toAccount = accounts.get(toAccountNum);
if (fromAccount != null && toAccount != null) {
if (fromAccount.withdraw(amount)) {
toAccount.deposit(amount);
System.out.println("Transfer successful: $" + amount);
System.out.println("From " + fromAccountNum + " to " + toAccountNum);
} else {
System.out.println("Transfer failed: Insufficient funds");
}
}
}
public void displayCustomerInfo(String customerId) {
Customer customer = customers.get(customerId);
if (customer != null) {
System.out.println("\n=== CUSTOMER INFO ===");
System.out.println("ID: " + customer.getCustomerId());
System.out.println("Name: " + customer.getName());
System.out.println("Email: " + customer.getEmail());
System.out.println("Total Balance: $" + customer.getTotalBalance());
System.out.println("\nAccounts:");
for (Account account : customer.getAccounts()) {
System.out.println(" - " + account.getAccountNumber() +
": $" + account.getBalance());
}
}
}
}
BankingSystemDemo.java:
import com.bank.accounts.*;
import com.bank.models.Customer;
import com.bank.services.BankingService;
public class BankingSystemDemo {
public static void main(String[] args) {
BankingService bank = new BankingService();
// Create customers
Customer alice = new Customer("CUST-001", "Alice Johnson", "alice@email.com");
Customer bob = new Customer("CUST-002", "Bob Smith", "bob@email.com");
bank.registerCustomer(alice);
bank.registerCustomer(bob);
// Open accounts
SavingsAccount aliceSavings = new SavingsAccount("SAV-001", "Alice Johnson", 10000.0, 4.0);
CheckingAccount aliceChecking = new CheckingAccount("CHK-001", "Alice Johnson", 3000.0, 1000.0);
CheckingAccount bobChecking = new CheckingAccount("CHK-002", "Bob Smith", 5000.0, 500.0);
bank.openAccount("CUST-001", aliceSavings);
bank.openAccount("CUST-001", aliceChecking);
bank.openAccount("CUST-002", bobChecking);
// Perform transactions
System.out.println("\n=== TRANSACTIONS ===");
aliceSavings.deposit(2000);
aliceSavings.applyInterest();
aliceChecking.withdraw(500);
// Transfer money
System.out.println();
bank.transfer("CHK-001", "CHK-002", 1000);
// Display customer info
bank.displayCustomerInfo("CUST-001");
bank.displayCustomerInfo("CUST-002");
}
}
Compile and run:
javac -d bin com/bank/**/*.java BankingSystemDemo.java java -cp bin BankingSystemDemo
Congratulations! 🎉 You've mastered Object-Oriented Programming in Java!
this keywordextends and superContinue your journey:
Strengthen your OOP skills: