Backend Systemsbeginner
Object-Oriented Programming in Java
Master Java OOP: classes, constructors, inheritance, interfaces, abstract classes, polymorphism, and encapsulation with practical examples.
Asma HafeezApril 17, 20266 min read
javaoopclassesinheritanceinterfaces
Object-Oriented Programming in Java
Java is built around objects. Understanding OOP well means you can design code that scales without becoming a mess.
Classes and Objects
JAVA
public class Person {
// Fields (instance variables)
String name;
int age;
// Constructor
public Person(String name, int age) {
this.name = name; // 'this' disambiguates field vs parameter
this.age = age;
}
// Method
public void greet() {
System.out.println("Hi, I'm " + name + " and I'm " + age + " years old.");
}
}
// Using the class
Person alice = new Person("Alice", 30);
alice.greet(); // Hi, I'm Alice and I'm 30 years old.Encapsulation — Private Fields + Getters/Setters
JAVA
public class BankAccount {
private String owner;
private double balance; // private — not accessible directly
public BankAccount(String owner, double initialBalance) {
this.owner = owner;
this.balance = initialBalance;
}
// Getter
public double getBalance() {
return balance;
}
// Setter with validation
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
balance += amount;
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
@Override
public String toString() {
return String.format("BankAccount[%s, balance=%.2f]", owner, balance);
}
}
BankAccount account = new BankAccount("Alice", 1000.0);
account.deposit(500.0);
System.out.println(account.getBalance()); // 1500.0
// account.balance = 9999; // compile error — private!Inheritance
JAVA
// Base class
public class Animal {
protected String name;
protected String sound;
public Animal(String name, String sound) {
this.name = name;
this.sound = sound;
}
public void makeSound() {
System.out.println(name + " says " + sound);
}
public String getName() { return name; }
}
// Subclass
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name, "Woof"); // call parent constructor
this.breed = breed;
}
@Override // overrides parent method
public void makeSound() {
System.out.println(name + " barks: WOOF WOOF!");
}
public void fetch() {
System.out.println(name + " fetches the ball!");
}
public String getBreed() { return breed; }
}
Dog rex = new Dog("Rex", "Labrador");
rex.makeSound(); // Rex barks: WOOF WOOF!
rex.fetch(); // Rex fetches the ball!
System.out.println(rex.getName()); // Rex (inherited from Animal)Polymorphism
JAVA
Animal animal1 = new Dog("Rex", "Lab");
Animal animal2 = new Cat("Whiskers"); // assuming Cat extends Animal
animal1.makeSound(); // Dog's version
animal2.makeSound(); // Cat's version
// Arrays of the base type
Animal[] animals = { new Dog("Rex", "Lab"), new Cat("Kitty") };
for (Animal a : animals) {
a.makeSound(); // polymorphic dispatch
}
// instanceof check before casting
if (animal1 instanceof Dog dog) { // Java 16+ pattern matching
dog.fetch(); // safe — we know it's a Dog
}Abstract Classes
JAVA
// Can't instantiate — must be subclassed
public abstract class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
// Abstract method — no body, must be implemented by subclasses
public abstract double area();
public abstract double perimeter();
// Concrete method — shared by all shapes
public void describe() {
System.out.printf("%s %s: area=%.2f%n", color, getClass().getSimpleName(), area());
}
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() { return Math.PI * radius * radius; }
@Override
public double perimeter() { return 2 * Math.PI * radius; }
}
public class Rectangle extends Shape {
private double width, height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double area() { return width * height; }
@Override
public double perimeter() { return 2 * (width + height); }
}
// Usage
Shape[] shapes = { new Circle("red", 5), new Rectangle("blue", 3, 4) };
for (Shape s : shapes) {
s.describe(); // polymorphic call to area()
}Interfaces
JAVA
// Interface — defines a contract
public interface Printable {
void print(); // implicitly public abstract
}
public interface Saveable {
void save(String filename);
// Default method (Java 8+) — optional override
default void saveWithTimestamp(String filename) {
save(filename + "_" + System.currentTimeMillis());
}
}
// A class can implement multiple interfaces
public class Document implements Printable, Saveable {
private String content;
public Document(String content) {
this.content = content;
}
@Override
public void print() {
System.out.println("Document: " + content);
}
@Override
public void save(String filename) {
System.out.println("Saving to " + filename);
}
}
Document doc = new Document("Hello World");
doc.print(); // Document: Hello World
doc.save("output.txt"); // Saving to output.txt
doc.saveWithTimestamp("output"); // Saving to output_1713369600000Interface vs Abstract Class
| | Interface | Abstract Class | |---|---|---| | Multiple inheritance | Yes (implement many) | No (extend one) | | Fields | Constants only | Any | | Constructor | No | Yes | | Use when | Defining a capability | Sharing base implementation |
Records (Java 16+)
Records are immutable data classes with less boilerplate.
JAVA
// This single line generates: constructor, getters, equals, hashCode, toString
public record Point(double x, double y) {
// Optional: custom method
public double distanceTo(Point other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
}
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.x()); // 0.0 (getter)
System.out.println(p1.distanceTo(p2)); // 5.0
System.out.println(p1); // Point[x=0.0, y=0.0]Enums
JAVA
public enum Priority {
LOW, MEDIUM, HIGH;
public boolean isUrgent() {
return this == HIGH;
}
}
Priority p = Priority.HIGH;
System.out.println(p.isUrgent()); // true
System.out.println(p.name()); // "HIGH"
System.out.println(p.ordinal()); // 2
// switch with enum
String label = switch (p) {
case LOW -> "Take your time";
case MEDIUM -> "Normal pace";
case HIGH -> "Do it now!";
};Static Members
JAVA
public class Counter {
private static int total = 0; // shared across all instances
private int id;
public Counter() {
total++;
this.id = total;
}
public int getId() { return id; }
public static int getTotal() { return total; }
}
Counter a = new Counter();
Counter b = new Counter();
Counter c = new Counter();
System.out.println(Counter.getTotal()); // 3
System.out.println(a.getId()); // 1
System.out.println(c.getId()); // 3Key Takeaways
- Encapsulate fields with
privateand expose behavior through methods - Prefer interfaces over abstract classes when sharing only a contract (not state)
- Use
@Overrideto make overrides explicit and catch typos at compile time - Records are the modern way to write immutable data classes — less boilerplate
- Polymorphism via interfaces is what makes dependency injection work — depend on abstractions, not concrete types
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.