OOP Fundamentals
Dive into object-oriented programming in Java with classes, objects, constructors, encapsulation, and the this keyword.
Classes and Objects
A class in Java is a blueprint that defines the structure and behavior of objects through fields and methods. Objects are instances of classes, each maintaining their own copy of instance variables. The new keyword allocates memory for an object and calls its constructor to initialize the state. Java's object-oriented model encourages you to model real-world entities as interacting objects with clearly defined responsibilities.
public class Car {
// Instance fields
String make;
String model;
int year;
double speed;
// Method to accelerate
void accelerate(double amount) {
speed += amount;
System.out.println(model + " accelerated to " + speed + " mph");
}
// Method to brake
void brake(double amount) {
speed = Math.max(0, speed - amount);
System.out.println(model + " slowed to " + speed + " mph");
}
// Display car info
void displayInfo() {
System.out.printf("%d %s %s (%.1f mph)%n", year, make, model, speed);
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.make = "Toyota";
myCar.model = "Camry";
myCar.year = 2024;
myCar.accelerate(60);
myCar.brake(20);
myCar.displayInfo(); // "2024 Toyota Camry (40.0 mph)"
}
}
Constructors
Constructors are special methods invoked when creating a new object, used to initialize the object's state. They have the same name as the class and no return type, not even void. Java provides a default no-argument constructor if you do not define any constructors, but this default disappears once you define your own. Constructor overloading and constructor chaining with this() allow you to provide flexible initialization options.
public class Student {
private String name;
private int age;
private String major;
// No-arg constructor with defaults
public Student() {
this("Unknown", 18, "Undeclared");
}
// Partial constructor
public Student(String name, int age) {
this(name, age, "Undeclared");
}
// Full constructor
public Student(String name, int age, String major) {
this.name = name;
this.age = age;
this.major = major;
}
@Override
public String toString() {
return String.format("Student{name='%s', age=%d, major='%s'}",
name, age, major);
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("Alice", 20);
Student s3 = new Student("Bob", 21, "Computer Science");
System.out.println(s1); // Student{name='Unknown', age=18, major='Undeclared'}
System.out.println(s2); // Student{name='Alice', age=20, major='Undeclared'}
System.out.println(s3); // Student{name='Bob', age=21, major='Computer Science'}
}
}
Encapsulation
Encapsulation is the practice of hiding an object's internal state and requiring all interaction through well-defined methods. In Java, this is achieved by declaring fields as private and providing public getter and setter methods. This approach protects the integrity of the data by allowing validation logic in setters and computed values in getters. Encapsulation also makes it possible to change the internal implementation without affecting code that uses the class.
public class BankAccount {
private String owner;
private double balance;
private final String accountNumber;
public BankAccount(String owner, String accountNumber, double initialBalance) {
this.owner = owner;
this.accountNumber = accountNumber;
setBalance(initialBalance);
}
// Getter
public double getBalance() {
return balance;
}
// Setter with validation
private void setBalance(double balance) {
if (balance < 0) {
throw new IllegalArgumentException("Balance cannot be negative");
}
this.balance = balance;
}
public String getOwner() {
return owner;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit must be positive");
}
this.balance += amount;
}
public void withdraw(double amount) {
if (amount > balance) {
throw new IllegalArgumentException("Insufficient funds");
}
this.balance -= amount;
}
public static void main(String[] args) {
BankAccount account = new BankAccount("Alice", "ACC-001", 1000);
account.deposit(500);
account.withdraw(200);
System.out.println("Balance: quot; + account.getBalance()); // Balance: $1300.0
}
}
The this Keyword
The this keyword refers to the current instance of the class and is used to distinguish between instance variables and method parameters with the same name. It can be used to call other constructors within the same class using this(), which must be the first statement in the constructor. Passing this as an argument allows the current object to register itself with other objects or callbacks. While this is optional when there is no naming conflict, using it explicitly can improve code clarity.
public class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x; // 'this.x' is the field, 'x' is the parameter
this.y = y;
}
// Using 'this' for method chaining (fluent API)
public Point translate(double dx, double dy) {
this.x += dx;
this.y += dy;
return this; // Return current instance
}
public Point scale(double factor) {
this.x *= factor;
this.y *= factor;
return this;
}
public double distanceTo(Point other) {
double dx = this.x - other.x;
double dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy);
}
@Override
public String toString() {
return String.format("(%.1f, %.1f)", x, y);
}
public static void main(String[] args) {
// Method chaining with 'this'
Point p = new Point(1, 2)
.translate(3, 4)
.scale(2);
System.out.println(p); // (8.0, 12.0)
Point origin = new Point(0, 0);
System.out.printf("Distance to origin: %.2f%n", p.distanceTo(origin));
}
}