Day 2 of my Java Backend Development journey was all about strengthening the foundations of Object-Oriented Programming (OOP) and exploring some modern Java features. Today, I dug deeper into packages, encapsulation, abstraction, inheritance, polymorphism, and lambda expressions. These concepts are the building blocks for scalable and maintainable backend applications.
π¦ Packages
Packages in Java help organize classes into namespaces, avoiding name conflicts and improving modularity. They are like folders in a file system, grouping related classes together.
-
Built-in packages: Provide ready-to-use utilities (
java.util
,java.io
,java.sql
, etc.). - User-defined packages: Custom packages we create to organize our code.
Example:
// File: com/example/utils/MathUtils.java
package com.example.utils;
public class MathUtils {
public static int square(int n) {
return n * n;
}
}
// File: Main.java
import com.example.utils.MathUtils;
public class Main {
public static void main(String[] args) {
System.out.println("Square of 5: " + MathUtils.square(5));
}
}
com.example
βββ utils
βββ MathUtils.java
Main.java
π Packages promote code reusability and structure.
π Encapsulation
Encapsulation is about protecting data by keeping variables private and exposing access through public methods. It ensures data integrity and prevents misuse.
Example:
class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) balance -= amount;
}
public double getBalance() {
return balance;
}
}
Real-World Use Case:
In banking applications, you wouldnβt want users to directly modify the balance field. Encapsulation ensures only valid operations (deposit/withdraw) are allowed.
π§© Abstraction
Abstraction means hiding implementation details while showing only the necessary functionalities. It allows developers to work with high-level concepts without worrying about the low-level implementation.
- Abstract Classes: Can have both abstract and concrete methods.
- Interfaces: Define contracts that must be implemented.
Example:
abstract class Shape {
abstract double area();
}
class Circle extends Shape {
double radius;
Circle(double r) { this.radius = r; }
double area() {
return Math.PI * radius * radius;
}
}
public class Main {
public static void main(String[] args) {
Shape s = new Circle(5);
System.out.println("Area: " + s.area());
}
}
π Abstraction allows developers to focus on what an object does, not how it does it.
ποΈ Inheritance
Inheritance allows one class to reuse properties and behaviors from another. It improves reusability but should be used carefully to avoid creating rigid hierarchies.
Example:
class Vehicle {
void start() {
System.out.println("Vehicle is starting...");
}
}
class Car extends Vehicle {
void honk() {
System.out.println("Car is honking...");
}
}
public class Main {
public static void main(String[] args) {
Car c = new Car();
c.start();
c.honk();
}
}
Vehicle
β
βββ Car
Real-World Use Case:
In backend APIs, a User
class could be inherited by AdminUser
and CustomerUser
to reuse common attributes while adding role-specific logic.
π Polymorphism
Polymorphism means many forms. The same method or object behaves differently depending on context. Java supports two main types of polymorphism:
1. Compile-Time Polymorphism (Method Overloading)
This happens when multiple methods in the same class share the same name but differ in parameters (number or type).
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Calls int version
System.out.println(calc.add(5.5, 10.5)); // Calls double version
}
}
π The method call is resolved at compile time.
2. Runtime Polymorphism (Method Overriding)
This occurs when a subclass provides its own implementation of a method already defined in the parent class.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
a.sound(); // Output: Dog barks
}
}
π The method call is resolved at runtime based on the objectβs type.
Compile-Time Polymorphism (Overloading)
Calculator.add()
βββ add(int, int)
βββ add(double, double)
Runtime Polymorphism (Overriding)
Animal.sound()
βββ Dog β "Dog barks"
βββ Cat β "Cat meows"
Real-World Use Case:
-
Compile-time polymorphism: In backend systems, method overloading is used in service classes to provide different variations of a function (e.g.,
findUserById(int id)
vsfindUserById(String uuid)
). -
Runtime polymorphism: In payment systems, different implementations of
processPayment()
are chosen at runtime depending on the payment method.
β‘οΈ Lambda Expressions
Lambda expressions, introduced in Java 8, allow functional programming style in Java. They make code shorter, more expressive, and easier to read.
Example:
import java.util.*;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Using Lambda Expression
names.forEach(n -> System.out.println(n));
}
}
Functional Interface Example:
interface Greeting {
void sayHello(String name);
}
public class Main {
public static void main(String[] args) {
Greeting greet = (n) -> System.out.println("Hello, " + n);
greet.sayHello("Alice");
}
}
(Stream Pipeline):
List β Stream β filter(u -> condition) β map(u -> transform) β collect()
Real-World Use Case:
In backend applications, lambdas are widely used in streams, filtering, and mapping data. For example, filtering user data:
List<String> users = Arrays.asList("admin", "guest", "superuser");
users.stream()
.filter(u -> u.startsWith("a"))
.forEach(System.out::println);
π― Day 2 Takeaway
Day 2 was all about mastering OOP principles and embracing modern Java syntax. I learned:
- Packages for structuring backend projects.
- Encapsulation to safeguard data.
- Abstraction to separate high-level logic from implementation.
- Inheritance for reusability.
- Polymorphism: both compile-time (overloading) and runtime (overriding).
- Lambda Expressions for concise and functional programming.
These concepts are indispensable in backend systems, where modularity, scalability, and maintainability are key. They form the core principles that frameworks like Spring Boot heavily rely on.
π‘ Stay tuned for Day 3
Top comments (0)