MITS Academy — Java Programming Course
MITS ACADEMY
Java Programming Course
Intermediate Level | Generics, Streams, Concurrency, Design Patterns, JDBC
[Link]
Page 1 | MITS Academy | [Link]
MITS Academy — Java Programming Course
Module 1: Java Foundations Review
1.1 JVM Architecture and Memory Model
Understanding how Java runs helps you write better, more efficient code. When you compile
a .java file with javac, it produces bytecode (.class files). The JVM (Java Virtual Machine)
interprets this bytecode. The JVM has several memory areas: the Heap (stores objects), Stack
(stores local variables and method call frames), Method Area (stores class metadata), and PC
Register.
Garbage Collection (GC) automatically reclaims memory from objects no longer referenced.
Java uses different GC algorithms: Serial, Parallel, G1, and ZGC. Understanding that
unreachable objects are collected helps you avoid memory leaks by not holding unnecessary
references.
public class MemoryDemo {
// Static variable — stored in Method Area
static int instanceCount = 0;
// Instance variable — stored on Heap
private String name;
private int id;
public MemoryDemo(String name) {
[Link] = name;
[Link] = ++instanceCount;
}
public static void main(String[] args) {
// Local variables — stored on Stack
int x = 10;
double y = 3.14;
// Objects — stored on Heap, reference on Stack
MemoryDemo obj1 = new MemoryDemo("Alpha");
MemoryDemo obj2 = new MemoryDemo("Beta");
[Link]("Instance count: " + instanceCount); // 2
// obj1 becomes eligible for GC after this
obj1 = null;
[Link](); // Suggest GC (not guaranteed)
// Autoboxing — primitive to wrapper
int primitive = 42;
Integer boxed = primitive; // Autoboxing
int unboxed = boxed; // Unboxing
// Integer cache: -128 to 127 are cached
Integer a = 100, b = 100;
[Link](a == b); // true (same cached object)
Integer c = 200, d = 200;
[Link](c == d); // false (different objects)
[Link]([Link](d)); // true (value comparison)
}
}
Page 2 | MITS Academy | [Link]
MITS Academy — Java Programming Course
Module 2: Generics
2.1 Generic Classes and Methods
Generics allow you to write code that works with any data type while providing compile-time
type safety. Without generics, you would need to cast objects and could get
ClassCastException at runtime. With generics, type errors are caught at compile time.
Type parameters (like T, E, K, V) are placeholders for actual types. Bounded type parameters
restrict which types can be used. Wildcards (?) provide flexibility when the exact type doesn't
matter but relationships between types do.
import [Link].*;
// Generic class
class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
[Link] = key;
[Link] = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
@Override
public String toString() {
return "(" + key + ", " + value + ")";
}
}
// Generic method
class ArrayUtils {
// Works with any type T
public static <T extends Comparable<T>> T findMax(T[] arr) {
T max = arr[0];
for (T item : arr)
if ([Link](max) > 0) max = item;
return max;
}
// Wildcard — read-only (upper bounded)
public static double sumList(List<? extends Number> list) {
return [Link]().mapToDouble(Number::doubleValue).sum();
}
}
public class GenericsDemo {
public static void main(String[] args) {
Pair<String, Integer> p = new Pair<>("Alice", 92);
[Link](p); // (Alice, 92)
Integer[] nums = {3, 7, 1, 9, 4};
[Link]([Link](nums)); // 9
Page 3 | MITS Academy | [Link]
MITS Academy — Java Programming Course
String[] words = {"banana", "apple", "cherry"};
[Link]([Link](words)); // cherry
List<Integer> ints = [Link](1, 2, 3, 4, 5);
List<Double> doubles = [Link](1.5, 2.5, 3.5);
[Link]([Link](ints)); // 15.0
[Link]([Link](doubles)); // 7.5
}
}
Module 3: Lambda Expressions and Streams
3.1 Functional Interfaces and Lambda Expressions
Java 8 introduced lambda expressions — compact anonymous function implementations. A
functional interface is an interface with exactly one abstract method (annotated with
@FunctionalInterface). Lambda expressions provide inline implementations of functional
interfaces without creating anonymous classes.
Java provides built-in functional interfaces in [Link]: Predicate<T> (boolean test),
Function<T,R> (transform T to R), Consumer<T> (operate on T, return void), Supplier<T>
(produce T), BiFunction<T,U,R> (two inputs one output), and more.
import [Link].*;
import [Link].*;
public class LambdaDemo {
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
}
public static void main(String[] args) {
// Lambda expressions
MathOperation add = (a, b) -> a + b;
MathOperation multiply = (a, b) -> a * b;
MathOperation power = (a, b) -> (int) [Link](a, b);
[Link]([Link](5, 3)); // 8
[Link]([Link](5, 3)); // 15
[Link]([Link](2, 10)); // 1024
// Built-in functional interfaces
Predicate<String> isLong = s -> [Link]() > 5;
Predicate<String> startsWithA = s -> [Link]("A");
Predicate<String> both = [Link](startsWithA);
[Link]([Link]("Algorithm")); // true
[Link]([Link]("Algo")); // false (too short)
Function<String, Integer> strLen = String::length; // Method reference
Function<Integer, Integer> doubled = x -> x * 2;
Function<String, Integer> lenThenDouble = [Link](doubled);
[Link]([Link]("Hello")); // 10
// Sorting with lambda
Page 4 | MITS Academy | [Link]
MITS Academy — Java Programming Course
List<String> names = [Link]("Charlie", "Alice", "Bob", "Diana");
[Link]((a, b) -> [Link](b)); // Sort alphabetically
[Link](names);
[Link]([Link](String::length));
[Link](names); // Sort by length
}
}
3.2 Stream API
The Stream API (Java 8+) provides a functional approach to processing collections. A stream is
a sequence of elements supporting sequential and parallel aggregate operations. Streams are
lazy — intermediate operations are only executed when a terminal operation is invoked.
Intermediate operations (filter, map, sorted, distinct, limit) return a new stream and are lazy.
Terminal operations (collect, forEach, count, reduce, findFirst) produce a result or side-effect
and trigger evaluation. Streams cannot be reused — once consumed, a new stream must be
created.
import [Link].*;
import [Link].*;
public class StreamDemo {
record Student(String name, int marks, String dept) {}
public static void main(String[] args) {
List<Student> students = [Link](
new Student("Alice", 92, "CS"),
new Student("Bob", 78, "Math"),
new Student("Carol", 85, "CS"),
new Student("David", 95, "CS"),
new Student("Eve", 70, "Math"),
new Student("Frank", 88, "Arts")
);
// Filter + Map + Collect
List<String> csToppers = [Link]()
.filter(s -> [Link]().equals("CS")) // CS students only
.filter(s -> [Link]() >= 85) // Above 85
.map(Student::name) // Extract names
.sorted() // Sort alphabetically
.collect([Link]());
[Link](csToppers); // [Alice, Carol, David]
// Statistics
OptionalDouble avg = [Link]()
.mapToInt(Student::marks)
.average();
[Link]("Average: " + [Link]());
// Group by department
Map<String, List<Student>> byDept = [Link]()
.collect([Link](Student::dept));
[Link]((dept, list) ->
[Link](dept + ": " + [Link]() + " students"));
// Average marks per department
Map<String, Double> avgByDept = [Link]()
Page 5 | MITS Academy | [Link]
MITS Academy — Java Programming Course
.collect([Link](
Student::dept,
[Link](Student::marks)
));
[Link](avgByDept);
// Count students per department
[Link]()
.collect([Link](Student::dept,
[Link]()))
.forEach((k, v) -> [Link](k + ": " + v));
}
}
Module 4: Multithreading and Concurrency
4.1 Threads and Runnable
A thread is a lightweight unit of execution. Java supports multithreading natively. You can create
threads by extending Thread class or implementing Runnable interface. Runnable is preferred
because Java supports single inheritance — using Runnable allows the class to extend another
class.
The Thread lifecycle: NEW → RUNNABLE → RUNNING → BLOCKED/WAITING →
TERMINATED. The sleep() method pauses a thread. The join() method makes the calling
thread wait for another thread to finish. Thread priority hints the scheduler but is not guaranteed.
public class ThreadDemo {
// Method 1: Implement Runnable (preferred)
static class NumberPrinter implements Runnable {
private String name;
NumberPrinter(String name) { [Link] = name; }
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
[Link](name + ": " + i);
try { [Link](100); } catch (InterruptedException e) {}
}
}
}
public static void main(String[] args) throws InterruptedException {
// Create threads
Thread t1 = new Thread(new NumberPrinter("Thread-A"));
Thread t2 = new Thread(new NumberPrinter("Thread-B"));
// Lambda Runnable
Thread t3 = new Thread(() -> {
[Link]("Lambda thread running");
});
[Link]();
[Link]();
[Link]();
Page 6 | MITS Academy | [Link]
MITS Academy — Java Programming Course
[Link](); // Wait for t1 to finish
[Link](); // Wait for t2 to finish
[Link]("All threads finished");
}
}
4.2 Synchronization and ExecutorService
When multiple threads access shared data, race conditions can occur. Synchronization uses
locks to ensure only one thread executes a critical section at a time. The synchronized keyword
can be applied to methods or blocks. It uses intrinsic locks (monitors).
The ExecutorService provides a higher-level thread management API. Instead of creating
threads manually, you submit tasks to a thread pool. This is more efficient (reuses threads) and
easier to manage. Callable is like Runnable but can return a value and throw exceptions.
import [Link].*;
import [Link].*;
public class ConcurrencyDemo {
// Race condition example — WITHOUT sync
static int unsafeCounter = 0;
// Thread-safe with AtomicInteger
static AtomicInteger safeCounter = new AtomicInteger(0);
// Thread-safe with synchronized method
static int syncCounter = 0;
static synchronized void increment() { syncCounter++; }
public static void main(String[] args) throws Exception {
// ExecutorService — thread pool
ExecutorService executor = [Link](4);
// Submit 1000 increment tasks
for (int i = 0; i < 1000; i++) {
[Link](() -> {
unsafeCounter++; // NOT thread-safe
[Link](); // Thread-safe atomic
increment(); // Thread-safe synchronized
});
}
[Link]();
[Link](5, [Link]);
[Link]("Unsafe: " + unsafeCounter); // < 1000 (race
condition)
[Link]("Atomic: " + [Link]()); // 1000
[Link]("Sync: " + syncCounter); // 1000
// Callable — returns a value
ExecutorService exec = [Link]();
Future<Integer> future = [Link](() -> {
[Link](1000);
Page 7 | MITS Academy | [Link]
MITS Academy — Java Programming Course
return 42;
});
[Link]("Doing other work...");
[Link]("Result: " + [Link]()); // Blocks until done
[Link]();
}
}
Module 5: Design Patterns
5.1 Creational Patterns — Singleton and Factory
Design patterns are proven, reusable solutions to common software design problems. The
Singleton pattern ensures only one instance of a class exists throughout the application. The
Factory pattern provides a way to create objects without specifying the exact class.
The Builder pattern constructs complex objects step by step. It separates object construction
from its representation. This is ideal for objects with many optional parameters — avoids
telescoping constructors (constructors with many combinations of parameters).
// Singleton — one instance only
class DatabaseConnection {
private static volatile DatabaseConnection instance;
private String url;
private DatabaseConnection(String url) { [Link] = url; }
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized ([Link]) {
if (instance == null) // Double-checked locking
instance = new
DatabaseConnection("jdbc:mysql://localhost/mydb");
}
}
return instance;
}
public void query(String sql) { [Link]("Querying: " + sql); }
}
// Factory Pattern
interface Notification { void send(String message); }
class EmailNotification implements Notification {
public void send(String msg) { [Link]("Email: " + msg); }
}
class SMSNotification implements Notification {
public void send(String msg) { [Link]("SMS: " + msg); }
}
class PushNotification implements Notification {
public void send(String msg) { [Link]("Push: " + msg); }
}
class NotificationFactory {
public static Notification create(String type) {
return switch ([Link]()) {
case "email" -> new EmailNotification();
Page 8 | MITS Academy | [Link]
MITS Academy — Java Programming Course
case "sms" -> new SMSNotification();
case "push" -> new PushNotification();
default -> throw new IllegalArgumentException("Unknown type: " +
type);
};
}
}
public class PatternsDemo {
public static void main(String[] args) {
DatabaseConnection db1 = [Link]();
DatabaseConnection db2 = [Link]();
[Link](db1 == db2); // true — same instance
for (String type : new String[]{"email", "sms", "push"}) {
Notification n = [Link](type);
[Link]("Course enrollment confirmed!");
}
}
}
5.2 Structural Patterns — Builder and Observer
The Builder pattern creates complex objects step by step using method chaining. The Observer
pattern defines a one-to-many dependency: when one object (Subject) changes state, all
dependents (Observers) are notified. This is the foundation of event systems, MVC frameworks,
and reactive programming.
import [Link].*;
// Builder Pattern
class Student {
private final String name;
private final int age;
private final String email;
private final String course;
private final double fees;
private Student(Builder b) {
[Link] = [Link]; [Link] = [Link];
[Link] = [Link]; [Link] = [Link]; [Link] = [Link];
}
public static class Builder {
private final String name; // Required
private int age = 0;
private String email = "";
private String course = "";
private double fees = 0.0;
public Builder(String name) { [Link] = name; }
public Builder age(int age) { [Link] = age; return this; }
public Builder email(String email) { [Link] = email; return this; }
public Builder course(String course) { [Link] = course; return
this; }
public Builder fees(double fees) { [Link] = fees; return this; }
public Student build() { return new Student(this); }
}
Page 9 | MITS Academy | [Link]
MITS Academy — Java Programming Course
public String toString() {
return name + " | " + course + " | Rs" + fees;
}
}
// Observer Pattern
interface Observer { void update(String event, Object data); }
class EventManager {
private Map<String, List<Observer>> listeners = new HashMap<>();
public void subscribe(String event, Observer listener) {
[Link](event, k -> new ArrayList<>()).add(listener);
}
public void notify(String event, Object data) {
[Link](event, [Link]())
.forEach(l -> [Link](event, data));
}
}
class BuilderObserverDemo {
public static void main(String[] args) {
// Builder
Student s = new [Link]("Alice")
.age(20).email("alice@[Link]")
.course("Python").fees(5000).build();
[Link](s);
// Observer
EventManager em = new EventManager();
[Link]("enroll", (e, d) -> [Link]("Email sent for: "
+ d));
[Link]("enroll", (e, d) -> [Link]("SMS sent for: " +
d));
[Link]("payment", (e, d) -> [Link]("Receipt
generated: " + d));
[Link]("enroll", "Alice");
[Link]("payment", "Rs 5000");
}
}
Module 6: JDBC — Database Connectivity
6.1 Connecting Java to MySQL
JDBC (Java Database Connectivity) is the standard API for connecting Java applications to
relational databases. You need the JDBC driver for your database (e.g., mysql-connector-java
for MySQL). A Connection represents the database connection, Statement or
PreparedStatement executes SQL, and ResultSet holds the results.
Always use PreparedStatement instead of Statement for user-provided input.
PreparedStatement uses parameterized queries (? placeholders) which prevent SQL injection
attacks — one of the most critical security vulnerabilities. Always close resources in the finally
block or use try-with-resources.
import [Link].*;
Page 10 | MITS Academy | [Link]
MITS Academy — Java Programming Course
public class JDBCDemo {
private static final String URL =
"jdbc:mysql://localhost:3306/mitsacademy";
private static final String USER = "root";
private static final String PASS = "password";
// Create table
public static void createTable(Connection conn) throws SQLException {
String sql = """
CREATE TABLE IF NOT EXISTS students (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) UNIQUE,
marks DOUBLE DEFAULT 0,
enrolled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)""";
[Link]().execute(sql);
}
// Insert — PreparedStatement prevents SQL injection
public static int insert(Connection conn, String name, String email, double
marks)
throws SQLException {
String sql = "INSERT INTO students (name, email, marks) VALUES
(?, ?, ?)";
try (PreparedStatement ps = [Link](sql,
Statement.RETURN_GENERATED_KEYS)) {
[Link](1, name);
[Link](2, email);
[Link](3, marks);
[Link]();
ResultSet rs = [Link]();
return [Link]() ? [Link](1) : -1;
}
}
// Query with ResultSet
public static void getTopStudents(Connection conn) throws SQLException {
String sql = "SELECT name, marks FROM students WHERE marks >= ? ORDER
BY marks DESC";
try (PreparedStatement ps = [Link](sql)) {
[Link](1, 80.0);
ResultSet rs = [Link]();
while ([Link]()) {
[Link]("%-20s %.1f%n",
[Link]("name"), [Link]("marks"));
}
}
}
// Transaction example
public static void transferMarks(Connection conn, int from, int to, double
amount)
throws SQLException {
[Link](false); // Begin transaction
try {
PreparedStatement deduct = [Link](
"UPDATE students SET marks = marks - ? WHERE id = ?");
PreparedStatement add = [Link](
"UPDATE students SET marks = marks + ? WHERE id = ?");
Page 11 | MITS Academy | [Link]
MITS Academy — Java Programming Course
[Link](1, amount); [Link](2, from);
[Link]();
[Link](1, amount); [Link](2, to); [Link]();
[Link]();
} catch (SQLException e) {
[Link](); // Undo on failure
throw e;
} finally {
[Link](true);
}
}
public static void main(String[] args) {
try (Connection conn = [Link](URL, USER, PASS)) {
createTable(conn);
insert(conn, "Alice", "alice@[Link]", 92.5);
insert(conn, "Bob", "bob@[Link]", 85.0);
getTopStudents(conn);
} catch (SQLException e) {
[Link]();
}
}
}
Module 7: File I/O and Serialization
7.1 NIO and File Operations
Java's NIO (New I/O) package introduced in Java 7 provides the Files and Paths classes that
make file operations much cleaner and more powerful than the old [Link] API. The Files
class provides static methods for creating, copying, moving, deleting files and directories.
import [Link].*;
import [Link].*;
import [Link].*;
import [Link].*;
public class FileDemo {
public static void main(String[] args) throws IOException {
Path dir = [Link]("data");
Path file = [Link]("[Link]");
// Create directory if not exists
[Link](dir);
// Write lines
List<String> lines = [Link]("Alice,92", "Bob,78", "Carol,85");
[Link](file, lines, [Link]);
// Read all lines
List<String> read = [Link](file);
[Link]([Link]::println);
// Process with Stream
double avg = [Link](file)
.map(line -> [Link](","))
.mapToInt(parts -> [Link](parts[1]))
Page 12 | MITS Academy | [Link]
MITS Academy — Java Programming Course
.average().orElse(0);
[Link]("Average marks: %.2f%n", avg);
// Copy file
[Link](file, [Link]("[Link]"),
StandardCopyOption.REPLACE_EXISTING);
// List files in directory
[Link](dir).forEach(p -> [Link]([Link]()));
}
}
Assignments
Assignment 1: Generics and Streams
• Implement a generic MinMaxStack<T> that tracks minimum and maximum values in O(1)
time.
• Write a generic merge sort algorithm that works with any Comparable type.
• Using Stream API on a list of employees: filter by department, group by salary range, and
find the top earner per department.
• Implement a generic EventBus that allows publishing and subscribing to typed events.
• Write a fluent builder for a Pizza class with: size, crust, sauce, toppings (varargs), and
delivery option.
Assignment 2: Concurrency and Patterns
• Implement a thread-safe Singleton Logger using double-checked locking.
• Build a simple thread pool from scratch using BlockingQueue<Runnable>.
• Implement the Strategy pattern for a payment processor: support CreditCard, UPI, and
NetBanking strategies.
• Build a simple command-line chat simulator using two threads sharing a
BlockingQueue<String>.
• Write a JDBC DAO (Data Access Object) for a Student table with full CRUD operations
and a connection pool.
Projects
Project 1: Multi-threaded File Processing System
Build a concurrent file processing application:
• Read a large CSV file of student records (name, marks, subject)
• Process records in parallel using ExecutorService (4 threads)
• Each thread processes its chunk: calculate grades, flag failures
Page 13 | MITS Academy | [Link]
MITS Academy — Java Programming Course
• Merge results thread-safely using ConcurrentHashMap
• Write final processed data to an output CSV
• Print summary: total students, pass/fail count, top scorer per subject
• Measure and print total processing time comparing sequential vs concurrent
Project 2: Mini E-Commerce Backend
Build a console-based e-commerce system:
• Entities: Product (id, name, price, stock), Order (id, items, total, status), Customer (id,
name, orders)
• JDBC + MySQL: full CRUD for all entities
• Business logic: place order (check stock, reduce inventory, create order record in a
transaction)
• Factory pattern for creating different types of Products (Electronics, Books, Clothing)
• Observer pattern: notify observers (email log, inventory alert) when stock falls below
threshold
• Stream API: product search by name/category, top 5 best-selling products, revenue by
category
Page 14 | MITS Academy | [Link]