In Java programming, some knowledge cannot be learned only through language specifications or standard API documentation. In this article, I will try to collect some of the most commonly used idioms, especially those that are difficult to guess.
I put all the code in this article in public places. You can copy and modify any code snippet according to your preferences, without any credentials.
Implement equals()
class Person { String name; int birthdayYear; byte[] raw; public boolean equals(Object obj) { if (!obj instanceof Person) return false; Person other = (Person)obj; return name.equals(other.name) && birthdayYear == other.birthYear && Arrays.equals(raw, other.raw); } public int hashCode() { ... }} The parameter must be of type Object, not of a peripheral class.
foo.equals(null) must return false and cannot throw NullPointerException. (Note that null instanceof any class always returns false, so the above code can be run.)
Comparison of basic type domains (for example, int) is used ==, and comparison of basic type array domains is used by Arrays.equals().
When overwriting equals(), remember to overwrite hashCode() accordingly, to be consistent with equals().
Reference: java.lang.Object.equals(Object).
Implement 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) { ... }} When the x and y objects have x.equals(y) == true , you must make sure x.hashCode() == y.hashCode().
According to the inverse proposition, if x.hashCode() != y.hashCode() , then x.equals(y) == false must be true.
You don't need to guarantee that x.hashCode() != y.hashCode() when x.equals(y) == false. However, this improves the performance of the hash table if you can make it as long as possible.
The simplest legal implementation of hashCode() is to simply return 0; although this implementation is correct, this will cause the data structures such as HashMap to run very slowly.
Implement compareTo()
class Person implements Comparable<Person> { String firstName; String lastName; int birthday; // 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; }} Always implement generic version Comparable instead of primitive type Comparable. Because this can save code volume and reduce unnecessary hassle.
Just care about the signs (negative/zero/positive) that return the result, their size does not matter.
Comparator.compare() implementation is similar to this one.
Implement 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); } }} Use super.clone() to make the Object class responsible for creating new objects.
The basic type domains have been copied correctly. Again, we don't need to clone immutable types like String and BigInteger.
Manually perform deep copying of all non-primitive types fields (objects and arrays).
The Cloneable class is implemented, and the clone() method should never throw a CloneNotSupportedException. Therefore, you need to catch this exception and ignore it, or wrap it with an unchecked exception.
It is OK and legal to manually implement the clone() method without using the Object.clone() method.
Use StringBuilder or 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();} Don't use duplicate string concatenation like this: s += item , because its time efficiency is O(n^2).
When using StringBuilder or StringBuffer, you can use the append() method to add text and use the toString() method to get the connected entire text.
Priority is given to StringBuilder as it is faster. All methods of StringBuffer are synchronized, and you usually don't need a synchronized method.
Generate random integers in a range
Random rand = new Random();// Between 1 and 6, includingint diceRoll() { return rand.nextInt(6) + 1;} Always use the Java API method to generate a random number in the range of integers.
Don't try to use Math.abs(rand.nextInt()) %n for these uncertain uses, because its results are biased. In addition, its result value may be negative, such as when rand.nextInt() == Integer.MIN_VALUE.
Use Iterator.remove()
void filter(List<String> list) { for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) { String item = iter.next(); if (...) iter.remove(); }}The remove() method acts on the recently returned entry of the next() method. Each entry can only use the remove() method once.
Return string
String reverse(String s) { return new StringBuilder(s).reverse().toString();}This method should probably be added to the Java standard library.
Start a thread
The following three examples do the same thing in different ways.
How to implement Runnnable:
void startAThread0() { new Thread(new MyRunnable()).start();}class MyRunnable implements Runnable { public void run() { ... }}How to inherit Thread:
void startAThread1() { new MyThread().start();} class MyThread extends Thread { public void run() { ... }}How to inherit Thread anonymously:
void startAThread2() { new Thread() { public void run() { ... } }.start();}Do not call the run() method directly. The Thread.start() method is always called, which creates a new thread and causes the newly created thread to call run().
Use try-finally
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 the statement before the try fails to run and an exception is thrown, then the finally statement block will not be executed. But no matter what, in this example, there is no need to worry about the release of resources.
If the statement in the try statement block throws an exception, the program's running will jump to the finally statement block to execute as many statements as possible, and then jump out of this method (unless this method has another peripheral finally statement block).
Read byte data from the input stream
InputStream in = (...);try { while (true) { int b = in.read(); if (b == -1) break; (... process b ...) }} finally { in.close();}The read() method either returns the next number of bytes read from the stream (0 to 255, including 0 and 255), or returns -1 when the end of the stream is reached.
Read block data from the input stream
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();}Remember that the read() method does not necessarily fill the entire buf, so you have to consider the length of the return in the processing logic.
Read 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();} The creation of the BufferedReader object appears to be very verbose. This is because Java treats bytes and characters as two different concepts (this is different from C).
You can use any type of InputStream instead of FileInputStream, such as socket.
BufferedReader.readLine() returns null when the end of the stream is reached.
To read one character at a time, use the Reader.read() method.
You can use other character encodings without UTF-8, but it's better not to do so.
Write 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();} The creation of Printwriter objects appears to be very verbose. This is because Java treats bytes and characters as two different concepts (this is different from C).
Just like System.out, you can use print() and println() to print multiple types of values.
You can use other character encodings without UTF-8, but it's better not to do so.
Defensive checking value
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);} Don't think that the input values are positive, small enough, etc. To detect these conditions explicitly.
A well-designed function should be able to execute correctly for all possible input values. Make sure that all situations are taken into account and that there is no wrong output (such as overflow).
Preventive testing objects
int findIndex(List<String> list, String target) { if (list == null || target == null) throw new NullPointerException(); ...}Don't think that object parameters will not be null. To explicitly detect this condition.
Preventive detection array index
void frob(byte[] b, int index) { if (b == null) throw new NullPointerException(); if (index < 0 || index >= b.length) throw new IndexOutOfBoundsException(); ...}Don't think that the array index given will not cross the bounds. To detect it explicitly.
Preventive detection array interval
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(); ...}Don't think that the given array interval (for example, starting from off, reading len elements) will not go beyond the bounds. To detect it explicitly.
Fill array elements
Using loops:
// Fill each element of array 'a' with 123byte[] a = (...);for (int i = 0; i < a.length; i++) a[i] = 123;
(Preferential) Methods to use the standard library:
Arrays.fill(a, (byte)123);
Copy an array element in a range
Using loops:
// Copy 8 elements from array 'a' starting at offset 3// to array 'b' starting at offset 6,// assuming 'a' and 'b' are distinct arraysbyte[] a = (...);byte[] b = (...);for (int i = 0; i < 8; i++) b[6 + i] = a[3 + i];
(Preferential) Methods to use the standard library:
System.arraycopy(a, 3, b, 6, 8);
Resize array
Use loops (scaling up):
// Make array 'a' larger to newLenbyte[] 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;
Use loops (reduce the size):
// Make array 'a' smaller to newLenbyte[] 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;
(Preferential) Methods to use the standard library:
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;}Decompose 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) };}Always use the unsigned right-shift operator (>>>) to wrap the bits, do not use the arithmetic right-shift operator (>>).