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

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);
    }
  }
}

Implementing hashCode()

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

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();
}

Staring 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 = (...);
while (true) {
  int b = in.read();
  if (b == -1)
    break;
  (... process b ...)
}
in.close();

Reading block-wise from an InputStream

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

Reading text from a file

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

Writing text to a file

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

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

Packing 4 bytes into an int

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

int packLittleEndian(byte[] b) {
  return b[0] & 0xFF
      | (b[1] & 0xFF) << 8
      | (b[2] & 0xFF) << 16
      |  b[3] << 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)
  };
}


Feedback

Question? Comment? Contact me

ProjectNayuki: Like, comment, follow updates on Facebook