DEV Community

Chandana prabhakar
Chandana prabhakar

Posted on

From Zero to Java: Day 2 of My Backend Development Journey

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;
    }
}
Enter fullscreen mode Exit fullscreen mode
// 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));
    }
}
Enter fullscreen mode Exit fullscreen mode
com.example
   └── utils
        └── MathUtils.java
Main.java
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ 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();
    }
}
Enter fullscreen mode Exit fullscreen mode
   Vehicle
     β”‚
     └── Car
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ 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
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ‘‰ 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"
Enter fullscreen mode Exit fullscreen mode

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) vs findUserById(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));
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

(Stream Pipeline):

List β†’ Stream β†’ filter(u -> condition) β†’ map(u -> transform) β†’ collect()
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

🎯 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)