Good Java idioms
2016-04-29 00:59
537 查看
原文链接
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.
Implementing:
Using:
Input/output:
Reading byte-wise from
Reading block-wise from
Reading text from file
Writing text from file
Defensive checking:
Values
Objects
Array indexes
Array ranges
Arrays:
Filling elements
Copying a range
Resizing an array
Packing:
4 bytes to
The parameter must be of type
(Note that
Compare primitive fields (e.g.
and compare array-of-primitive fields using
When overriding
See:
When two objects
By contrapositive, if
then it must be the case that
It is not required that when
can make this occur as often as possible, then it improves the performance of hash tables.
The simplest legal implementation of
like
See:
Always implement the generic version
and hassle.
Only the sign of the returned result matters (negative/zero/positive), not the magnitude.
Implementing
similar to this.
See:
Use
The primitive fields are already copied properly. Also, there is no need to clone fields of immutable types such as
Manually make a deep copy of all the non-primitive fields (objects and arrays).
When the class implements
So catch the exception and ignore it, or wrap it in an unchecked exception.
It’s also possible and legal to implement
See:
Don’t use repeated string concatenation like this because it takes O(n2) time:
In
add text and
don’t need.
See:
Always use the Java API method to generate random numbers in an integer range.
Never try to improvise something like
See:
only be used once per item.
See:
Maybe this ought to belong in the Java standard library.
See:
The following 3 examples all accomplish the same thing, but in different ways.
By implementing
By extending
By anonymously extending
Do not call
call
See:
Example with I/O stream:
Example with lock:
If the statement before the
is nothing to release anyway.
If a statement inside the
as much as possible, then jump out of the method (unless there is another enclosing
See:
Remember that
logic.
See:
The creation of the
You can replace the
the end of the stream is reached.
To read one character at a time instead, use
You could use character encodings other than UTF-8, but it is inadvisable.
See:
The creation of the
Just like with
types of values.
You could use character encodings other than UTF-8, but it is inadvisable.
See:
Never assume that numeric inputs are going to be positive, sufficiently small, etc. Check for these conditions explicitly.
A well-designed function should behave correctly for all possible input values. Carefully ensure that all cases are considered and that bad output (such as overflow) is never generated.
Never assume that object arguments are not
Never assume that a given array index is within bounds. Check explicitly.
Never assume that a given array range (i.e. “starting at
explicitly.
Using a loop:
Using the standard library method (preferred):
See:
See:
Using a loop:
Using the standard library method (preferred):
See:
Using a loop (upsizing):
Using a loop (downsizing):
Using the standard library method (preferred):
See:
See:
Always use the unsigned right shift operator (
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()
hashCode()
compareTo()
clone()
Using:
StringBuilder/
StringBuffer
Random.nextInt(int)
Iterator.remove()
StringBuilder.reverse()
Thread
/Runnable
try-
finally
Input/output:
Reading byte-wise from
InputStream
Reading block-wise from
InputStream
Reading text from file
Writing text from file
Defensive checking:
Values
Objects
Array indexes
Array ranges
Arrays:
Filling elements
Copying a range
Resizing an array
Packing:
4 bytes to
int
intto 4 bytes
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 type of the enclosing class.
foo.equals(null)must return
false, not throw a
NullPointerException.
(Note that
null instanceof AnyClassis always
false, so the code above works.)
Compare primitive fields (e.g.
int) using
==, compare object fields using
equals(),
and compare array-of-primitive fields using
Arrays.equals().
When overriding
equals(), remember to override
hashCode()in a way that is consistent with
equals().
See:
java.lang.Object.equals(Object)
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) { ... } }
When two objects
xand
yhave
x.equals(y) == true, you must ensure that
x.hashCode() == y.hashCode().
By contrapositive, if
x.hashCode() != y.hashCode(),
then it must be the case that
x.equals(y) == false.
It is not required that when
x.equals(y) == false, you have
x.hashCode() != y.hashCode(). But if you
can make this occur as often as possible, then it improves the performance of hash tables.
The simplest legal implementation of
hashCode()is simply
return 0;. However, this will cause data structures
like
HashMapto run slowly, though correctly.
See:
java.lang.Object.hashCode()
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; } }
Always implement the generic version
Comparable<T>rather than the raw type
Comparablebecause it saves code
and hassle.
Only the sign of the returned result matters (negative/zero/positive), not the magnitude.
Implementing
Comparator.compare()is quite
similar to this.
See:
java.lang.Comparable
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); } } }
Use
super.clone()to make the
Objectclass be responsible for creating the new object.
The primitive fields are already copied properly. Also, there is no need to clone fields of immutable types such as
Stringand
BigInteger.
Manually make a deep copy of all the non-primitive fields (objects and arrays).
When the class implements
Cloneable,
clone()will never throw
CloneNotSupportedException.
So catch the exception and ignore it, or wrap it in an unchecked exception.
It’s also possible and legal to implement
clone()manually without using
Object.clone().
See:
java.lang.Object.clone(),
java.lang.Cloneable
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(); }
Don’t use repeated string concatenation like this because it takes O(n2) time:
s += item;
In
StringBuilderor
StringBuffer, use
append()to
add text and
toString()to get the entire accumulated text.
StringBuilderis preferred because it’s faster.
StringBufferhas all synchronized methods, which you usually
don’t need.
See:
java.lang.StringBuilder,
java.lang.StringBuffer
Generating a random integer in a range
Random rand = new Random(); // Between 1 and 6, inclusive int diceRoll() { return rand.nextInt(6) + 1; }
Always use the Java API method to generate random numbers in an integer range.
Never try to improvise something like
Math.abs(rand.nextInt()) % nbecause it is biased. Furthermore, the value can be negative when
rand.nextInt() == Integer.MIN_VALUE.
See:
java.util.Random.nextInt(int)
Using Iterator.remove()
void filter(List<String> list) { for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) { String item = iter.next(); if (...) iter.remove(); } }
remove()acts on the most recent item returned by
next().
remove()can
only be used once per item.
See:
java.util.Iterator.remove()
Reversing a String
String reverse(String s) { return new StringBuilder(s).reverse().toString(); }
Maybe this ought to belong in the Java standard library.
See:
java.lang.StringBuilder.reverse()
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(); }
Do not call
run()directly. Always call
Thread.start(), which creates a new thread and makes that new thread
call
run().
See:
java.lang.Thread,
java.lang.Runnable
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(); } }
If the statement before the
tryfails and throws an exception, then the
finallyblock won’t execute, but there
is nothing to release anyway.
If a statement inside the
tryblock throws an exception, then execution will jump to the
finallyblock, execute
as much as possible, then jump out of the method (unless there is another enclosing
finallyblock).
Reading byte-wise from an InputStream
InputStream in = (...); try { while (true) { int b = in.read(); if (b == -1) break; (... process b ...) } } finally { in.close(); }
read()either returns the next byte value (range 0 to 255, inclusive) from the stream or returns −1 if the stream has ended.
See:
java.io.InputStream.read()
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(); }
Remember that
read()does not necessarily fill all of
buf. You must consider the returned length in your processing
logic.
See:
java.io.InputStream.read(byte[]),
java.io.InputStream.read(byte[], int, int)
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(); }
The creation of the
BufferedReaderobject is cumbersome. But it’s because Java treats bytes and characters as separate concepts (unlike C, for example).
You can replace the
FileInputStreamwith any kind of
InputStream, such as one from a
Socket.
BufferedReader.readLine()returns
nullwhen
the end of the stream is reached.
To read one character at a time instead, use
Reader.read().
You could use character encodings other than UTF-8, but it is inadvisable.
See:
java.io.BufferedReader,
java.io.InputStreamReader
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(); }
The creation of the
PrintWriterobject is cumbersome. But it’s because Java treats bytes and characters as separate concepts (unlike C, for example).
Just like with
System.out, you can
print()and
println()many
types of values.
You could use character encodings other than UTF-8, but it is inadvisable.
See:
java.io.PrintWriter,
java.io.OutputStreamWriter
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); }
Never assume that numeric inputs are going to be positive, sufficiently small, etc. Check for these conditions explicitly.
A well-designed function should behave correctly for all possible input values. Carefully ensure that all cases are considered and that bad output (such as overflow) is never generated.
Defensive checking: objects
int findIndex(List<String> list, String target) { if (list == null || target == null) throw new NullPointerException(); ... }
Never assume that object arguments are not
null. Check for this condition explicitly.
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(); ... }
Never assume that a given array index is within bounds. Check explicitly.
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(); ... }
Never assume that a given array range (i.e. “starting at
off, going for
lenelements”) is within bounds. Check
explicitly.
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);
See:
java.util.Arrays.fill(T[], T)
See:
java.util.Arrays.fill(T[], int, int, T)
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);
See:
java.lang.System.arraycopy(Object, int, Object, int, int)
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);
See:
java.util.Arrays.copyOf(T[], int)
See:
java.util.Arrays.copyOfRange(T[], int, int)
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) }; }
Always use the unsigned right shift operator (
>>>) for bit packing, never the arithmetic right shift operator (
>>).
相关文章推荐
- maven设置默认JDK版本
- Java内存模型-jsr133规范介绍
- java垃圾回收总结(2)
- java 垃圾回收总结(1)
- JDK集合分析Set和Map的关系(自己实现Set到Map的扩展)
- java中volatile关键字的含义
- Java中的format相关知识小结
- 【11】-java递归和非递归二叉树前序中序后序遍历
- 【11】-java递归和非递归二叉树前序中序后序遍历
- 【11】-java递归和非递归二叉树前序中序后序遍历
- Java 编程的动态性,第 1 部分: 类和类装入
- java 反射机制
- java上机。。。包。。。
- 学习笔记(2016-04-25)
- 【代码笔记】Java连连看项目的实现(1)——JTable 、TableModel的使用
- spring-core组件详解——类型转换系统
- SpringMVC 使用验证框架 Bean Validation(上)
- java中volatile关键字的含义
- Java 类加载机制详解
- Java提升-模板模式(六)