Fundamentals 14 min read

Java Programming Essentials: Implementing equals, hashCode, compareTo, clone, and Common Idioms

This article presents a collection of essential Java programming practices, covering how to correctly implement equals, hashCode, compareTo, clone, use StringBuilder, generate random numbers, manage threads, handle I/O with try‑finally, perform defensive checks, and manipulate arrays and primitive packing.

Java Captain
Java Captain
Java Captain
Java Programming Essentials: Implementing equals, hashCode, compareTo, clone, and Common Idioms

In Java programming, many useful techniques cannot be learned solely from the language specification or standard API documentation; this article gathers frequently needed idioms that are often non‑obvious.

1. Implementing equals()

class Person {
    String name;
    int birthYear;
    byte[] raw;
    public boolean equals(Object obj) {
        if (!(obj instanceof Person))
            return false;
        Person other = (Person) obj;
        return name.equals(other.name)
            && birthYear == other.birthYear
            && Arrays.equals(raw, other.raw);
    }
    public int hashCode() { /* ... */ }
}

The parameter must be of type Object , not the concrete class.

foo.equals(null) must return false without throwing NullPointerException .

Use == for primitive fields and Arrays.equals() for primitive arrays.

When overriding equals() , also override hashCode() to stay consistent.

2. Implementing hashCode()

class Person {
    String a;
    Object b;
    byte c;
    int[] d;
    public int hashCode() {
        return a.hashCode() + b.hashCode() + c + Arrays.hashCode(d);
    }
    public boolean equals(Object o) { /* ... */ }
}

If x.equals(y) == true , then x.hashCode() == y.hashCode() must hold.

The converse is not required, but aligning hash codes improves hash‑table performance.

A trivial but correct implementation is return 0; , though it degrades performance.

3. Implementing compareTo()

class Person implements Comparable
{
    String firstName;
    String lastName;
    int birthdate;
    public int compareTo(Person other) {
        if (firstName.compareTo(other.firstName) != 0)
            return firstName.compareTo(other.firstName);
        else if (lastName.compareTo(other.lastName) != 0)
            return lastName.compareTo(other.lastName);
        else if (birthdate < other.birthdate)
            return -1;
        else if (birthdate > other.birthdate)
            return 1;
        else
            return 0;
    }
}

Prefer the generic Comparable<T> form.

Only the sign of the result matters; its magnitude is irrelevant.

The same pattern applies to Comparator.compare() .

4. Implementing clone()

class Values implements Cloneable {
    String abc;
    double foo;
    int[] bars;
    Date hired;
    public Values clone() {
        try {
            Values result = (Values) super.clone();
            result.bars = result.bars.clone();
            result.hired = (Date) result.hired.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(e);
        }
    }
}

Use super.clone() to let Object create the shallow copy.

Manually deep‑copy mutable fields such as arrays and mutable objects.

Classes that implement Cloneable should never let clone() throw CloneNotSupportedException .

Providing a custom clone implementation is legal and sometimes preferable.

5. Using StringBuilder / StringBuffer

// join(["a", "b", "c"]) -> "a and b and c"
String join(List
strs) {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    for (String s : strs) {
        if (first) first = false;
        else sb.append(" and ");
        sb.append(s);
    }
    return sb.toString();
}

Avoid repeated string concatenation ( s += item ) which is O(n²).

Prefer StringBuilder (unsynchronized) over StringBuffer unless synchronization is required.

6. Generating a Random Integer in a Range

Random rand = new Random();
int diceRoll() {
    return rand.nextInt(6) + 1; // 1‑6 inclusive
}

Use the Java API (e.g., nextInt(bound) ) instead of biased tricks like Math.abs(rand.nextInt()) % n .

7. Using Iterator.remove()

void filter(List
list) {
    for (Iterator
iter = list.iterator(); iter.hasNext(); ) {
        String item = iter.next();
        if (/* condition */) {
            iter.remove();
        }
    }
}

remove() applies to the element returned by the most recent next() call and may be used only once per element.

8. Reversing a String

String reverse(String s) {
    return new StringBuilder(s).reverse().toString();
}

This utility could be a useful addition to the standard library.

9. Starting a Thread

Three equivalent ways:

void startAThread0() {
    new Thread(new MyRunnable()).start();
}
class MyRunnable implements Runnable {
    public void run() { /* ... */ }
}
void startAThread1() {
    new MyThread().start();
}
class MyThread extends Thread {
    public void run() { /* ... */ }
}
void startAThread2() {
    new Thread() {
        public void run() { /* ... */ }
    }.start();
}

Never invoke run() directly; always call Thread.start() to create a new thread.

10. Using try‑finally for Resource Management

I/O stream example:

void writeStuff() throws IOException {
    OutputStream out = new FileOutputStream(...);
    try {
        out.write(...);
    } finally {
        out.close();
    }
}

Lock example:

void doWithLock(Lock lock) {
    lock.acquire();
    try {
        /* ... */
    } finally {
        lock.release();
    }
}

If an exception occurs before the try block, the finally block is not executed.

If an exception is thrown inside the try , control jumps to finally before propagating.

11‑14. Common I/O Patterns

Reading bytes, reading blocks, reading lines, and writing text are demonstrated with InputStream , BufferedReader , and PrintWriter examples, emphasizing the separation of byte and character streams in Java.

15‑18. Defensive Checking

Examples show how to validate numeric arguments, object references, array indices, and array ranges, throwing appropriate exceptions such as IllegalArgumentException , NullPointerException , or IndexOutOfBoundsException .

19‑23. Array Utilities and Bit Packing

Techniques include filling arrays ( Arrays.fill ), copying ranges ( System.arraycopy ), resizing ( Arrays.copyOf ), packing four bytes into an int (big‑ and little‑endian), and unpacking an int back into bytes using unsigned right‑shift ( >>>> ).

Javabest practicesCollectionscloneequalsHashCodecompareTo
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.