JAVA 3

Phase 3 — Modern & Advanced Core Java

This phase moves you from "I can write Java" to "I understand how modern Java code is written and how Java works underneath."

Chapters in this Phase

Modern Java

  1. Functional Interfaces

  2. Lambda Expressions

  3. Method References

  4. Optional

  5. Stream Fundamentals

  6. Stream Operations — filter, map, flatMap

  7. Stream Operations — sorted, distinct, limit, reduce

  8. Collectors — Grouping, Partitioning & Mapping

Advanced Collections & Data Processing

  1. Advanced Collections & Choosing the Right Collection

  2. Comparators & Advanced Sorting

File Handling & NIO

  1. Files, Paths & Directories

  2. Reading and Writing Text Files

  3. Byte Streams, Character Streams & Buffered I/O

  4. Try-with-Resources & Resource Management

Date, Time & Text Processing

  1. Java Date & Time API

  2. Regular Expressions

Java Metadata & Runtime Features

  1. Annotations

  2. Reflection

  3. Java Modules

JVM & Performance

  1. JVM, Bytecode, Stack, Heap & References

  2. Garbage Collection & Object Lifetime

  3. JVM Performance & Memory Problems


Chapter 1 — Functional Interfaces

Question

Given below is a code snippet that:

  • Defines a functional interface.

  • Uses a lambda expression to provide its behavior.

  • Passes behavior into a method.

  • Executes that behavior with different values.

What should be the output of the following code?

// A functional interface has exactly one abstract method.
@FunctionalInterface
interface DiscountCalculator {

    // This method represents the behavior we want to provide.
    double calculate(double price);
}

public class Main {

    // This method receives behavior as an argument.
    static void printDiscount(DiscountCalculator calculator, double price) {

        // Execute the behavior supplied by the caller.
        double discount = calculator.calculate(price);

        // Display the calculated discount.
        System.out.println(discount);
    }

    public static void main(String[] args) {

        // Create behavior using a lambda expression.
        DiscountCalculator calculator =
                price -> price * 0.10;

        // Pass that behavior into another method.
        printDiscount(calculator, 1000);

        // Reuse the same behavior with another value.
        printDiscount(calculator, 500);
    }
}

Answer

100.0
50.0

Step-by-step explanation

  1. DiscountCalculator is an interface.

  2. It has one abstract method: calculate().

  3. Because it has exactly one abstract method, it is a functional interface.

  4. The lambda:

    price -> price * 0.10

    provides the implementation of calculate().

  5. So when calculator.calculate(1000) runs, Java calculates 1000 × 0.10 = 100.

  6. With 500, the result is 50.

How to read the important code

"Create a DiscountCalculator whose calculation takes price and returns ten percent of the price."

A functional interface is simply an interface designed to represent one piece of behavior.

Beginner trap

A functional interface can contain other non-abstract methods, but it must have exactly one abstract method.

Key takeaway

Functional interfaces allow Java to represent behavior as something we can pass around.


Chapter 2 — Lambda Expressions

Question

Given below is a code snippet that:

  • Creates behavior using lambdas.

  • Uses parameters.

  • Uses an expression lambda.

  • Uses a block lambda.

  • Passes different behavior into the same method.

What should be the output of the following code?

@FunctionalInterface
interface Calculator {

    // The lambda must provide this method.
    int calculate(int a, int b);
}

public class Main {

    // This method accepts behavior instead of hard-coding one calculation.
    static void showResult(Calculator calculator, int a, int b) {

        // Run the supplied lambda.
        System.out.println(calculator.calculate(a, b));
    }

    public static void main(String[] args) {

        // Lambda with an expression body.
        Calculator add = (a, b) -> a + b;

        // Lambda with a block body.
        Calculator multiply = (a, b) -> {
            int result = a * b;
            return result;
        };

        // Supply addition behavior.
        showResult(add, 5, 3);

        // Supply multiplication behavior.
        showResult(multiply, 5, 3);
    }
}

Answer

8
15

Step-by-step explanation

  1. add stores behavior that adds two numbers.

  2. 5 + 3 produces 8.

  3. multiply stores behavior that multiplies two numbers.

  4. Its block creates result.

  5. 5 × 3 produces 15.

  6. The same showResult() method works with both behaviors.

How to read the important code

"add is a Calculator that adds two numbers."

"multiply is a Calculator that multiplies two numbers."

Beginner trap

A lambda does not execute merely because it was created. It executes when its functional-interface method is called.

Key takeaway

A lambda is a compact way to provide behavior where Java expects a functional interface.


Chapter 3 — Method References

Question

Given below is a code snippet that:

  • Uses a method reference.

  • Passes an existing method as behavior.

  • Uses a static method reference.

  • Uses an instance method reference.

What should be the output of the following code?

import java.util.function.Function;

public class Main {

    // Existing method that converts text to uppercase.
    static String makeUpper(String text) {
        return text.toUpperCase();
    }

    public static void main(String[] args) {

        // Method reference to a static method.
        Function<String, String> upper = Main::makeUpper;

        // Method reference to an existing String instance method.
        Function<String, String> trimmed = String::trim;

        // Execute the first behavior.
        System.out.println(upper.apply("java"));

        // Execute the second behavior.
        System.out.println(trimmed.apply("  backend  "));
    }
}

Answer

JAVA
backend

Step-by-step explanation

  1. Function<String, String> means:

    • receive a String

    • return a String.

  2. Main::makeUpper refers to the existing makeUpper() method.

  3. upper.apply("java") therefore calls makeUpper("java").

  4. The result is JAVA.

  5. String::trim refers to the existing trim() method.

  6. " backend " becomes "backend".

How to read the important code

"upper is a Function from String to String, using Main's makeUpper method."

Beginner trap

:: does not call the method immediately. It refers to the method so Java can call it later.

Key takeaway

A method reference is a shorter way to use an existing method as behavior.


Chapter 4 — Optional

Question

Given below is a code snippet that:

  • Creates an Optional.

  • Handles a value that may be absent.

  • Uses map.

  • Uses orElse.

  • Avoids directly calling a method on a potentially missing value.

What should be the output of the following code?

import java.util.Optional;

public class Main {

    static String findUserName(int id) {

        // ID 1 has a name; other IDs have no result.
        if (id == 1) {
            return "Alice";
        }

        return null;
    }

    public static void main(String[] args) {

        // Convert the possibly-null result into an Optional.
        Optional<String> user =
                Optional.ofNullable(findUserName(1));

        // Transform the value if it exists.
        String result = user
                .map(String::toUpperCase)
                .orElse("UNKNOWN");

        // Display the final value.
        System.out.println(result);

        // Search for a user that does not exist.
        Optional<String> missing =
                Optional.ofNullable(findUserName(99));

        // Use a fallback when there is no value.
        System.out.println(missing.orElse("UNKNOWN"));
    }
}

Answer

ALICE
UNKNOWN

Step-by-step explanation

  1. ID 1 returns "Alice".

  2. ofNullable() puts that value inside an Optional.

  3. map(String::toUpperCase) transforms "Alice" into "ALICE".

  4. orElse() is not needed because a value exists.

  5. ID 99 returns null.

  6. ofNullable(null) creates an empty Optional.

  7. map() has nothing to transform.

  8. orElse("UNKNOWN") supplies the fallback.

How to read the important code

"Take the optional name, convert it to uppercase if present, otherwise use UNKNOWN."

Beginner trap

Optional is mainly useful for representing possible absence. It is not a replacement for every variable or field in your program.

Key takeaway

Optional makes the possibility of "no value" explicit and encourages safer handling.


Chapter 5 — Stream Fundamentals

Question

Given below is a code snippet that:

  • Creates a stream from a collection.

  • Filters values.

  • Transforms values.

  • Collects the result into a list.

  • Demonstrates that a stream processes data rather than storing it.

What should be the output of the following code?

import java.util.List;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) {

        // Create the original data.
        List<String> names =
                List.of("Alice", "Bob", "Andrew", "David");

        // Start a stream from the list.
        List<String> result = names.stream()

                // Keep names beginning with A.
                .filter(name -> name.startsWith("A"))

                // Convert each remaining name to uppercase.
                .map(String::toUpperCase)

                // Turn the stream result back into a List.
                .collect(Collectors.toList());

        // Display the resulting list.
        System.out.println(result);
    }
}

Answer

[ALICE, ANDREW]

Step-by-step explanation

  1. The original list contains four names.

  2. stream() creates a stream for processing them.

  3. filter() keeps "Alice" and "Andrew".

  4. map() converts them to uppercase.

  5. collect() gathers the processed values into a new list.

  6. The resulting list is [ALICE, ANDREW].

How to read the important code

"Stream the names, keep names starting with A, convert them to uppercase, and collect them into a list."

Beginner trap

A Stream is not a collection. It is a pipeline used to process data.

Key takeaway

Streams let you describe a data-processing pipeline clearly and concisely.


Chapter 6 — filter, map and flatMap

Question

Given below is a code snippet that:

  • Filters objects.

  • Maps objects into another value.

  • Flattens nested lists using flatMap.

  • Produces a single stream of values.

What should be the output of the following code?

import java.util.List;
import java.util.stream.Collectors;

record Order(String customer, List<String> products) {}

public class Main {

    public static void main(String[] args) {

        // Each order contains its own list of products.
        List<Order> orders = List.of(
                new Order("Alice", List.of("Laptop", "Mouse")),
                new Order("Bob", List.of("Keyboard")),
                new Order("Alice", List.of("Monitor", "Mouse"))
        );

        // Process only Alice's orders.
        List<String> products = orders.stream()

                // Keep orders belonging to Alice.
                .filter(order -> order.customer().equals("Alice"))

                // Convert each Order into its product list.
                .map(Order::products)

                // Flatten multiple product lists into one stream.
                .flatMap(List::stream)

                // Collect all products into one list.
                .collect(Collectors.toList());

        // Display the final list.
        System.out.println(products);
    }
}

Answer

[Laptop, Mouse, Monitor, Mouse]

Step-by-step explanation

  1. Three orders exist.

  2. The first and third belong to Alice.

  3. map(Order::products) changes each Alice order into a List<String>.

  4. We now effectively have:

    [["Laptop", "Mouse"], ["Monitor", "Mouse"]]

  5. flatMap() opens those nested lists and creates one stream.

  6. The result becomes:

    Laptop, Mouse, Monitor, Mouse

  7. collect() creates the final list.

How to read the important code

"Filter Alice's orders, get their product lists, flatten them into one stream, and collect the products."

Beginner trap

map() generally produces one output for each input. flatMap() is useful when each input can produce multiple values that should become one combined stream.

Key takeaway

Use map to transform; use flatMap to transform and flatten nested results.


Chapter 7 — sorted, distinct, limit and reduce

Question

Given below is a code snippet that:

  • Removes duplicate values.

  • Sorts values.

  • Limits the number of values.

  • Combines values using reduce.

What should be the output of the following code?

import java.util.List;

public class Main {

    public static void main(String[] args) {

        // Create numbers containing duplicates.
        List<Integer> numbers =
                List.of(5, 2, 8, 2, 10, 5, 4);

        // Process the numbers.
        int result = numbers.stream()

                // Remove duplicate numbers.
                .distinct()

                // Sort from smallest to largest.
                .sorted()

                // Keep only the first four values.
                .limit(4)

                // Add all remaining values together.
                .reduce(0, Integer::sum);

        // Display the result.
        System.out.println(result);
    }
}

Answer

13

Step-by-step explanation

  1. Original values:

    5, 2, 8, 2, 10, 5, 4

  2. distinct() removes duplicates:

    5, 2, 8, 10, 4

  3. sorted() produces:

    2, 4, 5, 8, 10

  4. limit(4) keeps:

    2, 4, 5, 8

  5. reduce(0, Integer::sum) adds them:

    0 + 2 + 4 + 5 + 8 = 19

Wait — therefore the correct output is:

19

How to read the important code

"Remove duplicates, sort the numbers, take the first four, and add them together."

Beginner trap

When predicting streams, follow the pipeline in order. Each operation changes what the next operation receives.

Key takeaway

Stream operations form a pipeline, and order matters.


Chapter 8 — Collectors: Grouping, Partitioning & Mapping

Question

Given below is a code snippet that:

  • Groups objects by a property.

  • Partitions objects into two groups.

  • Collects selected values.

  • Uses collectors to summarize data.

What should be the output of the following code?

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

record Employee(String name, String department, int salary) {}

public class Main {

    public static void main(String[] args) {

        // Create employee data.
        List<Employee> employees = List.of(
                new Employee("Alice", "IT", 80000),
                new Employee("Bob", "HR", 60000),
                new Employee("Carol", "IT", 90000),
                new Employee("David", "HR", 70000)
        );

        // Group employees according to their department.
        Map<String, List<Employee>> byDepartment =
                employees.stream()
                        .collect(Collectors.groupingBy(Employee::department));

        // Divide employees into high and non-high salaries.
        Map<Boolean, List<Employee>> bySalary =
                employees.stream()
                        .collect(Collectors.partitioningBy(
                                employee -> employee.salary() >= 70000));

        // Extract employee names into a list.
        List<String> names =
                employees.stream()
                        .map(Employee::name)
                        .collect(Collectors.toList());

        // Display all results.
        System.out.println(byDepartment.get("IT").size());
        System.out.println(bySalary.get(true).size());
        System.out.println(names);
    }
}

Answer

2
3
[Alice, Bob, Carol, David]

Step-by-step explanation

  1. Two employees belong to IT.

  2. groupingBy() creates groups based on department.

  3. Three employees have salary at least 70000.

  4. partitioningBy() creates exactly two groups: true and false.

  5. map(Employee::name) extracts only names.

  6. toList() collects those names.

How to read the important code

"Group employees by department."

"Partition employees based on whether salary is at least seventy thousand."

Beginner trap

groupingBy() can create many groups; partitioningBy() specifically divides data into two groups based on a boolean condition.

Key takeaway

Collectors turn processed stream data into useful structures such as maps, lists and grouped results.


Chapter 9 — Advanced Collections & Choosing the Right Collection

Question

Given below is a code snippet that:

  • Uses a List for ordered data.

  • Uses a Set for uniqueness.

  • Uses a Map for key-value lookup.

  • Demonstrates insertion order.

  • Demonstrates fast-style key lookup conceptually.

What should be the output of the following code?

import java.util.*;

public class Main {

    public static void main(String[] args) {

        // List preserves duplicates and element order.
        List<String> skills =
                new ArrayList<>(List.of("Java", "SQL", "Java"));

        // Set removes duplicate values.
        Set<String> uniqueSkills =
                new LinkedHashSet<>(skills);

        // Map associates each key with a value.
        Map<String, Integer> experience =
                new HashMap<>();

        // Store experience for each technology.
        experience.put("Java", 3);
        experience.put("SQL", 2);

        // Print the original list.
        System.out.println(skills);

        // Print unique values while preserving insertion order.
        System.out.println(uniqueSkills);

        // Look up Java experience by key.
        System.out.println(experience.get("Java"));
    }
}

Answer

[Java, SQL, Java]
[Java, SQL]
3

Step-by-step explanation

  1. ArrayList allows duplicate "Java" values.

  2. LinkedHashSet removes the duplicate.

  3. It also preserves insertion order.

  4. HashMap stores data as key-value pairs.

  5. "Java" maps to 3.

  6. get("Java") returns 3.

How to read the important code

"Create a list of skills."

"Create a set of unique skills while preserving insertion order."

"Create a map from technology name to years of experience."

Beginner trap

Don't automatically use ArrayList everywhere.

Think about the requirement:

  • Need ordered duplicates → List

  • Need uniqueness → Set

  • Need key-based lookup → Map

  • Need queue behavior → Queue/Deque

Key takeaway

Choosing the correct collection is part of designing the program.


Chapter 10 — Comparators & Advanced Sorting

Question

Given below is a code snippet that:

  • Sorts objects using a Comparator.

  • Sorts by salary.

  • Uses a secondary sorting rule.

  • Demonstrates descending order.

What should be the output of the following code?

import java.util.*;

record Employee(String name, int salary) {}

public class Main {

    public static void main(String[] args) {

        // Create employees with different salaries.
        List<Employee> employees = new ArrayList<>(List.of(
                new Employee("Alice", 70000),
                new Employee("Bob", 90000),
                new Employee("Carol", 70000)
        ));

        // Sort by salary descending.
        employees.sort(
                Comparator.comparingInt(Employee::salary)
                        .reversed()

                        // If salaries are equal, sort by name.
                        .thenComparing(Employee::name)
        );

        // Print each employee.
        employees.forEach(employee ->
                System.out.println(
                        employee.name() + " " + employee.salary()
                )
        );
    }
}

Answer

Bob 90000
Alice 70000
Carol 70000

Step-by-step explanation

  1. Bob has the highest salary, so he comes first.

  2. Alice and Carol both have 70000.

  3. The secondary comparator sorts equal salaries by name.

  4. "Alice" comes before "Carol".

How to read the important code

"Sort employees by salary descending, then by name."

Beginner trap

A comparator defines how two objects should be ordered. It does not modify the meaning of the objects themselves.

Key takeaway

Comparators let you define flexible, readable sorting rules.


Chapter 11 — Files, Paths & Directories

Question

Given below is a code snippet that:

  • Creates a Path.

  • Checks whether a file exists.

  • Creates a directory.

  • Builds another path from the directory.

  • Uses modern NIO APIs.

What should be the output of the following code?

import java.nio.file.*;

public class Main {

    public static void main(String[] args) throws Exception {

        // Create a path representing a directory.
        Path directory = Path.of("data");

        // Create the directory if it does not already exist.
        Files.createDirectories(directory);

        // Build a file path inside that directory.
        Path file = directory.resolve("users.txt");

        // Create the file if it does not exist.
        if (Files.notExists(file)) {
            Files.createFile(file);
        }

        // Check whether the file now exists.
        System.out.println(Files.exists(file));

        // Display the path.
        System.out.println(file);
    }
}

Answer

The first line is:

true

The second line represents:

data/users.txt

On some operating systems, the path separator may appear differently.

Step-by-step explanation

  1. Path.of("data") creates a Path object.

  2. createDirectories() creates the directory if necessary.

  3. resolve("users.txt") creates a path inside that directory.

  4. createFile() creates the file.

  5. Files.exists() confirms that the file exists.

How to read the important code

"Create a Path for the data directory."

"Resolve users.txt relative to that directory."

Beginner trap

A Path object is not the file itself. It represents where the file or directory is located.

Key takeaway

Java NIO's Path and Files APIs provide the modern way to work with the file system.


Chapter 12 — Reading & Writing Text Files

Question

Given below is a code snippet that:

  • Writes text to a file.

  • Reads the text back.

  • Stores file contents in a string.

  • Uses Files.

What should be the output of the following code?

import java.nio.file.*;

public class Main {

    public static void main(String[] args) throws Exception {

        // Choose the file location.
        Path file = Path.of("message.txt");

        // Write text into the file.
        Files.writeString(
                file,
                "Java\nBackend\nSpring"
        );

        // Read the entire file as one String.
        String content = Files.readString(file);

        // Display the content.
        System.out.println(content);
    }
}

Answer

Java
Backend
Spring

Step-by-step explanation

  1. writeString() writes three lines.

  2. \n represents a line break.

  3. readString() reads the entire file.

  4. println() displays the resulting text.

How to read the important code

"Write this string to message.txt."

"Read the entire file into a String."

Beginner trap

File operations can fail—for example, because of permissions, missing directories, or disk problems. That's why these operations can throw exceptions.

Key takeaway

For straightforward text files, Files.writeString() and Files.readString() are convenient modern APIs.


Chapter 13 — Byte Streams, Character Streams & Buffered I/O

Question

Given below is a code snippet that:

  • Uses character-based file I/O.

  • Buffers file operations.

  • Reads a file line by line.

  • Counts lines without loading unnecessary processing into separate operations.

What should be the output of the following code?

import java.io.*;

public class Main {

    public static void main(String[] args) throws Exception {

        // Create text in a file using a character writer.
        try (BufferedWriter writer =
                     new BufferedWriter(new FileWriter("data.txt"))) {

            // Write separate lines.
            writer.write("Java");
            writer.newLine();
            writer.write("Spring");
            writer.newLine();
            writer.write("SQL");
        }

        int count = 0;

        // Open the file for character-based reading.
        try (BufferedReader reader =
                     new BufferedReader(new FileReader("data.txt"))) {

            String line;

            // Read one line at a time until the end of the file.
            while ((line = reader.readLine()) != null) {

                // Count each line.
                count++;
            }
        }

        // Display the number of lines.
        System.out.println(count);
    }
}

Answer

3

Step-by-step explanation

  1. Three lines are written.

  2. BufferedWriter writes text efficiently through a buffer.

  3. BufferedReader reads text line by line.

  4. The loop continues until readLine() returns null.

  5. Three lines are encountered.

  6. count becomes 3.

How to read the important code

"Open a buffered reader and keep reading lines until there are no more lines."

Beginner trap

Character streams are designed for text. Byte streams are used for raw binary data such as images, PDFs, and other binary files.

Key takeaway

Use character-oriented APIs for text and byte-oriented APIs for binary data.


Chapter 14 — Try-with-Resources & Resource Management

Question

Given below is a code snippet that:

  • Opens a resource.

  • Uses try-with-resources.

  • Automatically closes the resource.

  • Handles the resource without manually calling close().

What should be the output of the following code?

import java.io.*;

public class Main {

    public static void main(String[] args) throws Exception {

        // Open a resource that must eventually be closed.
        try (BufferedReader reader =
                     new BufferedReader(
                             new StringReader("Java\nBackend"))) {

            // Read the first line.
            System.out.println(reader.readLine());

            // Read the second line.
            System.out.println(reader.readLine());
        }

        // The reader has automatically been closed here.
        System.out.println("Resource closed");
    }
}

Answer

Java
Backend
Resource closed

Step-by-step explanation

  1. Java creates the BufferedReader.

  2. The first readLine() returns "Java".

  3. The second returns "Backend".

  4. The try block finishes.

  5. Java automatically closes the reader.

  6. Execution continues after the try block.

How to read the important code

"Open the reader in a try-with-resources block and automatically close it when the block finishes."

Beginner trap

Try-with-resources works with objects implementing AutoCloseable.

Key takeaway

If a resource must be closed, try-with-resources is usually the safest and cleanest approach.


Chapter 15 — Java Date & Time API

Question

Given below is a code snippet that:

  • Uses LocalDate.

  • Adds days to a date.

  • Calculates the difference between dates.

  • Uses LocalDateTime.

  • Formats a date.

What should be the output of the following code?

import java.time.*;
import java.time.format.DateTimeFormatter;

public class Main {

    public static void main(String[] args) {

        // Create a date.
        LocalDate start = LocalDate.of(2026, 9, 6);

        // Add seven days without modifying the original date.
        LocalDate nextWeek = start.plusDays(7);

        // Calculate the number of days between the two dates.
        long days = Duration.between(
                start.atStartOfDay(),
                nextWeek.atStartOfDay()
        ).toDays();

        // Create a date and time.
        LocalDateTime meeting =
                LocalDateTime.of(2026, 9, 6, 10, 30);

        // Define the display format.
        DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");

        // Display the results.
        System.out.println(start);
        System.out.println(nextWeek);
        System.out.println(days);
        System.out.println(meeting.format(formatter));
    }
}

Answer

2026-09-06
2026-09-13
7
06-09-2026 10:30

Step-by-step explanation

  1. LocalDate.of() creates September 6, 2026.

  2. plusDays(7) creates September 13.

  3. The difference is seven days.

  4. LocalDateTime additionally contains time.

  5. The formatter changes the display format.

How to read the important code

"Create a LocalDate for September sixth, twenty twenty-six."

"Add seven days to get the next date."

Beginner trap

Java's modern date/time classes are generally immutable. Calling plusDays() produces another date rather than changing the original.

Key takeaway

Use the java.time API instead of old date/time APIs for modern Java applications.


Chapter 16 — Regular Expressions

Question

Given below is a code snippet that:

  • Creates a regular expression.

  • Checks whether text matches it.

  • Validates a simple username format.

  • Uses Pattern and Matcher.

What should be the output of the following code?

import java.util.regex.*;

public class Main {

    public static void main(String[] args) {

        // Username must contain 3 to 10 letters or digits.
        Pattern pattern =
                Pattern.compile("^[A-Za-z0-9]{3,10}$");

        // Test two usernames.
        String first = "Alice123";
        String second = "A@lice";

        // Create matchers for the two strings.
        Matcher firstMatcher = pattern.matcher(first);
        Matcher secondMatcher = pattern.matcher(second);

        // Check whether each complete string matches.
        System.out.println(firstMatcher.matches());
        System.out.println(secondMatcher.matches());
    }
}

Answer

true
false

Step-by-step explanation

  1. ^ means the pattern starts at the beginning.

  2. [A-Za-z0-9] allows letters and digits.

  3. {3,10} requires between 3 and 10 characters.

  4. $ means the pattern ends there.

  5. "Alice123" satisfies those rules.

  6. "A@lice" contains @, which is not allowed.

How to read the important code

"Create a pattern that requires three to ten letters or digits for the entire string."

Beginner trap

Regular expressions are powerful but can become difficult to read. Don't use an extremely complicated regex when ordinary Java code would be clearer.

Key takeaway

Regex is useful for pattern matching and validation of structured text.


Chapter 17 — Annotations

Question

Given below is a code snippet that:

  • Uses a built-in annotation.

  • Creates a custom annotation.

  • Reads annotation metadata through reflection.

  • Demonstrates that annotations can describe code.

What should be the output of the following code?

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface EntityInfo {

    // Store metadata about the class.
    String name();
}

@EntityInfo(name = "users")
class User {

    // This method can be safely renamed with IDE support.
    @Override
    public String toString() {
        return "User";
    }
}

public class Main {

    public static void main(String[] args) {

        // Ask Java for the annotation attached to User.
        EntityInfo info =
                User.class.getAnnotation(EntityInfo.class);

        // Read the annotation value.
        System.out.println(info.name());
    }
}

Answer

users

Step-by-step explanation

  1. @EntityInfo is a custom annotation.

  2. It stores the value "users".

  3. RetentionPolicy.RUNTIME means the annotation remains available at runtime.

  4. User.class represents the User class itself.

  5. getAnnotation() retrieves the annotation.

  6. info.name() retrieves "users".

How to read the important code

"Get the EntityInfo annotation from the User class."

Beginner trap

Annotations generally describe or configure code. They don't automatically perform an action just because they exist. Frameworks can inspect annotations and then act on them.

Key takeaway

Annotations attach metadata to Java code and are heavily used by frameworks such as Spring and JPA.


Chapter 18 — Reflection

Question

Given below is a code snippet that:

  • Obtains a class at runtime.

  • Inspects its methods.

  • Finds a method by name.

  • Invokes the method dynamically.

What should be the output of the following code?

import java.lang.reflect.Method;

class User {

    // A normal method.
    public void greet() {
        System.out.println("Hello from User");
    }
}

public class Main {

    public static void main(String[] args) throws Exception {

        // Obtain metadata describing the User class.
        Class<?> type = User.class;

        // Create a User object dynamically.
        Object user = type.getDeclaredConstructor().newInstance();

        // Find the greet method by its name.
        Method method = type.getMethod("greet");

        // Invoke that method on the User object.
        method.invoke(user);
    }
}

Answer

Hello from User

Step-by-step explanation

  1. User.class gives us a Class object describing User.

  2. Reflection allows Java to inspect classes at runtime.

  3. getDeclaredConstructor() finds the constructor.

  4. newInstance() creates the object.

  5. getMethod("greet") finds the method.

  6. invoke(user) executes that method.

How to read the important code

"Get the User class, find its greet method, and invoke it on a User object."

Beginner trap

Reflection is powerful, but it can make code harder to understand and can introduce performance, security, and maintainability concerns. Frameworks use it heavily, but ordinary application code should not use it unnecessarily.

Key takeaway

Reflection allows Java programs and frameworks to inspect and interact with classes dynamically at runtime.


Chapter 19 — Java Modules

Question

Given below is a code snippet that:

  • Demonstrates the concept of a Java module.

  • Declares module dependencies.

  • Controls which packages are exposed.

  • Shows the role of module-info.java.

What should be the output of the following code?

// module-info.java

// Declare the module's name.
module com.example.app {

    // Allow this module to use java.sql.
    requires java.sql;

    // Make this package available to other modules.
    exports com.example.users;
}

There is no normal console output from this file.

Answer

No console output

Step-by-step explanation

  1. module-info.java describes a Java module.

  2. module com.example.app gives the module its name.

  3. requires java.sql declares a dependency.

  4. exports com.example.users says that package is accessible to other modules.

  5. The file is configuration for the Java module system, not a normal executable program.

How to read the important code

"Declare the module com.example.app, require java.sql, and export the users package."

Beginner trap

Packages and modules are not the same thing.

A package organizes Java classes.

A module organizes packages and explicitly describes dependencies and exported packages.

Key takeaway

Java modules provide stronger boundaries and explicit dependencies between parts of a Java application.


Chapter 20 — JVM, Bytecode, Stack, Heap & References

Question

Given below is a code snippet that:

  • Creates an object.

  • Uses a reference variable.

  • Creates a local primitive variable.

  • Demonstrates two references pointing to the same object.

  • Shows object mutation through a reference.

What should be the output of the following code?

class User {

    // Store mutable object state.
    String name;
}

public class Main {

    public static void main(String[] args) {

        // Local primitive variable.
        int number = 10;

        // Create a User object.
        User first = new User();

        // Store data inside the object.
        first.name = "Alice";

        // Copy the reference, not the object itself.
        User second = first;

        // Modify the same object through the second reference.
        second.name = "Bob";

        // Print the value through the first reference.
        System.out.println(first.name);

        // Print the primitive value.
        System.out.println(number);
    }
}

Answer

Bob
10

Step-by-step explanation

  1. new User() creates a User object.

  2. first refers to that object.

  3. first.name becomes "Alice".

  4. second = first copies the reference.

  5. Both first and second therefore refer to the same object.

  6. second.name = "Bob" changes that object's name.

  7. first.name therefore also sees "Bob".

  8. number is an independent primitive value and remains 10.

How to read the important code

"Create a User object and make first refer to it."

"Make second refer to the same User object."

This is an important distinction:

first = object reference
second = same object reference

It does not mean Java created a second User.

Beginner trap

A variable containing an object does not contain the entire object itself. It contains a reference to the object.

Key takeaway

Understanding references is essential for understanding Java memory behavior, objects, collections and bugs involving shared state.


Chapter 21 — Garbage Collection & Object Lifetime

Question

Given below is a code snippet that:

  • Creates objects.

  • Changes references.

  • Makes an object unreachable.

  • Demonstrates that System.gc() is only a request.

  • Shows that Java manages object memory automatically.

What should be the output of the following code?

class User {

    // Store a user's name.
    String name;

    User(String name) {
        this.name = name;
    }
}

public class Main {

    public static void main(String[] args) {

        // Create the first object.
        User user = new User("Alice");

        // Make user refer to a different object.
        user = new User("Bob");

        // The original Alice object is now unreachable.
        System.gc();

        // The Bob object is still reachable through user.
        System.out.println(user.name);
    }
}

Answer

Bob

Step-by-step explanation

  1. The first User object contains "Alice".

  2. user refers to that object.

  3. A second object containing "Bob" is created.

  4. user is changed to refer to the Bob object.

  5. Nothing can reach the original Alice object anymore.

  6. It becomes eligible for garbage collection.

  7. System.gc() requests garbage collection, but Java does not guarantee that it happens immediately.

  8. The Bob object is still reachable.

  9. Therefore "Bob" is printed.

How to read the important code

"Create Alice, then make user refer to Bob. Alice is no longer reachable."

Beginner trap

Do not think:

"System.gc() means garbage collection happens now."

It doesn't guarantee immediate collection.

Key takeaway

Garbage collection automatically reclaims memory occupied by objects that are no longer reachable.


Chapter 22 — JVM Performance & Memory Problems

Question

Given below is a code snippet that:

  • Creates a large number of objects.

  • Keeps references to those objects in a collection.

  • Demonstrates why memory can continue being used even when objects are no longer logically needed.

  • Shows an important source of memory pressure.

What should be the output of the following code?

import java.util.*;

public class Main {

    public static void main(String[] args) {

        // This list keeps references to every object we create.
        List<byte[]> data = new ArrayList<>();

        // Create many arrays and keep them in the list.
        for (int i = 0; i < 1000; i++) {

            // Allocate 1 KB of memory.
            byte[] block = new byte[1024];

            // Keep the reference in the list.
            data.add(block);
        }

        // The list still references every allocated array.
        System.out.println(data.size());

        // Remove all references held by the list.
        data.clear();

        // The arrays are now eligible for garbage collection
        // if no other references exist.
        System.out.println(data.size());
    }
}

Answer

1000
0

Step-by-step explanation

  1. data starts as an empty list.

  2. The loop runs 1000 times.

  3. Each iteration creates a new 1 KB byte array.

  4. The array reference is added to data.

  5. The list therefore holds 1000 references.

  6. data.size() returns 1000.

  7. clear() removes those references from the list.

  8. The arrays may now become eligible for garbage collection, assuming nothing else references them.

  9. data.size() is now 0.

How to read the important code

"Create a list that holds references to byte arrays."

"Add one array per iteration."

"Clear the list so it no longer keeps those references."

Beginner trap

Garbage collection only considers whether objects are reachable. If your application accidentally keeps references to objects it no longer needs, garbage collection cannot reclaim those objects.

This is one way applications can experience memory problems.

Key takeaway

Java manages memory automatically, but your program is still responsible for not unnecessarily retaining references.


Phase 3 — What You Should Now Understand

You have now covered the major Modern & Advanced Core Java layer.

The progression was:

Functional Interfaces
        ↓
Lambda Expressions
        ↓
Method References
        ↓
Optional
        ↓
Streams
        ↓
map / filter / flatMap
        ↓
Advanced Stream Operations
        ↓
Collectors
        ↓
Collections + Comparators
        ↓
Files + NIO
        ↓
Buffered I/O
        ↓
Resource Management
        ↓
Date & Time
        ↓
Regular Expressions
        ↓
Annotations
        ↓
Reflection
        ↓
Modules
        ↓
JVM + References
        ↓
Garbage Collection
        ↓
Memory + Performance

The most important mental models from Phase 3

1. Lambda

Think:

"I am passing behavior."

price -> price * 0.10

2. Stream

Think:

"I have data, and I'm building a pipeline to process it."

data.stream()
    .filter(...)
    .map(...)
    .collect(...);

3. map vs flatMap

Think:

map     → one input → one transformed output
flatMap → one input → multiple values → flattened

4. Collection vs Stream

Collection
    ↓
stores data

Stream
    ↓
processes data

5. Optional

Think:

"This value might not exist."


6. Path + Files

Think:

"Path tells me where; Files performs the operation."


7. Reference

Think:

User first ─────┐
                ↓
             [User]
                ↑
User second ────┘

Two variables can refer to the same object.


8. Garbage collection

Think:

Reachable object
       ↓
    stays alive

Unreachable object
       ↓
eligible for GC

But:

Eligible for garbage collection ≠ immediately garbage collected.


One important correction to your mental model

You don't need to memorize every API from this phase.

For professional Java, the high-value concepts to become really comfortable with are:

Lambdas → Functional Interfaces → Streams → Collectors → Collections → Generics → Optional → NIO → Date/Time → Exceptions → References → JVM/GC.

Reflection, modules, annotations and lower-level JVM details become particularly important when you start using frameworks such as Spring, Hibernate and testing libraries.

Phase 4 will build directly on this foundation: Concurrency + Networking + HTTP + JSON + APIs.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.