Generics, Collections, and Core Data Structures

Practice and Assessment

ReadingPreview

আপনি একটি free preview lesson দেখছেন।

Project Overview

এই final project-এ আমরা একটি simplified LiveKlass learning platform model তৈরি করব।

Systemটি support করবে:

  • Unique course codes
  • Registration-order course catalog
  • Course-এর ordered lessons
  • Duplicate lesson prevention
  • Course lookup by code
  • Unique learner enrollments
  • Learner অনুযায়ী enrolled courses
  • Course অনুযায়ী enrollment counting
  • Defensive copying
  • Immutable collection snapshots

এই project-এ Module 4-এর core concepts একসঙ্গে ব্যবহার করা হবে:

Generics
List
Set
Map
ArrayList
HashSet
HashMap
LinkedHashMap
Iteration
Filtering
Counting
Grouping
Collection ownership
Defensive copying

Project Requirements

Course Catalog Rules

  • Course code unique হতে হবে
  • Course registration order preserve করতে হবে
  • Course code দিয়ে direct lookup support করতে হবে
  • Duplicate course registration existing course replace করবে না

Suitable structure:

LinkedHashMap<CourseCode, Course>

Course Rules

  • Course title required
  • Lessons ordered হবে
  • Duplicate lesson ID allowed নয়
  • Maximum 50 lessons
  • Published course-এর lesson structure change করা যাবে না
  • Empty course publish করা যাবে না
  • Internal lesson list direct expose করা যাবে না

Suitable structure:

ArrayList<Lesson>

Enrollment Rules

  • Learner ID positive হতে হবে
  • Same learner একই course-এ দুইবার enroll করতে পারবে না
  • One learner multiple courses-এ enroll করতে পারবে
  • Missing learner-এর জন্য empty set return করতে হবে
  • Internal nested collections expose করা যাবে না

Suitable structure:

Map<Long, Set<CourseCode>>

Project Structure

src/main/java/io/liveklass/
├── Main.java
├── catalog/
│   └── CourseCatalog.java
├── course/
│   ├── Course.java
│   ├── CourseCode.java
│   └── Lesson.java
└── enrollment/
    └── EnrollmentRegistry.java

Part 1: Create CourseCode

Path:

src/main/java/io/liveklass/course/CourseCode.java
package io.liveklass.course;

import java.util.Objects;

public final class CourseCode {

    private final String value;

    public CourseCode(
            String value
    ) {
        if (
                value == null
                || value.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        this.value =
                value.strip()
                        .toUpperCase();
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(
            Object other
    ) {
        if (this == other) {
            return true;
        }

        if (
                !(other
                instanceof CourseCode courseCode)
        ) {
            return false;
        }

        return value.equals(
                courseCode.value
        );
    }

    @Override
    public int hashCode() {
        return Objects.hash(
                value
        );
    }

    @Override
    public String toString() {
        return value;
    }
}

Why CourseCode Is a Value Object

These inputs:

java-oop
JAVA-OOP
 Java-Oop

সব normalize হয়ে:

JAVA-OOP

হয়।

equals() এবং hashCode() implement করা হয়েছে, কারণ CourseCode:

  • Map key হিসেবে ব্যবহৃত হবে
  • Set element হিসেবে ব্যবহৃত হবে
  • Logical value equality require করে

Fields immutable হওয়ায় hash-based collection lookup stable থাকে।


Part 2: Create Lesson

Path:

src/main/java/io/liveklass/course/Lesson.java
package io.liveklass.course;

public final class Lesson {

    private final long id;
    private final String title;

    public Lesson(
            long id,
            String title
    ) {
        if (id <= 0) {
            throw new IllegalArgumentException(
                    "Lesson ID must be positive."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Lesson title is required."
            );
        }

        this.id = id;
        this.title = title.strip();
    }

    public Lesson rename(
            String newTitle
    ) {
        return new Lesson(
                id,
                newTitle
        );
    }

    public long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    @Override
    public String toString() {
        return id
                + ": "
                + title;
    }
}

Why Lesson Is Immutable

Lesson object-এর fields:

private final long id;
private final String title;

Direct mutation নেই।

Rename করলে new object তৈরি হয়:

lesson.rename(
        "New Title"
);

এতে course-এর collection ownership stronger হয়।

Caller কোনো retrieved Lesson object direct mutate করতে পারে না।


Part 3: Create Course

Path:

src/main/java/io/liveklass/course/Course.java
package io.liveklass.course;

import java.util.ArrayList;
import java.util.List;

public final class Course {

    private static final int MAX_LESSON_COUNT =
            50;

    private final CourseCode code;
    private final String title;
    private final List<Lesson> lessons;

    private boolean published;

    public Course(
            CourseCode code,
            String title
    ) {
        if (code == null) {
            throw new IllegalArgumentException(
                    "Course code is required."
            );
        }

        if (
                title == null
                || title.isBlank()
        ) {
            throw new IllegalArgumentException(
                    "Course title is required."
            );
        }

        this.code = code;
        this.title = title.strip();

        this.lessons =
                new ArrayList<>();

        this.published = false;
    }

    public boolean addLesson(
            Lesson lesson
    ) {
        if (published) {
            return false;
        }

        if (lesson == null) {
            return false;
        }

        if (
                lessons.size()
                >= MAX_LESSON_COUNT
        ) {
            return false;
        }

        if (
                containsLessonId(
                        lesson.getId()
                )
        ) {
            return false;
        }

        lessons.add(
                lesson
        );

        return true;
    }

    public boolean addLessonAt(
            int index,
            Lesson lesson
    ) {
        if (published) {
            return false;
        }

        if (lesson == null) {
            return false;
        }

        if (
                index < 0
                || index > lessons.size()
        ) {
            return false;
        }

        if (
                lessons.size()
                >= MAX_LESSON_COUNT
        ) {
            return false;
        }

        if (
                containsLessonId(
                        lesson.getId()
                )
        ) {
            return false;
        }

        lessons.add(
                index,
                lesson
        );

        return true;
    }

    public boolean removeLesson(
            long lessonId
    ) {
        if (published) {
            return false;
        }

        int index =
                findLessonIndex(
                        lessonId
                );

        if (index < 0) {
            return false;
        }

        lessons.remove(
                index
        );

        return true;
    }

    public boolean renameLesson(
            long lessonId,
            String newTitle
    ) {
        if (published) {
            return false;
        }

        if (
                newTitle == null
                || newTitle.isBlank()
        ) {
            return false;
        }

        int index =
                findLessonIndex(
                        lessonId
                );

        if (index < 0) {
            return false;
        }

        Lesson lesson =
                lessons.get(
                        index
                );

        lessons.set(
                index,
                lesson.rename(
                        newTitle
                )
        );

        return true;
    }

    public boolean moveLesson(
            long lessonId,
            int targetIndex
    ) {
        if (published) {
            return false;
        }

        if (
                targetIndex < 0
                || targetIndex >= lessons.size()
        ) {
            return false;
        }

        int currentIndex =
                findLessonIndex(
                        lessonId
                );

        if (
                currentIndex < 0
                || currentIndex == targetIndex
        ) {
            return false;
        }

        Lesson lesson =
                lessons.remove(
                        currentIndex
                );

        lessons.add(
                targetIndex,
                lesson
        );

        return true;
    }

    public Lesson findLessonById(
            long lessonId
    ) {
        int index =
                findLessonIndex(
                        lessonId
                );

        if (index < 0) {
            return null;
        }

        return lessons.get(
                index
        );
    }

    public boolean publish() {
        if (published) {
            return false;
        }

        if (lessons.isEmpty()) {
            return false;
        }

        published = true;

        return true;
    }

    public List<Lesson> getLessons() {
        return List.copyOf(
                lessons
        );
    }

    public CourseCode getCode() {
        return code;
    }

    public String getTitle() {
        return title;
    }

    public int getLessonCount() {
        return lessons.size();
    }

    public boolean isPublished() {
        return published;
    }

    private boolean containsLessonId(
            long lessonId
    ) {
        return findLessonIndex(
                lessonId
        ) >= 0;
    }

    private int findLessonIndex(
            long lessonId
    ) {
        for (
                int index = 0;
                index < lessons.size();
                index++
        ) {
            Lesson lesson =
                    lessons.get(
                            index
                    );

            if (
                    lesson.getId()
                    == lessonId
            ) {
                return index;
            }
        }

        return -1;
    }

    @Override
    public String toString() {
        return code
                + " — "
                + title;
    }
}

Course Design Review

Why Use ArrayList?

Lessons:

  • Ordered
  • Reorderable
  • Index-based display useful
  • Usually small collection

So:

ArrayList<Lesson>

is appropriate।


Why Not Use Set<Lesson>?

Duplicate lesson ID forbidden হলেও lesson order equally important।

Set only uniqueness solve করত।

Course requires:

Order + unique ID

তাই List এবং explicit duplicate validation ব্যবহার করা হয়েছে।


Why Return List.copyOf()?

Weak:

return lessons;

Callerকে internal mutable list দিত।

Caller করতে পারত:

course.getLessons()
        .clear();

List.copyOf() structural mutation prevent করে।


Part 4: Create CourseCatalog

Path:

src/main/java/io/liveklass/catalog/CourseCatalog.java
package io.liveklass.catalog;

import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public final class CourseCatalog {

    private final Map<CourseCode, Course> coursesByCode;

    public CourseCatalog() {
        this.coursesByCode =
                new LinkedHashMap<>();
    }

    public boolean register(
            Course course
    ) {
        if (course == null) {
            return false;
        }

        Course existing =
                coursesByCode.putIfAbsent(
                        course.getCode(),
                        course
                );

        return existing == null;
    }

    public Course findByCode(
            CourseCode code
    ) {
        if (code == null) {
            return null;
        }

        return coursesByCode.get(
                code
        );
    }

    public boolean contains(
            CourseCode code
    ) {
        if (code == null) {
            return false;
        }

        return coursesByCode.containsKey(
                code
        );
    }

    public boolean remove(
            CourseCode code
    ) {
        if (code == null) {
            return false;
        }

        return coursesByCode.remove(
                code
        ) != null;
    }

    public List<Course> getCourses() {
        return List.copyOf(
                coursesByCode.values()
        );
    }

    public int getCourseCount() {
        return coursesByCode.size();
    }

    public void printCourses() {
        int position =
                1;

        for (
                Map.Entry<CourseCode, Course> entry
                : coursesByCode.entrySet()
        ) {
            System.out.println(
                    position
                    + ". "
                    + entry.getKey()
                    + " — "
                    + entry.getValue()
                            .getTitle()
            );

            position++;
        }
    }
}

Catalog Design Review

Why Use LinkedHashMap?

Requirements:

Unique course code
Direct code lookup
Registration order

LinkedHashMap তিনটি requirement এক structure-এ solve করে।


Why Use putIfAbsent()?

Weak:

coursesByCode.put(
        course.getCode(),
        course
);

Duplicate course code existing course replace করত।

putIfAbsent() existing registration preserve করে।


Why Return a List<Course>?

Catalog-এর public read operation registration-order sequence communicate করে।

getCourses()

callerকে internal map mutation expose করে না।


Part 5: Create EnrollmentRegistry

Path:

src/main/java/io/liveklass/enrollment/EnrollmentRegistry.java
package io.liveklass.enrollment;

import io.liveklass.course.CourseCode;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public final class EnrollmentRegistry {

    private final Map<Long, Set<CourseCode>>
            courseCodesByLearner;

    public EnrollmentRegistry() {
        this.courseCodesByLearner =
                new HashMap<>();
    }

    public boolean enroll(
            long learnerId,
            CourseCode courseCode
    ) {
        if (learnerId <= 0) {
            return false;
        }

        if (courseCode == null) {
            return false;
        }

        Set<CourseCode> courseCodes =
                courseCodesByLearner.get(
                        learnerId
                );

        if (courseCodes == null) {
            courseCodes =
                    new HashSet<>();

            courseCodesByLearner.put(
                    learnerId,
                    courseCodes
            );
        }

        return courseCodes.add(
                courseCode
        );
    }

    public boolean isEnrolled(
            long learnerId,
            CourseCode courseCode
    ) {
        if (
                learnerId <= 0
                || courseCode == null
        ) {
            return false;
        }

        Set<CourseCode> courseCodes =
                courseCodesByLearner.get(
                        learnerId
                );

        return courseCodes != null
                && courseCodes.contains(
                        courseCode
                );
    }

    public boolean cancel(
            long learnerId,
            CourseCode courseCode
    ) {
        if (
                learnerId <= 0
                || courseCode == null
        ) {
            return false;
        }

        Set<CourseCode> courseCodes =
                courseCodesByLearner.get(
                        learnerId
                );

        if (courseCodes == null) {
            return false;
        }

        boolean removed =
                courseCodes.remove(
                        courseCode
                );

        if (courseCodes.isEmpty()) {
            courseCodesByLearner.remove(
                    learnerId
            );
        }

        return removed;
    }

    public Set<CourseCode> findCourseCodes(
            long learnerId
    ) {
        Set<CourseCode> courseCodes =
                courseCodesByLearner.get(
                        learnerId
                );

        if (courseCodes == null) {
            return Set.of();
        }

        return Set.copyOf(
                courseCodes
        );
    }

    public Map<CourseCode, Integer>
    countEnrollmentsByCourse() {
        Map<CourseCode, Integer> counts =
                new HashMap<>();

        for (
                Set<CourseCode> courseCodes
                : courseCodesByLearner.values()
        ) {
            for (
                    CourseCode courseCode
                    : courseCodes
            ) {
                int currentCount =
                        counts.getOrDefault(
                                courseCode,
                                0
                        );

                counts.put(
                        courseCode,
                        currentCount + 1
                );
            }
        }

        return Map.copyOf(
                counts
        );
    }

    public Map<Long, Set<CourseCode>> getSnapshot() {
        Map<Long, Set<CourseCode>> copy =
                new HashMap<>();

        for (
                Map.Entry<Long, Set<CourseCode>> entry
                : courseCodesByLearner.entrySet()
        ) {
            copy.put(
                    entry.getKey(),
                    Set.copyOf(
                            entry.getValue()
                    )
            );
        }

        return Map.copyOf(
                copy
        );
    }

    public int getLearnerCount() {
        return courseCodesByLearner.size();
    }
}

Enrollment Design Review

Why Use Map<Long, Set<CourseCode>>?

Relationship:

Learner ID → Unique enrolled course codes

Map provides learner lookup।

Set prevents duplicate enrollment।


Why Remove Empty Learner Entries?

After final cancellation:

courseCodes.isEmpty()

Current model means:

No enrollments → No learner entry

This keeps map state compact এবং clear।


Why Copy Both Map and Inner Sets?

This is not enough:

return Map.copyOf(
        courseCodesByLearner
);

Outer map immutable হলেও inner sets mutable থাকতে পারত।

Therefore:

Set.copyOf(...)

প্রতিটি inner set-এর জন্যও করা হয়েছে।


Part 6: Build the Application

Path:

src/main/java/io/liveklass/Main.java
package io.liveklass;

import io.liveklass.catalog.CourseCatalog;
import io.liveklass.course.Course;
import io.liveklass.course.CourseCode;
import io.liveklass.course.Lesson;
import io.liveklass.enrollment.EnrollmentRegistry;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Main {

    public static void main(
            String[] args
    ) {
        Course javaCourse =
                createJavaCourse();

        Course backendCourse =
                createBackendCourse();

        CourseCatalog catalog =
                new CourseCatalog();

        boolean javaRegistered =
                catalog.register(
                        javaCourse
                );

        boolean backendRegistered =
                catalog.register(
                        backendCourse
                );

        boolean duplicateRegistered =
                catalog.register(
                        new Course(
                                new CourseCode(
                                        "java-oop"
                                ),
                                "Duplicate Java Course"
                        )
                );

        System.out.println(
                "Java registered: "
                + javaRegistered
        );

        System.out.println(
                "Backend registered: "
                + backendRegistered
        );

        System.out.println(
                "Duplicate registered: "
                + duplicateRegistered
        );

        System.out.println();
        System.out.println(
                "Course catalog:"
        );

        catalog.printCourses();

        Course found =
                catalog.findByCode(
                        new CourseCode(
                                "JAVA-OOP"
                        )
                );

        System.out.println();
        System.out.println(
                "Found course: "
                + found
        );

        demonstrateEnrollments(
                javaCourse,
                backendCourse
        );
    }

    private static Course createJavaCourse() {
        Course course =
                new Course(
                        new CourseCode(
                                "JAVA-OOP"
                        ),
                        "Java and OOP Foundation"
                );

        course.addLesson(
                new Lesson(
                        101L,
                        "Introduction to Generics"
                )
        );

        course.addLesson(
                new Lesson(
                        102L,
                        "Working with List"
                )
        );

        course.addLesson(
                new Lesson(
                        103L,
                        "Working with Set"
                )
        );

        course.addLesson(
                new Lesson(
                        104L,
                        "Working with Map"
                )
        );

        course.moveLesson(
                104L,
                2
        );

        course.publish();

        return course;
    }

    private static Course createBackendCourse() {
        Course course =
                new Course(
                        new CourseCode(
                                "BACKEND"
                        ),
                        "Backend Development"
                );

        course.addLesson(
                new Lesson(
                        201L,
                        "HTTP Fundamentals"
                )
        );

        course.addLesson(
                new Lesson(
                        202L,
                        "REST API Design"
                )
        );

        course.publish();

        return course;
    }

    private static void demonstrateEnrollments(
            Course javaCourse,
            Course backendCourse
    ) {
        EnrollmentRegistry registry =
                new EnrollmentRegistry();

        boolean firstEnrollment =
                registry.enroll(
                        1001L,
                        javaCourse.getCode()
                );

        boolean duplicateEnrollment =
                registry.enroll(
                        1001L,
                        javaCourse.getCode()
                );

        registry.enroll(
                1001L,
                backendCourse.getCode()
        );

        registry.enroll(
                1002L,
                javaCourse.getCode()
        );

        System.out.println();
        System.out.println(
                "First enrollment: "
                + firstEnrollment
        );

        System.out.println(
                "Duplicate enrollment: "
                + duplicateEnrollment
        );

        Set<CourseCode> learnerCourses =
                registry.findCourseCodes(
                        1001L
                );

        System.out.println(
                "Learner 1001 courses: "
                + learnerCourses
        );

        Map<CourseCode, Integer> counts =
                registry
                        .countEnrollmentsByCourse();

        System.out.println(
                "Enrollment counts: "
                + counts
        );

        boolean cancelled =
                registry.cancel(
                        1001L,
                        backendCourse.getCode()
                );

        System.out.println(
                "Backend enrollment cancelled: "
                + cancelled
        );

        System.out.println(
                "Learner 1001 courses after cancellation: "
                + registry.findCourseCodes(
                        1001L
                )
        );

        printJavaLessons(
                javaCourse
        );
    }

    private static void printJavaLessons(
            Course javaCourse
    ) {
        List<Lesson> lessons =
                javaCourse.getLessons();

        System.out.println();
        System.out.println(
                "Java course lessons:"
        );

        for (
                int index = 0;
                index < lessons.size();
                index++
        ) {
            System.out.println(
                    (index + 1)
                    + ". "
                    + lessons.get(
                            index
                    ).getTitle()
            );
        }
    }
}

Expected Output

HashMap এবং HashSet-based values-এর printed order vary করতে পারে।

Java registered: true
Backend registered: true
Duplicate registered: false

Course catalog:
1. JAVA-OOP — Java and OOP Foundation
2. BACKEND — Backend Development

Found course: JAVA-OOP — Java and OOP Foundation

First enrollment: true
Duplicate enrollment: false
Learner 1001 courses: [JAVA-OOP, BACKEND]
Enrollment counts: {JAVA-OOP=2, BACKEND=1}
Backend enrollment cancelled: true
Learner 1001 courses after cancellation: [JAVA-OOP]

Java course lessons:
1. Introduction to Generics
2. Working with List
3. Working with Map
4. Working with Set

Compile and Run

Project root থেকে:

javac -d out \
    $(find src/main/java -name "*.java")

Run:

java -cp out io.liveklass.Main

Integrated Collection Review

Where Is List Used?

List<Lesson>

Reason:

Lesson order matters
Reordering supported
Index-based display useful

Where Is Set Used?

Set<CourseCode>

Reason:

Same learner cannot enroll in the same course twice

Where Is Map Used?

Catalog:

Map<CourseCode, Course>

Reason:

Course code দিয়ে direct lookup

Enrollment registry:

Map<Long, Set<CourseCode>>

Reason:

Learner ID দিয়ে enrolled course set lookup

Where Is LinkedHashMap Used?

CourseCatalog

কারণ registration order preserve করতে হবে।


Where Is HashMap Used?

Enrollment registry-তে learner order meaningful নয়।

HashMap<Long, Set<CourseCode>>

appropriate।


Where Is Defensive Copying Used?

List.copyOf(...)
Set.copyOf(...)
Map.copyOf(...)

These prevent callers from changing internal collection structures।


Part 7: Required Extension Tasks

Extension 1: Add Course Removal Protection

Catalog থেকে published course remove করা যাবে না।

Modify:

boolean remove(
        CourseCode code
)

Extension 2: Add Lesson Batch Insertion

Create:

boolean addLessons(
        List<Lesson> lessons
)

Rules:

  • Validate all lessons first
  • No null
  • No duplicate IDs
  • Maximum 50 lessons
  • No partial addition
  • Published course reject করবে

Extension 3: Find Most Popular Course

Add:

CourseCode findMostPopularCourse()

to EnrollmentRegistry

Use:

countEnrollmentsByCourse()

Handle empty registry।


Extension 4: Find Learners by Course

Add:

Set<Long> findLearnerIds(
        CourseCode courseCode
)

Return immutable set।


Extension 5: Preserve Enrollment Order

Current learner course set:

HashSet<CourseCode>

Change it so enrollment order preserve হয়।

Choose an appropriate implementation।


Extension 6: Group Courses by Lesson Count

Create:

Map<Integer, List<Course>>

Example:

2 lessons → Backend Development
4 lessons → Java and OOP Foundation

Protect outer map and inner lists।


Part 8: Collection Choice Assessment

Choose the best collection type and implementation।

Question 1

Ordered course modules that can be manually reordered।

Question 2

Unique completed lesson IDs where order is irrelevant।

Question 3

Course code to course lookup preserving registration order।

Question 4

Unique tags preserving instructor-selected order।

Question 5

Country code to country sorted by code।

Question 6

Learner activity history where repeated actions are meaningful।


Collection Choice Answers

Answer 1

ArrayList<Module>

Order and manual reordering matter।

Answer 2

HashSet<Long>

Unique membership matters; order does not।

Answer 3

LinkedHashMap<CourseCode, Course>

Unique lookup key and insertion order both matter।

Answer 4

LinkedHashSet<String>

Uniqueness and insertion order matter।

Answer 5

TreeMap<String, Country>

Key lookup and sorted key order matter।

Answer 6

ArrayList<Activity>

Sequence and repeated events matter।


Part 9: Predict the Result

Question 1

Map<String, Integer> values =
        new LinkedHashMap<>();

values.put(
        "A",
        1
);

values.put(
        "B",
        2
);

values.put(
        "A",
        3
);

System.out.println(
        values.size()
);

System.out.println(
        values
);

Answer

1? 

Incorrect.

Actual size:

2

Result:

{A=3, B=2}

Existing key replacement does not create another key।

Its original insertion position remains unchanged।


Question 2

Set<String> values =
        new HashSet<>();

System.out.println(
        values.add(
                "JAVA"
        )
);

System.out.println(
        values.add(
                "JAVA"
        )
);

Answer

true
false

Question 3

List<String> values =
        List.of(
                "A",
                "B"
        );

values.add(
        "C"
);

Answer

Runtime-এ:

UnsupportedOperationException

Question 4

CourseCode first =
        new CourseCode(
                "java"
        );

CourseCode second =
        new CourseCode(
                "JAVA"
        );

System.out.println(
        first.equals(
                second
        )
);

Answer

true

Both normalize to the same value।


Question 5

Set<CourseCode> courses =
        registry.findCourseCodes(
                9999L
        );

System.out.println(
        courses.isEmpty()
);

Answer

true

Missing learner returns an empty set।


Part 10: Find the Design Problem

Problem 1

public List<Lesson> getLessons() {
    return lessons;
}

Issue

Internal mutable list leaks।

Caller domain rules bypass করতে পারে।

Better

return List.copyOf(
        lessons
);

Problem 2

List<Course> courses =
        new ArrayList<>();

Every request searches by unique course code।

Issue

Repeated linear search।

Better

Map<CourseCode, Course>

Problem 3

Set<Lesson> lessons =
        new HashSet<>();

Lessons must remain in curriculum order।

Issue

HashSet order guarantee করে না।

Better

List<Lesson>

with duplicate ID validation।


Problem 4

Map<Long, List<CourseCode>>

Same learner-course enrollment must be unique।

Issue

List duplicates allow করে।

Better

Map<Long, Set<CourseCode>>

Problem 5

Map<MutableCourseCode, Course>

MutableCourseCode.value changes after insertion।

Issue

Hash lookup may fail।

Better

Use immutable key।


Part 11: Concept Assessment

Determine True or False.

Question 1

List preserves sequence.

Question 2

Set provides index-based access.

Question 3

A Map allows multiple equal keys.

Question 4

Different map keys may point to the same value.

Question 5

HashMap guarantees insertion order.

Question 6

LinkedHashMap preserves insertion order.

Question 7

List.copyOf() deep-copies every element.

Question 8

Set.add() can indicate whether a duplicate was rejected.

Question 9

Equal hash-based keys must have equal hash codes.

Question 10

An immutable outer map automatically makes inner sets immutable.

Question 11

A list can still be correct when duplicate IDs are manually rejected.

Question 12

Object-level validation replaces database uniqueness constraints.


Concept Assessment Answers

1. True
2. False
3. False
4. True
5. False
6. True
7. False
8. True
9. True
10. False
11. True
12. False

Part 12: Final Independent Challenge

Build a simplified learning platform without copying the reference implementation।

Required classes:

CourseCode
Lesson
Course
CourseCatalog
EnrollmentRegistry

Required behavior:

  • Course code normalized and value-based
  • Course lessons ordered
  • Duplicate lesson IDs rejected
  • Course code lookup supported
  • Registration order preserved
  • Duplicate enrollment rejected
  • Learner courses returned as immutable set
  • Enrollment count by course calculated
  • Internal mutable collections not exposed
  • Empty results returned instead of null collections

Evaluation Rubric

Score each area:

0 = Missing or incorrect
1 = Partially correct
2 = Correct and intentional
AreaScore
Generic types used safely/2
Appropriate List usage/2
Appropriate Set usage/2
Appropriate Map usage/2
Ordering requirement preserved/2
Duplicate course prevention/2
Duplicate lesson prevention/2
Duplicate enrollment prevention/2
Custom key equality correct/2
Hash key immutable/2
Defensive copying used/2
Nested collections protected/2
Empty collections returned/2
Search and iteration readable/2
Domain methods control mutation/2

Maximum:

30

Interpretation:

26–30 → Strong understanding
21–25 → Good foundation
15–20 → Review collection design
Below 15 → Rebuild the project

Module Completion Checklist

Before completing Module 4, verify that you can:

  • Explain generic type safety
  • Distinguish type parameter and type argument
  • Use wrapper types with generics
  • Explain generic invariance
  • Create and modify an ArrayList
  • Use index safely
  • Distinguish mutable and immutable lists
  • Use HashSet
  • Explain equals() and hashCode()
  • Avoid mutable hash keys
  • Use HashMap
  • Use LinkedHashMap
  • Use putIfAbsent()
  • Iterate keys, values, and entries
  • Search, filter, transform, and aggregate
  • Count values using a map
  • Group values into nested collections
  • Remove safely during iteration
  • Defensively copy constructor inputs
  • Return immutable snapshots
  • Protect nested collections
  • Choose collections based on order, uniqueness, and lookup

Module Summary

এই module-এ আমরা শিখেছি:

  • Generics compile-time type safety provide করে
  • Raw types type information হারায়
  • Generic classes এবং methods reusable type relationships model করে
  • Primitive types-এর জন্য wrapper classes প্রয়োজন
  • Generic types invariant
  • List ordered sequence model করে
  • ArrayList common mutable list implementation
  • List index 0 থেকে শুরু হয়
  • List.of() immutable list তৈরি করে
  • List.copyOf() immutable structural snapshot তৈরি করে
  • Set unique membership model করে
  • HashSet order guarantee করে না
  • LinkedHashSet insertion order preserve করে
  • TreeSet sorted unique values রাখে
  • Hash-based structures equals() এবং hashCode()-এর ওপর depend করে
  • Mutable equality state dangerous
  • Map key-value lookup model করে
  • HashMap common key lookup implementation
  • LinkedHashMap insertion order preserve করে
  • TreeMap keys sorted রাখে
  • put() existing key replace করে
  • putIfAbsent() duplicate replacement prevent করে
  • getOrDefault() counting simplify করে
  • Enhanced for, index loop, এবং Iterator different needs serve করে
  • Iteration-এর সময় direct structural removal unsafe হতে পারে
  • Filtering selected values তৈরি করে
  • Transformation valuesকে new representation-এ convert করে
  • Aggregation total, count, average, minimum, এবং maximum তৈরি করে
  • Maps frequency counting এবং grouping-এর জন্য useful
  • Nested collection structures carefully protect করতে হয়
  • Classes internal collections own এবং control করতে পারে
  • Defensive copying aliasing prevent করে
  • Immutable snapshots mutable state leakage prevent করে
  • Collection choice order, uniqueness, lookup, and scale থেকে আসা উচিত
  • Domain clarity premature optimization-এর চেয়ে বেশি important
  • List, Set, এবং Map competing নয়; different relationships model করে

Module Complete

আপনি এখন Java collections শুধু syntax হিসেবে নয়, domain modeling tool হিসেবে ব্যবহার করতে প্রস্তুত।

Collection নির্বাচন করার আগে তিনটি প্রশ্ন করুন:

Does order matter?
Are duplicates meaningful?
Is lookup by a key the main operation?

এই প্রশ্নগুলোর উত্তর সাধারণত আপনাকে List, Set, এবং Map-এর মধ্যে সঠিক starting choice-এর দিকে নিয়ে যাবে।