Project Nayuki


Good Java idioms

There are some things about programming in Java that are not obvious just by learning from the language specification or standard API documentation. In this document I will try to collect the most frequently used idioms, especially ones that are hard to get right by guessing. (To learn even more, the book Effective Java by Joshua Bloch gives a much more thorough treatment of this topic.)

I hereby place all code on this page in the public domain. Feel free to copy and modify any snippet of code however you like without credit.

Contents


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() { ... }
}

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) { ... }
}

Implementing compareTo()

class Person implements Comparable<Person> {
  String firstName;
  String lastName;
  int birthdate;
  
  // Compare by firstName, break ties by lastName,
  // finally break ties by 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;
  }
}

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 = result.hired.clone();
      return result;
    } catch (CloneNotSupportedException e) {  // Impossible
      throw new AssertionError(e);
    }
  }
}

Using StringBuilder/StringBuffer

// join(["a", "b", "c"]) -> "a and b and c"
String join(List<String> 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();
}

Generating a random integer in a range

Random rand = new Random();

// Between 1 and 6, inclusive
int diceRoll() {
  return rand.nextInt(6) + 1;
}

Using Iterator.remove()

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

Reversing a String

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

Starting a thread

The following 3 examples all accomplish the same thing, but in different ways.

By implementing Runnable:

void startAThread0() {
  new Thread(new MyRunnable()).start();
}

class MyRunnable implements Runnable {
  public void run() {
    ...
  }
}

By extending Thread:

void startAThread1() {
  new MyThread().start();
}

class MyThread extends Thread {
  public void run() {
    ...
  }
}

By anonymously extending Thread:

void startAThread2() {
  new Thread() {
    public void run() {
      ...
    }
  }.start();
}

Using try-finally

Example with I/O stream:

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

Example with lock:

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

Reading byte-wise from an InputStream

InputStream in = (...);
try {
  while (true) {
    int b = in.read();
    if (b == -1)
      break;
    (... process b ...)
  }
} finally {
  in.close();
}

Reading block-wise from an InputStream

InputStream in = (...);
try {
  byte[] buf = new byte[100];
  while (true) {
    int n = in.read(buf);
    if (n == -1)
      break;
    (... process buf with offset=0 and length=n ...)
  }
} finally {
  in.close();
}

Reading text from a file

BufferedReader in = new BufferedReader(
    new InputStreamReader(new FileInputStream(...), "UTF-8"));
try {
  while (true) {
    String line = in.readLine();
    if (line == null)
      break;
    (... process line ...)
  }
} finally {
  in.close();
}

Writing text to a file

PrintWriter out = new PrintWriter(
    new OutputStreamWriter(new FileOutputStream(...), "UTF-8"));
try {
  out.print("Hello ");
  out.print(42);
  out.println(" world!");
} finally {
  out.close();
}

Reading/writing an entire file

// Read all bytes from a file
byte[] bytes = Files.readAllBytes(Paths.get("infile.bin"));
// Write all bytes to a file
Files.write(Paths.get("outfile.bin"), bytes);

Charset cs = StandardCharsets.UTF_8;
// Read all lines from a file
List<String> lines = Files.readAllLines(Paths.get("infile.txt"), cs);
// Write all lines to a file
Files.write(Paths.get("outfile.txt"), lines, cs);

Defensive checking: values

int factorial(int n) {
  if (n < 0)
    throw new IllegalArgumentException("Undefined");
  else if (n >= 13)
    throw new ArithmeticException("Result overflow");
  else if (n == 0)
    return 1;
  else
    return n * factorial(n - 1);
}

Defensive checking: objects

int findIndex(List<String> list, String target) {
  if (list == null || target == null)
    throw new NullPointerException();
  ...
}

Defensive checking: array indexes

void frob(byte[] b, int index) {
  if (b == null)
    throw new NullPointerException();
  if (index < 0 || index >= b.length)
    throw new IndexOutOfBoundsException();
  ...
}

Defensive checking: array ranges

void frob(byte[] b, int off, int len) {
  if (b == null)
    throw new NullPointerException();
  if (off < 0 || off > b.length
    || len < 0 || b.length - off < len)
    throw new IndexOutOfBoundsException();
  ...
}

Filling array elements

Using a loop:

// Fill each element of array 'a' with 123
byte[] a = (...);
for (int i = 0; i < a.length; i++)
  a[i] = 123;

Using the standard library method (preferred):

Arrays.fill(a, (byte)123);

Copying a range of array elements

Using a loop:

// Copy 8 elements from array 'a' starting at offset 3
// to array 'b' starting at offset 6,
// assuming 'a' and 'b' are distinct arrays
byte[] a = (...);
byte[] b = (...);
for (int i = 0; i < 8; i++)
  b[6 + i] = a[3 + i];

Using the standard library method (preferred):

System.arraycopy(a, 3, b, 6, 8);

Resizing an array

Using a loop (upsizing):

// Make array 'a' larger to newLen
byte[] a = (...);
byte[] b = new byte[newLen];
for (int i = 0; i < a.length; i++)  // Goes up to length of A
  b[i] = a[i];
a = b;

Using a loop (downsizing):

// Make array 'a' smaller to newLen
byte[] a = (...);
byte[] b = new byte[newLen];
for (int i = 0; i < b.length; i++)  // Goes up to length of B
  b[i] = a[i];
a = b;

Using the standard library method (preferred):

a = Arrays.copyOf(a, newLen);

Packing 4 bytes into an int

int packBigEndian(byte[] b) {
  return (b[0] & 0xFF) << 24
       | (b[1] & 0xFF) << 16
       | (b[2] & 0xFF) <<  8
       | (b[3] & 0xFF) <<  0;
}

int packLittleEndian(byte[] b) {
  return (b[0] & 0xFF) <<  0
       | (b[1] & 0xFF) <<  8
       | (b[2] & 0xFF) << 16
       | (b[3] & 0xFF) << 24;
}

Unpacking an int into 4 bytes

byte[] unpackBigEndian(int x) {
  return new byte[] {
    (byte)(x >>> 24),
    (byte)(x >>> 16),
    (byte)(x >>>  8),
    (byte)(x >>>  0)
  };
}

byte[] unpackLittleEndian(int x) {
  return new byte[] {
    (byte)(x >>>  0),
    (byte)(x >>>  8),
    (byte)(x >>> 16),
    (byte)(x >>> 24)
  };
}