OOP Using Java
OOP Using Java
Department of CS
OOP using java
🇪🇹
Preparing for the Ethiopian National Exit
Examination
Design Java classes using OOP principles.
Implement real-world systems (e.g., banking, e-
commerce) with Java.
Develop GUI applications using JavaFX/Swing.
Apply SOLID principles to write maintainable
code.
Solve exam-style problems on OOP and Java.
Introduction to OOP
What is OOP?
Example:
Definition: class Student { // Class
"A programming paradigm that String name; // Field (data)
organizes software design
around objects (data + behavior) void study() { // Method
rather than functions." (behavior)
Key Terms: System.out.println(name
Class: Blueprint (e.g., class Car {}) + " is studying.");
Object: Instance (e.g., Car tesla = }
new Car();)
}
Procedural vs. OOP: comparison table
Example:
// Procedural
void printStudentName(String
name) { ... }
// OOP
class Student {
String name;
void printName() { ... }
}
Why Use OOP?
Advantages:
Analogy:
Modularity: Isolate code into
Car Manufacturing:
objects (e.g., BankAccount, Different teams (objects)
User). work on engine, wheels,
Reusability: Inherit features etc.
(e.g., ElectricCar extends Car).
Security: Encapsulation
protects data (e.g., private
balance).
Key OOP Concepts (Preview)
The 4 Pillars:
Encapsulation
Inheritance
Polymorphism
Abstraction
Objects in Real Life & Code
Real-World Object:
Java Equivalent:
Car: Attributes (color, class Car {
model), Behaviors (start,
String color; // Attribute
stop).
void start() { // Behavior
System.out.println("Car
started.");
}
}
Objects: software perspective
is a software
construct/module that
bundles together state
(data) and behavior
(functions) which, taken
together, represent an
abstraction of a real-world
object.
Class vs. Object
[Class: Car] → [Objects:
A class is an abstraction
Tesla, Toyota, BMW] describing the common
features of all objects in a
group of similar objects.
For example: “Student”
Class vs. Object...
Example:
Car tesla = new Car(); //
Object 1
tesla.color = "Red";
args) {
Analogy:
Student student1 = new
Student(); // Object 1
Class = Cookie cutter 🍪
student1.name = "Alice";
Object = Actual cookies
student1.introduce(); // Output:
"Hello, I'm Alice"
}
}
Constructors
Purpose: Initialize objects
Example:
at creation. class Car {
String model;
Types:
// Parameterized constructor
Default Constructor: Auto- Car(String m) {
generated (no args). this.model = m;
Parameterized }
Constructor: Custom }
initialization.
// Usage:
Car myCar = new Car("Tesla");
The this Keyword
Why? Resolves ambiguity
Example:
between instance variables class Employee {
and parameters.
String name;
Employee(String name) {
this.name = name; //
"this" refers to the current
object
}
}
Common Mistake Alert:
Employee(String name) {
name = name; // Bug!
Doesn't assign to the field.
}
Memory Allocation (Stack vs. Heap)
Diagram:
Key Points:
[Stack] [Heap]
Stack: Stores references
(e.g., Car myCar).
myCar (reference) → Car
Heap: Stores object data
{ model: "Tesla" }
(e.g., new Car()).
Object Lifecycle
Creation: new keyword
Example:
allocates memory. Car car = new Car(); //
Usage: Call Creation
methods/access fields. car.drive(); // Usage
Destruction: Garbage car = null; // Eligible for
GC
Collector (GC) reclaims
unused memory.
Real-World Example: Bank Account
Class Design:
Usage:
class BankAccount {
BankAccount account =
private double balance;
void deposit(double amount) {
new BankAccount();
balance += amount;
account.deposit(1000);
}
System.out.println(account.
double getBalance() {
getBalance()); // 1000.0
return balance;
}
}
Common Pitfalls
NullPointerException:
Uninitialized Objects:
Car car = null;
Student s; // No 'new' →
car.drive(); // Crash! Compiler error
Summary & Exam Tips
Key Takeaways:
Classes define structure; objects are instances.
Use constructors for initialization.
this avoids field/parameter conflicts.
Exercise:
Write a Rectangle class with fields (width, height) and methods
(calculateArea()).
Hands-On Exercise
Task:
Starter Code
Create a MobilePhone class
class MobilePhone {
with:
// Your code here
Fields: brand, price
}
Constructor: Parameterized
Method: printDetails()
🔒
Chapter 3 - Encapsulation & Information
Hiding
🔒 Encapsulation & Information Hiding
Definition:
Example:
"Bundling data (fields) and class BankAccount {
methods that operate on that
data into a single unit (class), private double balance; //
while restricting direct access to Hidden data
some components." public double getBalance()
Key Principles: { // Controlled access
Data Hiding: Mark fields as return balance;
private.
}
Controlled Access: Provide
public getters/setters. }
Why Encapsulation?
Benefits:
Real-World Analogy:
Security: Prevent invalid
Medical Records: Only
data (e.g., negative doctors (methods) can
balance). access/update your records
Flexibility: Change internal (data).
logic without breaking
external code.
Maintainability: Easier
debugging (validation in
one place).
Access Modifiers in Java
Golden Rule:
Fields: private (usually)
Methods: public (if part of
API)
Getters & Setters (Standard Pattern)
Purpose: Example:
class Student {
Getter: Retrieve field value
private String name;
// Getter
(getFieldName()).
public String getName() {
Setter: Validate + modify
return name;
}
field value (setFieldName()).
// Setter with validation
public void setName(String newName) {
if (newName != null && !
newName.isEmpty()) {
this.name = newName;
}
}
}
Real-World Example: Temperature Control
Problem: Prevent invalid Solution:
class Thermostat {
temperature values.
private double temperature;
public void setTemperature(double temp) {
if (temp >= -50 && temp <= 50) { //
Validation
this.temperature = temp;
} else {
System.out.println("Invalid
temperature!");
}
}
}
Information Hiding vs. Encapsulation
Subtle Difference:
Analogy:
Information Hiding:
Information Hiding: "Don’t
Design principle (hide show how the engine
implementation details). works."
Encapsulation:
Encapsulation: "Put the
Implementation technique engine under the hood and
(using private + provide a pedal."
getters/setters).
Common Mistakes
Exposing Data:
Weak Validation:
public double balance; //
public void setAge(int age) {
Danger! No control.
this.age = age; // No
check for negative age!
}
Summary & Exam Tips
Key Takeaways:
Exam Focus:
Use private fields + public
Identify encapsulation
getters/setters. violations in given code.
Validate data in setters.
Write a Person class with
encapsulated age (validate:
0-120).
Hands-On Exercise
Task:
Starter Code:
Create a Book class with:
class Book {
private fields: title, author,
// Your code here
price
}
Validate price (> 0) in setter
Solution:
class Book {
private String title, author;
private double price;
public void setPrice(double price) {
if (price > 0) {
this.price = price;
} else {
System.out.println("Price must be
positive!");
}
}
// ... (other getters/setters)
}
Chapter 4 - Inheritance
Definition:
Syntax:
"A mechanism where a
class Animal { } //
child class inherits fields Superclass
and methods from a parent
class Dog extends Animal
class, enabling code reuse."
{ } // Subclass
Key Terms:
Superclass (Parent): Base
class being inherited from
Subclass (Child): Class that
inherits
Why Use Inheritance?
Benefits:
Real-World Example:
Code Reuse: Avoid
Animal
duplicating common logic
/ \
Polymorphism: Enable
method overriding (Chapter
Dog Cat
5)
Logical Hierarchy: Model
"is-a" relationships
Types of Inheritance in Java
Supported Types:
Unsupported:
Single: One parent → one
Multiple inheritance (use
child interfaces instead)
Multilevel: Grandparent →
parent → child
Hierarchical: One parent →
multiple children
The extends Keyword
How It Works:
Example:
class Vehicle { // Parent
Subclass gains all non-
void start() {
private members of
superclass
System.out.println("Vehicle
started");
}
}
class Car extends Vehicle { // Child
// Inherits start()
}
Method Overriding
Concept: Example:
class Bird {
Child class provides its own
void sing() {
implementation of an
System.out.println("Bird song");
inherited method
}
}
Rules:
class Duck extends Bird {
Same method signature as
@Override
parent
void sing() {
System.out.println("Quack quack");
Use @Override annotation
}
}
The super Keyword
Three Uses: Example:
class Employee {
Access parent class fields:
String name = "Employee";
super.field
}
class Manager extends Employee {
Call parent class methods:
String name = "Manager";
super.method()
void printNames() {
Invoke parent constructor:
System.out.println(super.name); //
super() "Employee"
System.out.println(this.name); //
"Manager"
}
}
Constructor Chaining
Key Points: Example:
class Person {
Child constructors must call
Person() {
}
Default super() is added
class Student extends Person {
automatically if missing
Student() {
// super(); // Added automatically
System.out.println("Student constructor");
}
}
Output when creating
Student:
"Person constructor"
"Student constructor"
Real-World Example: Banking System
Class Hierarchy:
Usage:
class BankAccount {
double balance;
SavingsAccount acct = new
void deposit(double amount) { balance +=
amount; }
SavingsAccount();
}
acct.deposit(1000); //
class SavingsAccount extends BankAccount {
Inherited method
double interestRate;
void addInterest() {
acct.addInterest(); //
balance += balance * interestRate; Specialized method
}
}
Common Pitfalls
Accidental Overriding:
Same method name but
different signature → method
hiding
Incorrect super Usage:
super() must be first line in
constructor
Overuse of Inheritance:
Favor composition over
inheritance when possible
Summary & Exam Tips
Key Takeaways:
Exam Focus:
Use extends to create
Identify inheritance
parent-child relationships relationships in code
Override methods for snippets
specialized behavior
Write a Shape superclass
Use super to access parent with Circle and Rectangle
members subclasses
Hands-On Exercise
Task:
Starter Code:
Create:
class Person {
Person superclass with
// Your code here
name and introduce()
method
}
Student subclass that adds
studentId and overrides
introduce()
Solution
class Person {
class Student extends Person {
String name;
String studentId;
@Override
void introduce() {
void introduce() {
System.out.println("I'm
super.introduce();
" + name);
System.out.println("My
} student ID: " + studentId);
}
}
}
Chapter 5: Polymorphism
Objective:
Understand polymorphism
and its types
Master method overriding
and dynamic binding
Apply polymorphism in
real-world Java programs
What is Polymorphism?
Definition:
Real-World Analogy:
Greek for "many forms"
A button performs different
Ability of an object to take actions:
different forms
Power button → Turns
Same method behaves device on/off
differently based on the
Elevator button → Moves to
object a floor
Types of Polymorphism
1. Compile-Time
Example:
Polymorphism (Static)
class Calculator {
Achieved via method
// Method 1
overloading
int add(int a, int b) { return a +
b; }
Methods must have:
Same name
// Method 2 (Overloaded)
Different parameters
(type/count/order)
double add(double a, double b)
{ return a + b; }
}
Runtime Polymorphism (Dynamic)
Achieved via method
overriding
Requires inheritance +
same method signature
Method Overriding (Runtime Polymorphism)
Rules: Example:
class Animal {
Same method name and
void makeSound() {
parameters as parent
System.out.println("Animal sound");
Child class method cannot
}
}
have a more restrictive
@Override
Use @Override annotation
void makeSound() {
(best practice)
System.out.println("Bark!");
}
}
Usage:
Animal myPet = new
Dog(); // Upcasting
myPet.makeSound(); //
Output: "Bark!" (Not
"Animal sound")
Dynamic Method Dispatch
How it Works: Example:
Animal[] pets = new Animal[3];
JVM (not compiler) decides
pets[0] = new Dog(); // Actual object:
which method to call at Dog
runtime
pets[1] = new Cat(); // Actual object:
Based on the actual object Cat
abstraction (100%
void draw(); // Abstract method
}
polymorphic)
class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing circle");
}
}
class Square implements Drawable {
@Override
public void draw() {
System.out.println("Drawing square");
}
}
Usage:
Drawable[] shapes = {new
Circle(), new Square()};
for (Drawable shape :
shapes) {
shape.draw(); //
Polymorphic call
}
Limitations of Polymorphism
Cannot override:
Example of Invalid Override:
static methods (class-level,
class Parent {
not object-level)
public void show() { /* ... */ }
final methods
}
private methods
class Child extends Parent {
Cannot reduce visibility:
@Override
If parent method is public,
private void show() { /* ...
*/ } // Compile error!
child cannot make it private
}
super Keyword in Polymorphism
Purpose: Call parent class’s Example:
class Vehicle {
overridden method
void start() {
System.out.println("Vehicle started");
}
}
class Car extends Vehicle {
@Override
void start() {
super.start(); // Calls Vehicle's start()
System.out.println("Car started");
}
}
Real-World Example (Banking System)
Scenario: Different account
class Account {
types calculate interest
double balance;
differently
double
calculateInterest() {
return balance *
0.01; // Default 1%
}
}
class SavingsAccount
class FixedDeposit extends
extends Account { Account {
@Override
@Override
double calculateInterest() {
double calculateInterest() {
return balance * 0.05; //
return balance * 0.08; //
5% for savings 8% for FD
}
}
}
}
Usage:
Account[] accounts = {
new SavingsAccount(),
new FixedDeposit()
};
for (Account acc : accounts) {
System.out.println(acc.calculateInt
erest());
}
Common Mistakes & Debugging
Error 1: Forgetting
class Parent {
@Override
protected void demo() { /* ... */
}
Risk: Accidentally
}
overloading instead of
overriding
class Child extends Parent {
@Override
Error 2: Incorrect access
void demo() { /* ... */ } // Error:
modifiers Default (package-private) <
protected
}
Error 3: Assuming
polymorphism works with
fields
Field access is compile-time
(based on reference type)
Exam-Style Questions
Q1: What’s the output?
Answer: (Runtime
class A { void m1() polymorphism)
{ System.out.println("A"); } }
class B extends A { void m1()
{ System.out.println("B"); } }
A obj = new B();
obj.m1();
Q2: Fix the code:
Issue: Print() ≠ print() (Case-
class X { void print() { /* ... sensitive = no override)
*/ } }
class Y extends X { void
Print() { /* ... */ } }
Summary
Hands-On Exercise
Problem:
Starter Code:
Create a Shape hierarchy
abstract class Shape {
with Circle, Rectangle
abstract double area();
Override area() method
polymorphically
}
// Implement Circle and
Rectangle
Chapter 6: Abstraction (Abstract Classes &
Interfaces)
Objective:
Understand abstraction as
an OOP concept
Differentiate abstract
classes and interfaces
Apply abstraction to real-
world problems
What is Abstraction?
Definition:
Why Use Abstraction?
Hiding complex
Reduces complexity
implementation details,
Enforces structure
exposing only essential
features.
Supports modular design
Analogy: Car dashboard
(shows speed, hides engine
mechanics).
Abstract Classes
Key Features: abstract class Animal {
// Abstract method (no implementation)
Declared with abstract
abstract void makeSound();
// Concrete method
keyword
void breathe() {
Can have both abstract (no
System.out.println("Breathing...");
}
methods
class Dog extends Animal {
Cannot be instantiated
@Override
void makeSound() {
directly
System.out.println("Bark!");
}
}
Usage:
Animal myDog = new
Dog();
myDog.makeSound(); //
Output: "Bark!"
myDog.breathe(); //
Output: "Breathing..."
Interfaces
Key Features (Pre-Java 8+):
interface Drawable {
void draw(); // Abstract method
100% abstract (until Java 8)
}
Uses interface keyword
class Circle implements Drawable {
All methods are public
@Override
abstract by default
public void draw() {
Fields are public static final
System.out.println("Drawing a
circle");
}
}
Usage:
Drawable d = new Circle();
d.draw(); // Output:
"Drawing a circle"
Java 8+ Interface Enhancements
Default Methods:
interface Vehicle {
Provide implementation in
default void start() {
interfaces
System.out.println("Vehicle
started");
Avoid breaking existing
code
}
}
class Car implements Vehicle {
// No need to override start()
}
Static Methods:
interface MathUtils {
static int square(int x)
{ return x * x; }
}
// Usage:
int result =
MathUtils.square(5);
When to Use Abstract Classes vs. Interfaces
Use Abstract Classes When:
Use Interfaces When:
You want to share code
among related classes
You need multiple
You need non-final fields inheritance of type
You require a constructor
You want to define a
contract (API)
You’re working with
lambda expressions
Real-World Example (Payment System)
Abstract Class Approach:
class CreditCardPayment
extends Payment {
abstract class Payment {
@Override
double amount;
void processPayment() {
abstract void
processPayment(); System.out.println("Processin
} g credit card: $" + amount);
}
}
Interface Approach:
interface Refundable {
class PayPalPayment extends
Payment implements Refundable {
void processRefund();
@Override
}
void processPayment() { /* ... */ }
@Override
public void processRefund() {
/* ... */ }
}
Common Pitfalls
Instantiation Attempt:
Missing
Animal a = new Animal(); // Implementations:
Error: Animal is abstract
class BadDog extends
Animal { } // Error: Must
implement makeSound()
Diamond Problem (Solved with Default
Methods):
interface A { default void show()
{ /* ... */ } }
interface B { default void show()
{ /* ... */ } }
class C implements A, B {
@Override // Must override to
resolve conflict
public void show()
{ A.super.show(); }
}
Summary Cheat Sheet