Java idioms summary

Java idioms summary

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. (Joshua Bloch's "Effective Java" gives a more detailed discussion on this topic, and you can learn more usage from this book.)

I have made all the code in this article available in a public place. You can copy and modify any code snippet as you like, no credentials are required.

Implementing equals()

  1. class Person {
  2. String name;
  3. int birthYear;
  4. byte[] raw;
  5. public boolean equals(Object obj) {
  6. if (!obj instanceof Person)
  7. return false;
  8. Person other = (Person)obj;
  9. return name.equals(other.name)
  10. && birthYear == other.birthYear
  11. && Arrays.equals(raw, other.raw);
  12. }
  13. public int hashCode() { ... }
  14. }

The parameter must be of type Object and cannot be of a peripheral class.

foo.equals(null) must return false and must not throw a NullPointerException. (Note that null instanceof any class always returns false, so the above code works.)

Primitive type fields (such as int) are compared using ==, and primitive type array fields are compared using Arrays.equals().

When overriding equals(), remember to override hashCode() accordingly to be consistent with equals().

Reference: java.lang.Object.equals(Object).

Implementing hashCode()

  1. class Person {
  2. String a;
  3. Object b;
  4. byte c;
  5. int[] d;
  6. public int hashCode() {
  7. return a.hashCode() + b.hashCode() + c + Arrays.hashCode(d);
  8. }
  9. public boolean equals(Object o) { ... }
  10. }

When two objects x and y have x.equals(y) == true , you must ensure that x.hashCode() == y.hashCode().

By contraposition, if x.hashCode() != y.hashCode(), then x.equals(y) == false must hold.

You don't need to ensure that x.hashCode() != y.hashCode() when x.equals(y) == false. However, if you can make it as close to true as possible, it will improve the performance of your hash table.

The simplest legal implementation of hashCode() is to simply return 0; while this implementation is correct, it will cause data structures such as HashMap to run very slowly.

Reference: java.lang.Object.hashCode().

Implementing compareTo()

  1. class Person implements Comparable<Person> {
  2. String firstName;
  3. String lastName;
  4. int birthdate;
  5. // Compare by firstName, break ties by lastName, finally break ties by birthdate
  6. public int compareTo(Person other) {
  7. if (firstName.compareTo(other.firstName) != 0)
  8. return firstName.compareTo(other.firstName);
  9. else if (lastName.compareTo(other.lastName) != 0)
  10. return lastName.compareTo(other.lastName);
  11. else if (birthdate < other.birthdate)
  12. return -1;
  13. else if (birthdate > other.birthdate)
  14. return 1;
  15. else
  16. return 0;
  17. }
  18. }

Always implement the generic version of Comparable instead of the original type Comparable, because it can save code and reduce unnecessary trouble.

Only the sign (negative/zero/positive) of the returned result matters, their magnitude is not important.

The implementation of Comparator.compare() is similar to this.

Reference: java.lang.Comparable.

#p#

Implementing clone()

  1. class Values ​​implements Cloneable {
  2. String abc;
  3. double foo;
  4. int[] bars;
  5. Date hired;
  6. public Values ​​clone() {
  7. try {
  8. Values ​​result = (Values)super.clone();
  9. result.bars = result.bars.clone();
  10. result.hired = result.hired.clone();
  11. return result;
  12. } catch (CloneNotSupportedException e) { // Impossible
  13. throw new AssertionError(e);
  14. }
  15. }
  16. }

Use super.clone() to let the Object class be responsible for creating new objects.

Primitive fields are correctly copied. Also, we don't need to clone immutable types like String and BigInteger.

Manually deep copy all non-primitive fields (objects and arrays).

For classes that implement Cloneable, the clone() method should never throw CloneNotSupportedException. Therefore, you need to catch this exception and ignore it, or wrap it with an unchecked exception.

It is possible and legal to implement the clone() method manually instead of using Object.clone().

Reference: java.lang.Object.clone(), java.lang.Cloneable().

Using StringBuilder or StringBuffer

  1. // join(["a", "b", "c"]) -> "a and b and c"
  2. String join(List<String> strs) {
  3. StringBuilder sb = new StringBuilder();
  4. boolean first = true;
  5. for (String s : strs) {
  6. if (first) first = false;
  7. else sb.append(" and ");
  8. sb.append(s);
  9. }
  10. return sb.toString();
  11. }

Do not use repeated string concatenation like this: s += item , as it is O(n^2) in time efficiency.

When using StringBuilder or StringBuffer, you can use the append() method to add text and the toString() method to get the entire concatenated text.

Prefer StringBuilder because it is faster. All methods of StringBuffer are synchronized, and you usually don't need synchronized methods.

Refer to java.lang.StringBuilder, java.lang.StringBuffer.

Generate a random integer in a range

  1. Random rand = new Random();
  2. // Between 1 and 6, inclusive
  3. int diceRoll() {
  4. return rand.nextInt(6) + 1;
  5. }

Always use the Java API methods to generate a random number within an integer range.

Do not try to use Math.abs(rand.nextInt()) % n, as the result is biased. In addition, the result may be negative, such as when rand.nextInt() == Integer.MIN_VALUE.

Reference: java.util.Random.nextInt(int).

Using Iterator.remove()

  1. void filter(List<String> list) {
  2. for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) {
  3. String item = iter.next();
  4. if (...)
  5. iter.remove();
  6. }
  7. }

The remove() method acts on the entry most recently returned by the next() method. The remove() method can only be used once per entry.

Reference: java.util.Iterator.remove().

Return string

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

This method should probably be added to the Java standard library.

Reference: java.lang.StringBuilder.reverse().

Start a thread

The following three examples use different methods to accomplish the same thing.

How to implement Runnnable:

  1. void startAThread0() {
  2. new Thread(new MyRunnable()).start();
  3. }
  4. class MyRunnable implements Runnable {
  5. public void run() {
  6. ...
  7. }
  8. }

Inherit Thread method:  

  1. void startAThread1() {
  2. new MyThread().start();
  3. }
  4. class MyThread extends Thread {
  5. public void run() {
  6. ...
  7. }
  8. }

Anonymous inheritance of Thread:  

  1. void startAThread2() {
  2. new Thread() {
  3. public void run() {
  4. ...
  5. }
  6. }.start();
  7. }

Do not call the run() method directly. Always call the Thread.start() method, which creates a new thread and makes the newly created thread call run().

Reference: java.lang.Thread, java.lang.Runnable.

#p#

Using try-finally

I/O stream example:

  1. void writeStuff() throws IOException {
  2. OutputStream out = new FileOutputStream(...);
  3. try {
  4. out.write(...);
  5. finally
  6. out.close();
  7. }
  8. }

Lock example:

  1. void doWithLock(Lock lock) {
  2. lock.acquire();
  3. try {
  4. ...
  5. finally
  6. lock.release();
  7. }
  8. }

If the statement before the try fails and throws an exception, the finally block will not be executed. But in any case, there is no need to worry about releasing resources in this example.

If the statement in the try block throws an exception, the program will jump to the finally block to execute as many statements as possible, and then jump out of the method (unless the method has another outer finally block).

Read bytes from the input stream

  1. InputStream in = (...);
  2. try {
  3. while (true) {
  4. int b = in.read();
  5. if (b == -1)
  6. break;
  7. (... process b ...)
  8. }
  9. finally
  10. in.close();
  11. }

The read() method either returns the next number of bytes to be read from the stream (0 to 255, inclusive), or returns -1 if the end of the stream is reached.

Reference: java.io.InputStream.read().

Read chunks of data from an input stream

  1. InputStream in = (...);
  2. try {
  3. byte[] buf = new byte[100];
  4. while (true) {
  5. int n = in.read(buf);
  6. if (n == -1)
  7. break;
  8. (... process buf with offset=0 and length=n ...)
  9. }
  10. finally
  11. in.close();
  12. }

Keep in mind that the read() method will not necessarily fill the entire buf, so you must account for the returned length in your processing logic.

Reference: java.io.InputStream.read(byte[]), java.io.InputStream.read(byte[], int, int).

Reading text from a file

  1. BufferedReader in = new BufferedReader(
  2. new InputStreamReader(new FileInputStream(...), "UTF-8"));
  3. try {
  4. while (true) {
  5. String line = in.readLine();
  6. if (line == null)
  7. break;
  8. (... process line ...)
  9. }
  10. finally
  11. in.close();
  12. }

The creation of the BufferedReader object is lengthy because Java treats bytes and characters as two different concepts (which is different from C).

You can use any type of InputStream instead of FileInputStream, such as socket.

When the end of the stream is reached, BufferedReader.readLine() returns null.

To read one character at a time, use the Reader.read() method.

You can use character encodings other than UTF-8, but you should not do so.

Reference: java.io.BufferedReader, java.io.InputStreamReader.

Writing text to a file

  1. PrintWriter out = new PrintWriter(
  2. new OutputStreamWriter(new FileOutputStream(...), "UTF-8"));
  3. try {
  4. out.print("Hello ");
  5. out.print(42);
  6. out.println(" world!");
  7. finally
  8. out.close();
  9. }

The creation of the Printwriter object is lengthy because Java treats bytes and characters as two different concepts (which is different from C).

Just like System.out, you can use print() and println() to print values ​​of many types.

You can use character encodings other than UTF-8, but you should not do so.

Reference: java.io.PrintWriter, java.io.OutputStreamWriter.

Defensive checking value

  1. int factorial(int n) {
  2. if (n < 0)
  3. throw new IllegalArgumentException("Undefined");
  4. else if (n >= 13)
  5. throw new ArithmeticException("Result overflow");
  6. else if (n == 0)
  7. return 1;
  8. else
  9. return n * factorial(n - 1);
  10. }

Don't assume that input values ​​are always positive, small enough, etc. Check these conditions explicitly.

A well-designed function should perform correctly for all possible input values. It should ensure that all cases are considered and that no incorrect output (such as overflow) is produced.

Preventive testing targets

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

Do not assume that object parameters will not be null. Check for this condition explicitly.

#p#

Preventive detection array index

  1. void frob(byte[] b, int index) {
  2. if (b == null)
  3. throw new NullPointerException();
  4. if (index < 0 || index >= b.length)
  5. throw new IndexOutOfBoundsException();
  6. ...
  7. }

Don't assume that any given array index will not be out of bounds. Check for it explicitly.

Preventive detection of array intervals

  1. void frob(byte[] b, int off, int len) {
  2. if (b == null)
  3. throw new NullPointerException();
  4. if (off < 0 || off > b.length
  5. || len < 0 || b.length - off < len)
  6. throw new IndexOutOfBoundsException();
  7. ...
  8. }

Do not assume that a given array range (e.g., starting at off and reading len elements) will not go out of bounds. Check it explicitly.

Filling array elements

Using loops:

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

(Preferred) Method using the standard library:

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

Reference: java.util.Arrays.fill(T[], T).

Reference: java.util.Arrays.fill(T[], int, int, T).

Copies a range of array elements

Using loops:  

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

(Preferred) Method using the standard library:

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

Reference: java.lang.System.arraycopy(Object, int, Object, int, int).

Resizing an Array

Using a loop (scaling up):

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

Using a loop (reduced size):

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

(Preferred) Method using the standard library:

  1. a = Arrays.copyOf(a, newLen);

Reference: java.util.Arrays.copyOf(T[], int).

Reference: java.util.Arrays.copyOfRange(T[], int, int).

Packing 4 bytes into an int

  1. int packBigEndian( byte [] b) {
  2. return (b[ 0 ] & 0xFF ) << 24   
  3. | (b[ 1 ] & 0xFF ) << 16   
  4. | (b[ 2 ] & 0xFF ) << 8   
  5. | (b[ 3 ] & 0xFF ) << 0 ;
  6. }
  7.    
  8. int packLittleEndian( byte [] b) {
  9. return (b[ 0 ] & 0xFF ) << 0   
  10. | (b[ 1 ] & 0xFF ) << 8   
  11. | (b[ 2 ] & 0xFF ) << 16   
  12. | (b[ 3 ] & 0xFF ) << 24 ;
  13. }

Unpacking an int into 4 bytes

  1. byte[] unpackBigEndian(int x) {
  2. return new byte[] {
  3. (byte)(x >>> 24),
  4. (byte)(x >>> 16),
  5. (byte)(x >>> 8),
  6. (byte)(x >>> 0)
  7. };
  8. }
  9. byte[] unpackLittleEndian(int x) {
  10. return new byte[] {
  11. (byte)(x >>> 0),
  12. (byte)(x >>> 8),
  13. (byte)(x >>> 16),
  14. (byte)(x >>> 24)
  15. };
  16. }

Always use the unsigned right shift operator (>>>) for packing bits, never the arithmetic right shift operator (>>).

Original link: nayuki Translation: ImportNew.com - Jinlin Translation link: http://www.importnew.com/15605.html

<<:  Alibaba campus recruitment: Talking about interviews and interview questions

>>:  Introduction to frequently used iOS third-party libraries and XCode plug-ins

Recommend

Kuaishou Search for Hidden Traffic Opportunities

When users' content consumption needs extend ...

The same old story: TV versions of video sites undergo a name change trend

Since June this year, the State Administration of...

Tooth-stuck warning! Be careful when buying these types of mangoes!

(Pictures are from the Internet) Mangoes are not ...

Countless shots, just to create a perfect memory

Memories are irreplaceable. Money can buy a new T...

Brand loyalty drops to 16%; S8 failure puts Samsung in trouble in China

The Galaxy S8 was seen by DJ Koh, the head of Sam...

Panasonic's new air purifier F-655FCV is available first

Since last year, the air purifier industry has sh...

Is this pop-up window weird? Beware of "logic bombs"!

1. Introduction Xiaobai: Dadongdong, can you help...