Skip to content
IT-306 · Java Programming Lab/Quick Revision Short Notes

Java Programming Lab (IT-306) - Unit 4 Short Notes

UNIT 4: Java Programming Lab - Intermediate & Advanced Concepts


4.1. Java Collections Framework Deep Dive

The Java Collections Framework (JCF) provides a unified architecture for representing and manipulating collections. It consists of core interfaces, implementations, and algorithms.

Core Interfaces & Hierarchy:

  • Collection<E>: Root interface for a group of objects.

    • List<E>: Ordered collection (sequence), allows duplicates. (ArrayList, LinkedList)

    • Set<E>: Unordered collection, no duplicates. (HashSet, TreeSet)

    • Queue<E> / Deque<E>: For holding elements prior to processing (FIFO/LIFO).

  • Map<K,V>: Not a true Collection. Stores key-value pairs, keys unique.

Implementations & Performance (Big O Time Complexity):

Interface Implementation Key Characteristics get()/contains() add()/remove() (at end) add()/remove() (at index/known position) Ordering
List ArrayList<E> Resizable array. Fast random access. O(1) O(1) amortized O(n) (shifts elements) Insertion order
LinkedList<E> Doubly-linked list. Fast inserts/removes. O(n) O(1) (if known node) O(1) (if known node) Insertion order
Set HashSet<E> Hash table. Uses hashCode() & equals(). O(1) O(1) N/A No guaranteed order
LinkedHashSet<E> Hash table + linked list. O(1) O(1) N/A Insertion order
TreeSet<E> Red-Black tree. O(log n) O(log n) N/A Sorted order (natural/comparator)
Map HashMap<K,V> Hash table. O(1) O(1) N/A No guaranteed order
LinkedHashMap<K,V> Hash table + linked list. O(1) O(1) N/A Insertion or access order
TreeMap<K,V> Red-Black tree. O(log n) O(log n) N/A Sorted order by keys
Hashtable<K,V> Legacy. Synchronized (thread-safe). O(1) O(1) N/A No guaranteed order

PriorityQueue: Implements a heap. peek()/poll(): O(log n). Ordering by natural order or Comparator.

Common Operations:

  • Iteration: Use enhanced for-loop or Iterator<E> (for safe removal via iterator.remove()).

  • Sorting: Collections.sort(list) for List. TreeSet/TreeMap for sorted Set/Map.

  • Conversion: new ArrayList<>(array); list.toArray(new String[0]).

[!TIP] Exam Focus: Be prepared to choose the right collection based on required operations (access pattern, ordering, uniqueness) and state the time complexity of core operations. Remember HashMap is not synchronized; use ConcurrentHashMap for thread-safe maps.


4.2. Generics for Type Safety

Motivation: Raw types (e.g., List list = new ArrayList();) allow any Object, leading to ClassCastException at runtime. Generics provide compile-time type checking.

Syntax:

  • Generic Class: class Box<T> { private T content; }

  • Generic Method: <T> void printArray(T[] arr) { ... }

  • Instantiation: Box<String> stringBox = new Box<>(); (Diamond operator <> from Java 7).

Wildcards (?):

  • ? : Unknown type.

  • ? extends T : Producer (PECS). Can read T or its subtypes. Safe for getting values.

    
    List<? extends Number> list = new ArrayList<Integer>();
    
    Number n = list.get(0); // Safe read
    
    // list.add(10); // Compile error - cannot add
    
    
  • ? super T : Consumer (PECS). Can write T or its subtypes. Safe for adding values.

    
    List<? super Integer> list = new ArrayList<Number>();
    
    list.add(10); // Safe write
    
    // Number n = list.get(0); // Compile error - can only get Object
    
    

Type Erasure: Generics are implemented by the compiler. All generic type information is erased at runtime and replaced with bounds (e.g., T becomes Object, T extends Number becomes Number). No runtime performance cost, but prevents using new T() or T.class.

[!TIP] Common Pitfall: Confusing ? extends T and ? super T. Remember PECS: Producer Extends, Consumer Super.


4.3. Exception Handling & Robust Programming

Exception Hierarchy:


Throwable

├── Error (System-level, unrecoverable, e.g., OutOfMemoryError)

└── Exception (Application-level)

    ├── RuntimeException (Unchecked, e.g., NullPointerException, IllegalArgumentException)

    └── Checked Exceptions (Must be caught or declared, e.g., IOException, SQLException)

Handling Mechanisms:


try {

    // Risky code

} catch (SpecificException e) {

    // Handle specific case

} catch (Exception e) {

    // Generic fallback (use sparingly)

} finally {

    // Always executes (except System.exit()), for cleanup (e.g., closing streams)

}
  • Try-with-resources (Java 7+): For AutoCloseable resources (streams, connections).

    
    try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    
        // Use br
    
    } // br.close() called automatically
    
    

Creating Custom Exceptions:

  • Checked: class MyCheckedException extends Exception { ... } (requires throws declaration).

  • Unchecked: class MyRuntimeException extends RuntimeException { ... } (no throws needed).

Best Practices:

  • Catch specific exceptions, not Exception or Throwable.

  • Never ignore caught exceptions (empty catch block).

  • Preserve stack trace: Use throw new MyException("msg", e); for exception chaining.

  • Log exceptions with context (using Logger).

  • Use unchecked exceptions for programming errors (invalid arguments).

  • Use checked exceptions for recoverable conditions (file not found).

[!TIP] Lab Focus: Wrap I/O and JDBC code in try-with-resources. Create domain-specific exceptions (e.g., InvalidUserInputException).


4.4. File I/O and Serialization

Byte Streams vs. Character Streams:

  • Byte Streams (InputStream/OutputStream): For raw binary data (images, .class files). Key classes: FileInputStream, FileOutputStream, BufferedInputStream.

  • Character Streams (Reader/Writer): For text data, handles character encoding (UTF-8, etc.). Key classes: FileReader, FileWriter, BufferedReader, BufferedWriter, PrintWriter.

Modern NIO.2 (java.nio.file - Java 7+):

  • Path & Paths: Platform-independent path representation.

  • Files utility class: Static methods for common operations.

    
    Path path = Paths.get("data.txt");
    
    String content = Files.readString(path); // Read all text
    
    Files.writeString(path, "new content"); // Write all text
    
    List<String> lines = Files.readAllLines(path);
    
    

Object Serialization:

  • Purpose: Convert an object graph into a byte stream for storage or transmission.

  • Requirements: Class must implement java.io.Serializable (marker interface).

  • Key Classes: ObjectOutputStream (writeObject()), ObjectInputStream (readObject()).

  • serialVersionUID: Explicitly declare to control versioning and avoid InvalidClassException.

[!TIP] Comparison: Prefer NIO.2 (Files) over old File/FileInputStream for simpler code and better performance. Use try-with-resources for all streams.


4.5. Multithreading & Concurrency Basics

Thread Creation:

  1. Extend Thread class and override run().

  2. Implement Runnable interface and pass to Thread constructor (preferred, as Java doesn't support multiple inheritance).

Thread Lifecycle States:

NEW → RUNNABLE → (BLOCKED/WAITING/TIMED_WAITING) → TERMINATED.

Synchronization & Locks:

  • synchronized keyword:

    • Method: public synchronized void method() { ... } (locks on this).

    • Block: synchronized(lockObject) { ... } (explicit lock object).

  • volatile keyword: Guarantees visibility of changes across threads (reads/writes go directly to main memory). Does not provide atomicity for compound actions (e.g., i++).

Concurrency Utilities (java.util.concurrent):

  • ExecutorService: Manages a pool of threads.

    
    ExecutorService pool = Executors.newFixedThreadPool(4);
    
    pool.submit(() -> task()); // Runnable
    
    Future<String> result = pool.submit(() -> { return "done"; }); // Callable
    
    pool.shutdown();
    
    
  • Callable<V>: Like Runnable but can return a value and throw checked exceptions.

Common Problems:

  • Race Condition: Multiple threads access shared mutable data without proper synchronization. Fix: synchronized or Lock.

  • Deadlock: Two or more threads waiting for each other's locks indefinitely. Fix: Avoid nested locks, enforce lock ordering.

  • Starvation: A thread is perpetually denied access to resources.

[!TIP] Lab Focus: Use ExecutorService over raw Thread creation. For simple atomic operations, consider java.util.concurrent.atomic package (e.g., AtomicInteger).


4.6. Introduction to Design Patterns (Creational & Behavioral)

Creational Patterns:

  • Singleton: Ensure a class has only one instance, provide a global point of access.

    
    // Thread-safe (Java 5+)
    
    public class Singleton {
    
        private static volatile Singleton instance; // volatile for double-checked locking
    
        private Singleton() {}
    
        public static Singleton getInstance() {
    
            if (instance == null) {
    
                synchronized (Singleton.class) {
    
                    if (instance == null) {
    
                        instance = new Singleton();
    
                    }
    
                }
    
            }
    
            return instance;
    
        }
    
    }
    
    // **Best:** Enum Singleton (inherently serializable & thread-safe)
    
    public enum EnumSingleton { INSTANCE; }
    
    
  • Factory Method: Define an interface for creating an object, but let subclasses decide which class to instantiate.

  • Abstract Factory: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Behavioral Patterns:

  • Observer: Define a one-to-many dependency so that when one object (subject) changes state, all its dependents (observers) are notified automatically.

    • Key: Subject interface with attach(Observer), notifyObservers(). Observer interface with update().
  • Strategy: Define a family of algorithms, encapsulate each one, and make them interchangeable. Lets the algorithm vary independently from clients that use it.

    • Key: Strategy interface with execute(). Concrete strategies implement it. Client holds a reference to a Strategy.

[!TIP] Application: Use Singleton for configuration managers, database connection pools. Use Strategy for different payment methods, sorting algorithms.


4.7. Java 8+ Features (Lab Integration)

Lambda Expressions: Concise representation of a functional interface (interface with one abstract method).

  • Syntax: (parameters) -> expression or (parameters) -> { statements; }.

  • Example: Runnable r = () -> System.out.println("Hello");

Functional Interfaces (from java.util.function):

  • Predicate<T>: test(T t) → boolean (condition).

  • Consumer<T>: accept(T t) → void (operation).

  • Function<T,R>: apply(T t) → R (transformation).

  • Supplier<T>: get() → T (supply value).

Method References: Shorter syntax for lambdas calling an existing method.

  • Class::staticMethod → (args) -> Class.staticMethod(args)

  • instance::instanceMethod → (args) -> instance.instanceMethod(args)

  • Class::instanceMethod → (obj, args) -> obj.instanceMethod(args)

Streams API: For processing sequences of elements (from collections, arrays, I/O).

  • Intermediate Operations (lazy, return a Stream): filter, map, sorted, distinct.

  • Terminal Operations (eager, produce result/side-effect): forEach, collect, reduce, count.

    
    list.stream()
    
        .filter(s -> s.length() > 3)
    
        .map(String::toUpperCase)
    
        .sorted()
    
        .forEach(System.out::println);
    
    

Optional<T>: Container object to represent the presence/absence of a value, avoiding NullPointerException.

  • Optional.of(value), Optional.empty().

  • Methods: isPresent(), get() (unsafe), orElse(default), ifPresent(consumer).

[!TIP] Refactoring: Convert legacy loops:

// Old

for (String s : list) { if (s.length()>3) System.out.println(s); }

// New

list.stream().filter(s -> s.length()>3).forEach(System.out::println);


4.8. Database Connectivity (JDBC)

JDBC Architecture (4-Tier):

  1. Application: Your Java code.

  2. JDBC API: DriverManager, Connection, Statement, ResultSet.

  3. JDBC Driver Manager: Loads driver-specific classes.

  4. Database: Actual DBMS.

Core Steps for CRUD:


// 1. Load Driver (optional since JDBC 4.0)

// Class.forName("com.mysql.cj.jdbc.Driver");

// 2. Get Connection

String url = "jdbc:mysql://localhost:3306/db";

try (Connection conn = DriverManager.getConnection(url, "user", "pass")) {

    // 3. Create Statement

    // For static SQL (no user input):

    // Statement stmt = conn.createStatement();

    // ResultSet rs = stmt.executeQuery("SELECT * FROM users");

    // **ALWAYS use PreparedStatement for queries with user input** (prevents SQL injection)

    String sql = "INSERT INTO users(name, email) VALUES(?, ?)";

    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {

        pstmt.setString(1, "Alice");

        pstmt.setString(2, "[email protected]");

        int rows = pstmt.executeUpdate(); // For INSERT/UPDATE/DELETE

    }

    // 4. Process ResultSet (for SELECT)

    try (Statement stmt = conn.createStatement();

         ResultSet rs = stmt.executeQuery("SELECT * FROM users")) {

        while (rs.next()) {

            int id = rs.getInt("id");

            String name = rs.getString("name");

        }

    }

}

Transaction Management:


conn.setAutoCommit(false); // Start transaction

try {

    // multiple DML statements

    conn.commit(); // Success

} catch (Exception e) {

    conn.rollback(); // Failure

    throw e;

}

DAO Pattern: Encapsulate all JDBC code in a Data Access Object class.

  • UserDAO with methods: User findById(int id), void save(User user).

[!TIP] Security: Never concatenate user input into SQL strings. Always use PreparedStatement. Manage resources with try-with-resources.


4.9. Unit Testing with JUnit 5 (Jupiter)

Core Annotations:

  • @Test: Marks a method as a test method.

  • @BeforeEach: Runs before each @Test method.

  • @AfterEach: Runs after each @Test method.

  • @BeforeAll: Runs once before all tests in the class (must be static).

  • @AfterAll: Runs once after all tests (must be static).

  • @DisplayName("Custom Name"): Custom test name in reports.

Assertions (static imports from org.junit.jupiter.api.Assertions):

  • assertEquals(expected, actual)

  • assertTrue(condition), assertFalse(condition)

  • assertNull(obj), assertNotNull(obj)

  • assertThrows(Exception.class, () -> { method(); }) // Verify exception thrown

  • assertArrayEquals(expectedArray, actualArray)

Test Lifecycle Example:


class MyTest {

    @BeforeAll

    static void initAll() { /* runs once */ }

    @BeforeEach

    void init() { /* runs before each test */ }

    @Test

    @DisplayName("Test adding positive numbers")

    void testAdd() {

        assertEquals(5, Calculator.add(2, 3));

    }

    @Test

    void testDivideByZero() {

        assertThrows(ArithmeticException.class, () -> Calculator.divide(10, 0));

    }

    @AfterEach

    void tearDown() { /* cleanup */ }

    @AfterAll

    static void tearDownAll() { /* final cleanup */ }

}

Test Naming Convention: methodName_StateUnderTest_ExpectedBehavior() (e.g., withdraw_ insufficientFunds_throwsException).

[!TIP] Lab Focus: Write tests for business logic methods (not getters/setters). Test edge cases and exception paths. Aim for meaningful assertions and isolated tests (no dependency on external resources like DB; use mocks if needed).

Go to where you left off?

Quick Add to Notes

Save questions, your own notes and screenshots into notes filed by unit. It takes a free account.

Create free account

Have an account? Log in