Algorithms and Problem Solving with Java

Java's Built-In Sorting and Searching APIs

ReadingPreview

You are viewing a free preview lesson.

Lesson Overview

আগের কয়েকটি lesson-এ আমরা manually implement করেছি:

Linear Search
Binary Search
Bubble Sort
Selection Sort
Insertion Sort
Merge Sort
Quick Sort

এই implementationগুলো algorithm বুঝতে সাহায্য করেছে।

কিন্তু normal production Java code-এ আমরা সাধারণত নিজের sorting algorithm লিখি না।

Instead, Java standard library ব্যবহার করি।

Examples:

Arrays.sort(...)
Arrays.binarySearch(...)
List.sort(...)
Collections.sort(...)
Collections.binarySearch(...)

Object sorting-এর জন্য আমরা ব্যবহার করি:

Comparable<T>
Comparator<T>

এই lesson-এ আমরা শিখব:

  • Arrays.sort()
  • Sorting primitive arrays
  • Sorting object arrays
  • List.sort()
  • Collections.sort()
  • Natural ordering
  • Comparable<T>
  • Comparator<T>
  • Comparator.comparing(...)
  • Primitive comparator helpers
  • Reversed ordering
  • Multiple-field sorting
  • thenComparing(...)
  • Arrays.binarySearch()
  • Collections.binarySearch()
  • Searching with custom comparators
  • Why sorting and searching must use the same ordering rule
  • When manual algorithms should be replaced with standard-library APIs

Why Use Standard Library Sorting?

Suppose we need to sort:

int[] scores = {
        90,
        70,
        85,
        60
};

We could write:

bubbleSort(
        scores
);

But ordinary application code should normally use:

Arrays.sort(
        scores
);

Why?

Because standard-library code is:

Well tested
Optimized
Familiar to Java developers
Maintained as part of the platform
Less error-prone than custom implementations

The purpose of manually learning sorting algorithms was:

Understand the mechanics.

The purpose of standard APIs is:

Solve normal application problems reliably.

Arrays.sort()

For arrays, Java provides:

java.util.Arrays

Import:

import java.util.Arrays;

Example:

int[] numbers = {
        40,
        10,
        30,
        20
};

Arrays.sort(
        numbers
);

After sorting:

[10, 20, 30, 40]

Arrays.sort() Mutates the Array

This is important।

Arrays.sort(
        numbers
);

sorts:

the original array

It does not create a new sorted copy।


Preserve the Original Array

If original order matters:

int[] sorted =
        Arrays.copyOf(
                numbers,
                numbers.length
        );

Arrays.sort(
        sorted
);

Now:

numbers

remains unchanged।

And:

sorted

contains the sorted version।


Sorting Primitive Arrays

Arrays.sort() supports primitive arrays such as:

int[]
long[]
double[]
char[]
byte[]
short[]
float[]

Example:

long[] prices = {
        5000,
        1500,
        3000
};

Arrays.sort(
        prices
);

Result:

[1500, 3000, 5000]

Sorting String[]

Strings have a natural ordering।

Example:

String[] names = {
        "Sumu",
        "Sakib",
        "Nur",
        "Subu"
};

Arrays.sort(
        names
);

Result follows String natural ordering।


Natural Ordering

Some Java types already know how to compare themselves naturally।

Examples:

Integer
Long
Double
String
LocalDate

Conceptually:

Integer
→ numeric order

String
→ lexicographical order

LocalDate
→ chronological order

For custom classes, natural ordering can be defined through:

Comparable<T>

List.sort()

Suppose:

List<Integer> scores =
        new ArrayList<>(
                List.of(
                        90,
                        60,
                        80,
                        70
                )
        );

Sort:

scores.sort(
        null
);

Passing:

null

means:

Use natural ordering.

Result:

[60, 70, 80, 90]

Clearer Natural Ordering

Instead of:

scores.sort(
        null
);

you can also write:

scores.sort(
        Comparator.naturalOrder()
);

This can be more explicit।

Required import:

import java.util.Comparator;

Collections.sort()

Older/common API:

Collections.sort(
        scores
);

Import:

import java.util.Collections;

This also sorts a mutable list using natural ordering।


List.sort() vs Collections.sort()

Both can sort mutable lists।

Modern application code often prefers:

list.sort(
        comparator
);

because sorting is expressed directly on the list।

Example:

scores.sort(
        Comparator.naturalOrder()
);

But:

Collections.sort(
        scores
);

is still valid and commonly encountered।


Mutable List Requirement

This fails conceptually:

List<Integer> scores =
        List.of(
                30,
                10,
                20
        );

scores.sort(
        Comparator.naturalOrder()
);

Why?

Because:

List.of(...)

returns an unmodifiable list।

Sorting requires reordering elements।

Use:

List<Integer> scores =
        new ArrayList<>(
                List.of(
                        30,
                        10,
                        20
                )
        );

then:

scores.sort(
        Comparator.naturalOrder()
);

Comparable<T>

Suppose we define:

record Course(
        String code,
        String title,
        long priceInPaisa
) {
}

What is the "natural" order of Course?

Could be:

code
title
price

There is no universal answer।

If the domain has one clear default ordering, we may implement:

Comparable<Course>

Example with Comparable

record Course(
        String code,
        String title,
        long priceInPaisa
) implements Comparable<Course> {

    @Override
    public int compareTo(
            Course other
    ) {
        return code.compareTo(
                other.code
        );
    }
}

Now Course's natural order is:

course code

Using the Natural Order

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

courses.add(
        new Course(
                "SYSTEM-DESIGN",
                "System Design",
                800_000
        )
);

courses.add(
        new Course(
                "JAVA",
                "Java Foundation",
                300_000
        )
);

courses.add(
        new Course(
                "BACKEND",
                "Backend Development",
                500_000
        )
);

courses.sort(
        Comparator.naturalOrder()
);

Ordering:

BACKEND
JAVA
SYSTEM-DESIGN

compareTo() Contract Intuition

A comparison method returns an integer।

Conceptually:

negative
→ this comes before other

zero
→ equal in ordering

positive
→ this comes after other

Example:

code.compareTo(
        other.code
);

Do Not Return Arbitrary Boolean Logic

Incorrect:

return priceInPaisa
        > other.priceInPaisa
        ? 1
        : -1;

This fails when prices are equal because it never returns:

0

Better:

return Long.compare(
        priceInPaisa,
        other.priceInPaisa
);

Avoid Subtraction Comparators

Bad:

return first - second;

Why?

Because subtraction can overflow for large integer values।

Prefer:

Integer.compare(
        first,
        second
);

For long:

Long.compare(
        first,
        second
);

Should Every Domain Class Implement Comparable?

No।

Only define a natural order when there is a meaningful, stable default।

Suppose Course can commonly be sorted by:

title
price
code
lesson count
publication date

If none is clearly the natural identity of ordering, forcing one through Comparable may be misleading।

In such cases, prefer:

Comparator<Course>

Comparator<T>

Comparator<T> defines ordering externally।

Example:

Comparator<Course> byPrice =
        Comparator.comparingLong(
                Course::priceInPaisa
        );

Then:

courses.sort(
        byPrice
);

Why Comparator Is Powerful

It lets the same type have multiple sort orders।

Example:

By code
By title
By price
By price descending
By title then code

without modifying the class each time।


Sort by Title

Comparator<Course> byTitle =
        Comparator.comparing(
                Course::title
        );

Then:

courses.sort(
        byTitle
);

Method Reference

This:

Course::title

means conceptually:

Given a Course,
extract its title.

Equivalent lambda:

course -> course.title()

We'll study method references more deeply in Modern Java।


Comparator.comparing()

For object-valued properties:

Comparator.comparing(
        Course::title
);

This works because String has natural ordering।


Primitive Comparator Helpers

For primitive-like properties, prefer specialized helpers:

Comparator.comparingInt(...)
Comparator.comparingLong(...)
Comparator.comparingDouble(...)

Example:

Comparator<Course> byPrice =
        Comparator.comparingLong(
                Course::priceInPaisa
        );

This avoids unnecessary boxing in the comparison path।


Sorting by Price

Example:

List<Course> courses =
        new ArrayList<>(
                List.of(
                        new Course(
                                "JAVA",
                                "Java Foundation",
                                300_000
                        ),
                        new Course(
                                "BACKEND",
                                "Backend Development",
                                500_000
                        ),
                        new Course(
                                "ALGORITHMS",
                                "Algorithms",
                                400_000
                        )
                )
        );

courses.sort(
        Comparator.comparingLong(
                Course::priceInPaisa
        )
);

Result by price:

JAVA       300000
ALGORITHMS 400000
BACKEND    500000

Descending Order

Use:

reversed()

Example:

Comparator<Course> byPriceDescending =
        Comparator.comparingLong(
                Course::priceInPaisa
        ).reversed();

Then:

courses.sort(
        byPriceDescending
);

Result:

BACKEND
ALGORITHMS
JAVA

Natural Reverse Order

For naturally ordered values:

numbers.sort(
        Comparator.reverseOrder()
);

Example:

List<Integer> numbers =
        new ArrayList<>(
                List.of(
                        10,
                        30,
                        20
                )
        );

numbers.sort(
        Comparator.reverseOrder()
);

Result:

[30, 20, 10]

Sorting by Multiple Fields

Suppose:

record Learner(
        String name,
        int score
) {
}

Data:

Sakib 90
Subu  80
Sumu  90
Nur   80

We want:

score ascending
then name ascending

Use:

Comparator<Learner> comparator =
        Comparator.comparingInt(
                Learner::score
        ).thenComparing(
                Learner::name
        );

thenComparing()

The first comparator decides primary ordering।

If two values compare equally, the next comparator is used।

Example:

score first
name second

This is common in production systems।


Example

learners.sort(
        Comparator.comparingInt(
                Learner::score
        ).thenComparing(
                Learner::name
        )
);

Possible result:

Nur   80
Subu  80
Sakib 90
Sumu  90

Descending Primary, Ascending Secondary

Suppose we want:

score descending
name ascending

Use:

Comparator<Learner> byScoreDescendingThenName =
        Comparator.comparingInt(
                Learner::score
        )
                .reversed()
                .thenComparing(
                        Learner::name
                );

Be Careful with reversed()

This:

Comparator.comparingInt(
        Learner::score
)
        .thenComparing(
                Learner::name
        )
        .reversed();

reverses the entire composed comparator।

That means both:

score
and
name

ordering are reversed।

If only the primary field should be descending, reverse it before chaining:

Comparator.comparingInt(
        Learner::score
)
        .reversed()
        .thenComparing(
                Learner::name
        );

Comparator as a Named Rule

For reusable domain sorting:

static final Comparator<Course> BY_PRICE =
        Comparator.comparingLong(
                Course::priceInPaisa
        );

Then:

courses.sort(
        BY_PRICE
);

This can improve readability when the ordering has business meaning।


Comparator Naming Matters

Compare:

Comparator<Course> comparator

with:

Comparator<Course> byPriceThenCode

The second communicates intent much better।


Sorting Arrays of Objects

Arrays.sort() also supports object arrays।

Example:

Course[] courses = {
        new Course(
                "JAVA",
                "Java Foundation",
                300_000
        ),
        new Course(
                "BACKEND",
                "Backend Development",
                500_000
        )
};

Sort by comparator:

Arrays.sort(
        courses,
        Comparator.comparingLong(
                Course::priceInPaisa
        )
);

Primitive Arrays Cannot Use Object Comparator Directly

For:

int[]

you use the primitive Arrays.sort() overload।

You cannot pass:

Comparator<Integer>

directly to an int[] sort।

If custom ordering such as descending is needed, one option is to use:

Integer[]

but that introduces object boxing overhead।

Often, for primitive arrays, sort ascending and traverse backwards if all you need is descending processing।


Example: Descending Primitive Traversal

int[] numbers = {
        40,
        10,
        30,
        20
};

Arrays.sort(
        numbers
);

for (
        int i = numbers.length - 1;
        i >= 0;
        i--
) {
    System.out.println(
            numbers[i]
    );
}

Output:

40
30
20
10

No need to convert to Integer[] just for printing in reverse order।


Searching Again

After sorting, Java provides built-in Binary Search।

For arrays:

Arrays.binarySearch(...)

For lists:

Collections.binarySearch(...)

Arrays.binarySearch()

Example:

int[] numbers = {
        10,
        20,
        30,
        40,
        50
};

int index =
        Arrays.binarySearch(
                numbers,
                30
        );

Result:

2

Search Requirement

The array must already be sorted according to the same ordering expected by the search।

This is non-negotiable।

Incorrect:

int[] numbers = {
        40,
        10,
        30,
        20
};

Arrays.binarySearch(
        numbers,
        30
);

Correct:

Arrays.sort(
        numbers
);

int index =
        Arrays.binarySearch(
                numbers,
                30
        );

Checking Presence

Simple membership check:

boolean found =
        Arrays.binarySearch(
                numbers,
                target
        ) >= 0;

Missing Value

If missing:

result < 0

Remember:

Do not assume the result is always -1.

The negative value encodes an insertion point।


Collections.binarySearch()

Example:

List<Integer> numbers =
        new ArrayList<>(
                List.of(
                        10,
                        20,
                        30,
                        40
                )
        );

int index =
        Collections.binarySearch(
                numbers,
                30
        );

Result:

2

Sorting Before Collections.binarySearch()

numbers.sort(
        Comparator.naturalOrder()
);

int index =
        Collections.binarySearch(
                numbers,
                target
        );

Binary Search with Objects

Suppose:

record Course(
        String code,
        String title,
        long priceInPaisa
) {
}

We sort by code:

Comparator<Course> byCode =
        Comparator.comparing(
                Course::code
        );

Then:

courses.sort(
        byCode
);

To search by that ordering, the search must use the same comparator।


Search Target Object

Collections.binarySearch() compares objects।

So we need a Course search key compatible with the comparator।

Example:

Course target =
        new Course(
                "JAVA",
                "",
                0
        );

Then:

int index =
        Collections.binarySearch(
                courses,
                target,
                byCode
        );

Because byCode only compares:

code

the other fields do not affect the search।


Complete Example

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<Course> courses =
                new ArrayList<>(
                        List.of(
                                new Course(
                                        "SYSTEM-DESIGN",
                                        "System Design",
                                        800_000
                                ),
                                new Course(
                                        "JAVA",
                                        "Java Foundation",
                                        300_000
                                ),
                                new Course(
                                        "BACKEND",
                                        "Backend Development",
                                        500_000
                                )
                        )
                );

        Comparator<Course> byCode =
                Comparator.comparing(
                        Course::code
                );

        courses.sort(
                byCode
        );

        Course target =
                new Course(
                        "JAVA",
                        "",
                        0
                );

        int index =
                Collections.binarySearch(
                        courses,
                        target,
                        byCode
                );

        System.out.println(
                index
        );
    }

    record Course(
            String code,
            String title,
            long priceInPaisa
    ) {
    }
}

Sorting and Searching Must Agree

This is one of the most important rules in this lesson।

If you sort by:

price

then Binary Search must also compare by:

price

You cannot sort by:

title

and then Binary Search using:

price

The data is not ordered correctly for that search criterion।


Wrong Example

Comparator<Course> byTitle =
        Comparator.comparing(
                Course::title
        );

Comparator<Course> byPrice =
        Comparator.comparingLong(
                Course::priceInPaisa
        );

courses.sort(
        byTitle
);

Collections.binarySearch(
        courses,
        target,
        byPrice
);

This violates Binary Search's ordering requirement।


Correct Example

courses.sort(
        byPrice
);

Collections.binarySearch(
        courses,
        target,
        byPrice
);

Same ordering rule।


Comparable Search

If Course implements Comparable<Course> by code:

Collections.sort(
        courses
);

then:

Collections.binarySearch(
        courses,
        target
);

can use that same natural ordering।


Comparator vs Comparable

A useful distinction:

Comparable<T>

Ordering belongs to the type itself।

Method:

compareTo(...)

Good when:

One clear natural ordering exists.

Comparator<T>

Ordering is defined externally।

Method:

compare(...)

usually created through utility methods or lambdas।

Good when:

Multiple meaningful orderings exist.

Practical Rule

For many domain models:

Prefer Comparator
unless a natural ordering is truly obvious.

Do not make a class Comparable just because sorting may happen someday।


Example: Course Ordering Choices

A Course may reasonably be sorted by:

Code
Title
Price
Lesson count
Popularity
Creation time

No single ordering may deserve to be called:

the natural Course order

Comparators are therefore often clearer।


Comparator Composition

Suppose we want:

price ascending
then title ascending
then code ascending

Use:

Comparator<Course> ordering =
        Comparator.comparingLong(
                Course::priceInPaisa
        )
                .thenComparing(
                        Course::title
                )
                .thenComparing(
                        Course::code
                );

This is significantly clearer than writing long manual comparison logic।


Manual Comparator

You can also write:

Comparator<Course> byPrice =
        new Comparator<>() {

            @Override
            public int compare(
                    Course first,
                    Course second
            ) {
                return Long.compare(
                        first.priceInPaisa(),
                        second.priceInPaisa()
                );
            }
        };

But modern Java usually uses lambdas or comparator factory methods।


Lambda Comparator

Equivalent:

Comparator<Course> byPrice =
        (
                first,
                second
        ) -> Long.compare(
                first.priceInPaisa(),
                second.priceInPaisa()
        );

Cleaner:

Comparator<Course> byPrice =
        Comparator.comparingLong(
                Course::priceInPaisa
        );

Prefer the factory method when it expresses the intent clearly।


Null Values and Sorting

Null introduces additional complexity।

Example:

List<String> names =
        new ArrayList<>();

names.add(
        "Sakib"
);

names.add(
        null
);

Natural comparison cannot simply call methods on null।

Comparator provides helpers:

Comparator.nullsFirst(...)
Comparator.nullsLast(...)

Example: Nulls Last

names.sort(
        Comparator.nullsLast(
                Comparator.naturalOrder()
        )
);

Result conceptually:

Sakib
null

Prefer Stronger Domain Invariants

Even though null-aware comparators exist, do not use them as an excuse to allow meaningless null values everywhere।

If:

Course title must never be null

validate that rule when constructing the Course।

Then sorting becomes simpler।


Sorting Cost

Built-in sorting does not make complexity disappear।

For general comparison-based object sorting, think broadly:

O(n log n)

for typical scalable library sorting behavior।

The exact implementation varies by data type and JDK internals।

For this course, focus on:

Use standard APIs
Understand the general complexity
Do not depend on internal algorithm details

Arrays.sort() Implementation Details

Do not build application logic around assumptions such as:

Arrays.sort always uses algorithm X.

Different overloads and Java versions may use different implementation strategies।

Treat:

Arrays.sort(...)

as an API contract, not an invitation to depend on internal implementation details।


Searching Cost

Binary Search:

O(log n)

But remember that preparing the data may require sorting:

O(n log n)

If you sort only to perform one search, Linear Search might still be simpler and cheaper overall।


Repeated Searches

If data is:

sorted once
searched many times

Binary Search becomes much more attractive।

Example:

Sort:
O(n log n)

Then each search:
O(log n)

Or Use a Map

If the real requirement is:

Find Course by code

then repeatedly Binary Searching a list may not be the best design।

A map may express the requirement better:

Map<String, Course>

Then typical lookup:

coursesByCode.get(
        code
);

is average:

O(1)

Use sorting when ordering matters।

Use maps when key-based lookup is the main access pattern।


Search API vs Data-Structure Design

Do not think:

"I know Binary Search,
therefore every lookup should use it."

Instead ask:

Do I need ordering?

Do I need positional access?

Do I need fast lookup by key?

Will data change frequently?

How many searches will happen?

Algorithm and data structure should be chosen together।


Common Mistake 1 — Manual Bubble Sort in Application Code

Avoid:

bubbleSort(
        values
);

when:

Arrays.sort(
        values
);

solves the normal requirement।

Manual algorithms belong mainly to learning and specialized cases।


Common Mistake 2 — Sorting an Unmodifiable List

This:

List<Integer> numbers =
        List.of(
                3,
                1,
                2
        );

numbers.sort(
        Comparator.naturalOrder()
);

will fail because the list is unmodifiable।

Create a mutable copy:

List<Integer> numbers =
        new ArrayList<>(
                List.of(
                        3,
                        1,
                        2
                )
        );

Common Mistake 3 — Forgetting Mutation

Both:

Arrays.sort(...)

and:

list.sort(...)

reorder the existing structure।

If original order matters, copy first।


Common Mistake 4 — Wrong Binary Search Ordering

Sorting by:

title

and searching with:

price comparator

is invalid।

Sorting comparator and searching comparator must agree।


Common Mistake 5 — Forcing Comparable

Do not define:

implements Comparable<Course>

unless the type has a meaningful natural ordering।

Use comparators for contextual ordering।


Common Mistake 6 — Subtraction Comparator

Avoid:

return first.score()
        - second.score();

Prefer:

Integer.compare(
        first.score(),
        second.score()
);

or:

Comparator.comparingInt(
        Learner::score
);

Common Mistake 7 — Misplacing reversed()

If only one field should be descending, reverse that comparator before thenComparing()

Correct:

Comparator.comparingInt(
        Learner::score
)
        .reversed()
        .thenComparing(
                Learner::name
        );

Common Mistake 8 — Expecting Binary Search to Return First Duplicate

Normal Binary Search gives a matching position, not necessarily:

first match

or:

last match

Use specialized boundary logic when that distinction matters।


Common Mistake 9 — Using Binary Search for Key Lookup by Habit

If you repeatedly need:

ID → object

a Map may be a much better abstraction।


Common Mistake 10 — Depending on JDK Sorting Internals

Your code should depend on:

API behavior

not assumptions about the exact internal algorithm Java currently uses।


Practical Example — Course Sorting

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

public class Main {

    public static void main(String[] args) {
        List<Course> courses =
                new ArrayList<>(
                        List.of(
                                new Course(
                                        "JAVA",
                                        "Java Foundation",
                                        300_000
                                ),
                                new Course(
                                        "SYSTEM-DESIGN",
                                        "System Design",
                                        800_000
                                ),
                                new Course(
                                        "BACKEND",
                                        "Backend Development",
                                        500_000
                                ),
                                new Course(
                                        "ALGORITHMS",
                                        "Algorithms",
                                        500_000
                                )
                        )
                );

        Comparator<Course> byPriceThenTitle =
                Comparator.comparingLong(
                        Course::priceInPaisa
                ).thenComparing(
                        Course::title
                );

        courses.sort(
                byPriceThenTitle
        );

        for (
                Course course
                : courses
        ) {
            System.out.println(
                    course.title()
                    + " - "
                    + course.priceInPaisa()
            );
        }
    }

    record Course(
            String code,
            String title,
            long priceInPaisa
    ) {
    }
}

Possible output:

Java Foundation - 300000
Algorithms - 500000
Backend Development - 500000
System Design - 800000

Among equal prices:

Algorithms

comes before:

Backend Development

because title is the secondary ordering field।


Practical Example — Ranking Learners

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

public class Main {

    public static void main(String[] args) {
        List<Learner> learners =
                new ArrayList<>(
                        List.of(
                                new Learner(
                                        "Sakib",
                                        91
                                ),
                                new Learner(
                                        "Subu",
                                        95
                                ),
                                new Learner(
                                        "Sumu",
                                        95
                                ),
                                new Learner(
                                        "Nur",
                                        88
                                )
                        )
                );

        Comparator<Learner> ranking =
                Comparator.comparingInt(
                        Learner::score
                )
                        .reversed()
                        .thenComparing(
                                Learner::name
                        );

        learners.sort(
                ranking
        );

        for (
                Learner learner
                : learners
        ) {
            System.out.println(
                    learner.name()
                    + " - "
                    + learner.score()
            );
        }
    }

    record Learner(
            String name,
            int score
    ) {
    }
}

Result:

Subu - 95
Sumu - 95
Sakib - 91
Nur - 88

Practice 1 — Sort an int[]

Given:

int[] scores = {
        90,
        60,
        80,
        70
};

sort ascending।

Solution

Arrays.sort(
        scores
);

Practice 2 — Preserve Original

Create a sorted copy without changing the original।

Solution

int[] sorted =
        Arrays.copyOf(
                scores,
                scores.length
        );

Arrays.sort(
        sorted
);

Practice 3 — Sort a List Naturally

List<String> names =
        new ArrayList<>(
                List.of(
                        "Sumu",
                        "Sakib",
                        "Nur"
                )
        );

Solution

names.sort(
        Comparator.naturalOrder()
);

Practice 4 — Reverse Order

Sort:

List<Integer>

descending।

Solution

numbers.sort(
        Comparator.reverseOrder()
);

Practice 5 — Sort Objects by Price

Given:

record Course(
        String title,
        long price
) {
}

Solution

courses.sort(
        Comparator.comparingLong(
                Course::price
        )
);

Practice 6 — Price Descending

Solution

courses.sort(
        Comparator.comparingLong(
                Course::price
        ).reversed()
);

Practice 7 — Price Then Title

Solution

courses.sort(
        Comparator.comparingLong(
                Course::price
        ).thenComparing(
                Course::title
        )
);

Practice 8 — Search Sorted Array

Given:

int[] numbers = {
        10,
        20,
        30,
        40
};

search for 30

Solution

int index =
        Arrays.binarySearch(
                numbers,
                30
        );

Practice 9 — Search Result

What does:

index >= 0

mean after binarySearch()?

Answer

The target was found at a valid index।


Practice 10 — Sort/Search Consistency

If a Course list is sorted using:

Comparator.comparing(
        Course::code
)

what comparator should Binary Search use?

Answer

The same code-based comparator, or another comparator defining exactly the same ordering।


Practice 11 — Comparable or Comparator?

You need to sort Course by:

price
title
popularity

in different screens।

Which abstraction is more appropriate?

Answer

Usually:

Comparator<Course>

because multiple contextual orderings are required।


Practice 12 — Key Lookup

You repeatedly need:

Course code → Course

and sorting is irrelevant।

Which structure is probably more appropriate?

Answer

Map<String, Course>

rather than repeatedly sorting and Binary Searching a list।


True or False

  1. Arrays.sort() mutates the supplied array.
  2. List.sort() can reorder an unmodifiable List.of(...).
  3. Comparable defines natural ordering.
  4. Comparator can define multiple external orderings.
  5. Comparator.comparingLong() is useful for a long property.
  6. reversed() can reverse comparator ordering.
  7. thenComparing() adds a secondary ordering.
  8. Binary Search works correctly regardless of how data was sorted.
  9. Sorting and Binary Search must use compatible ordering.
  10. Arrays.binarySearch() returns a negative result when missing.
  11. A domain type must always implement Comparable.
  12. Standard sorting APIs should normally replace textbook sorting algorithms in application code.

Answers

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

Knowledge Check

Question 1

Why should standard-library sorting normally be preferred over custom Bubble Sort or Quick Sort?

Question 2

What does Arrays.sort() do to the original array?

Question 3

What is natural ordering?

Question 4

What does Comparable<T> represent?

Question 5

What does Comparator<T> represent?

Question 6

When is Comparator preferable to Comparable?

Question 7

What does Comparator.comparingLong() do?

Question 8

What does thenComparing() do?

Question 9

Why must sorting and Binary Search use the same ordering rule?

Question 10

Why should subtraction usually not be used to implement numeric comparison?

Question 11

Why can a Map be better than Binary Search for repeated key lookup?

Question 12

Why should application code avoid depending on the exact internal sorting algorithm used by the JDK?


Knowledge Check Answers

Answer 1

Because standard-library implementations are tested, optimized, maintained, familiar, and significantly less likely to contain subtle algorithmic bugs।

Answer 2

It sorts the supplied array in place, changing its element order।

Answer 3

Natural ordering is the default ordering defined by a type, such as numeric order for Integer or lexicographical order for String

Answer 4

Comparable<T> allows a type to define its own natural ordering through:

compareTo(...)

Answer 5

Comparator<T> defines an ordering externally and allows the same type to have multiple different sorting rules।

Answer 6

When a type has several meaningful contextual orderings or no obvious single natural order।

Answer 7

It creates a comparator based on a long property extracted from an object।

Answer 8

It adds another comparison rule that is used when the earlier comparison considers two elements equal।

Answer 9

Binary Search decides which half to discard based on ordering. If its comparison rule differs from the rule used to sort the data, those decisions become invalid।

Answer 10

Subtraction can overflow and can therefore produce an incorrect comparison result for some numeric values।

Answer 11

A hash-based Map provides typical average O(1) lookup by key and directly models:

key → value

which may fit the requirement better than ordered searching।

Answer 12

Implementation details can differ between overloads and Java versions. Application code should depend on the public API's behavior rather than undocumented implementation choices।


Practical Decision Guide

Use:

Arrays.sort(...)

when:

You have an array
and need normal sorting.

Use:

list.sort(...)

when:

You have a mutable List
and need a specific ordering.

Use:

Comparable<T>

when:

The type has one genuinely natural order.

Use:

Comparator<T>

when:

Ordering depends on context
or multiple orders are useful.

Use:

Arrays.binarySearch(...)

when:

An array is already sorted
using compatible ordering.

Use:

Collections.binarySearch(...)

when:

A List is sorted
using compatible ordering.

Use:

HashSet

when:

Fast exact membership matters
and ordering is not required.

Use:

HashMap

when:

Fast key → value lookup
is the real requirement.

Lesson Summary

এই lesson-এ আমরা manual sorting/searching থেকে production-oriented Java APIs-এর দিকে এসেছি।

We learned:

  • Arrays.sort() sorts arrays in place
  • Primitive and object arrays can be sorted using standard APIs
  • List.sort() sorts mutable lists
  • Collections.sort() is another standard list-sorting API
  • Natural ordering is the default ordering of comparable types
  • Comparable<T> defines a type's natural order
  • Comparator<T> defines external/context-specific ordering
  • Comparator.comparing() sorts by object-valued properties
  • comparingInt(), comparingLong(), and comparingDouble() are useful for primitive properties
  • reversed() reverses ordering
  • thenComparing() supports multiple-field sorting
  • Comparator chaining can express business sorting rules clearly
  • Arrays.binarySearch() searches sorted arrays
  • Collections.binarySearch() searches sorted lists
  • Binary Search must use the same ordering semantics as sorting
  • Missing Binary Search results are negative
  • Standard sorting APIs should normally replace textbook manual sorts
  • A Set or Map may be more appropriate than Binary Search for some lookup requirements
  • Application code should depend on API contracts rather than JDK implementation details

The core production principle is:

Learn algorithms manually
to understand how they work.

Use standard Java APIs
for ordinary application code.

And for object ordering:

Comparable
→ one natural order

Comparator
→ contextual and multiple orders

Next Lesson

পরবর্তী lesson:

Using Stacks, Queues, and Deques in Algorithms

আমরা শিখব:

  • Choosing FIFO vs LIFO
  • Stack-based algorithms
  • Balanced brackets
  • Undo-style processing
  • Iterative traversal with Stack
  • Queue-based algorithms
  • Breadth-first processing intuition
  • Deque-based problems
  • Using ArrayDeque as an algorithmic worklist
  • How data-structure choice changes algorithm behavior