Table of contents
- Item 57. Minimize the scope of local variables
- Item 58. Prefer for-each loops to traditional for loops
- Item 59. Know and use the libraries
- Item 60. Avoid float and double if exact answers are required
- Item 61. Prefer primitive types to boxed primitives
- Item 62. Avoid strings where other types are more appropriate
- Item 63. Beware the performance of string concatenation
- Item 64. Refer to objects by their interfaces
- Item 65. Prefer interfaces to reflection
- Item 66. Use native methods judiciously
- Item 67. Optimize judiciously
- Item 68. Adhere to generally accepted naming conventions
Item 57. Minimize the scope of local variables
Minimize the scope of local variables
Declare and initialize local variables where you use them
Older coding habits put declarations at the top of a block. Java allows declarations anywhere, so declare variables when you first need them. This reduces scope. Also initialize every local variable when you declare it to avoid confusion.
If you cannot initialize a variable immediately, declare it when you can. try-catch is an exception. If you must use the variable outside the try, declare it before the try and initialize inside the try.
Prefer for loops to while loops
while loops often leave variables in scope after the loop ends.
Iterator<Element> i = c.iterator(); // Unnecessary.
while (i.hasNext()) {
doSomething(i.next());
}
for loops keep the loop variable inside the loop scope. You can reuse the same variable name in another loop without interference.
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
Element e = i.next();
// Do something with e and i.
}
Another way to reduce scope is to keep methods small and focused. When a method does multiple jobs, unrelated code can access variables it should not. Splitting methods by responsibility keeps scopes tight.
Item 58. Prefer for-each loops to traditional for loops
Prefer for-each loops to traditional for loops
You often traverse arrays and collections with for loops. The iterator or index variable exists only to reach elements, so it adds noise and can introduce errors.
Prefer the enhanced for statement (for-each).
Item 59. Know and use the libraries
Know and use the libraries
Benefits of standard libraries
- You leverage the expertise of the developers who wrote them.
- You spend less time on non-core work.
- Performance improves continuously without extra effort.
- Features keep expanding as the community drives new releases.
- The code is familiar to more people, so it is easier to read, maintain, and reuse.
Third-party libraries
Major releases add many features to the standard library. Java developers should be comfortable with java.lang, java.util, java.io, and subpackages. If what you need is missing, look for a reliable third-party library.
Item 60. Avoid float and double if exact answers are required
60: Avoid float and double if exact answers are required
float and double are designed for scientific and engineering calculations. They represent a wide range of numbers as fast, precise approximations. Because they cannot represent 0.1 or powers of 10 exactly, they are not suitable for financial calculations.
System.out.println(1.03 - 0.42);
// Expected: 0.61
// Actual: 0.6100000000000001
When you need exact answers, use BigDecimal, int, or long.
BigDecimal is slower and less convenient than primitives. Use int or long when possible, but note the range limits and the need to manage the decimal point yourself.
If performance is not a priority, use BigDecimal. If your numbers fit, use int or long. Use int for up to 9 decimal digits, long for up to 18, and BigDecimal beyond that.
Item 61. Prefer primitive types to boxed primitives
Prefer primitive types to boxed primitives
Java types fall into primitives like int, double, and boolean, and reference types like String and List. Each primitive has a boxed counterpart such as Integer, Double, and Boolean.
Autoboxing and unboxing make them easy to mix, but there are important differences.
First, primitives have values only, while boxed types have values and identity. Two boxed instances can have the same value but still be distinct objects. Second, primitives always have a valid value, while boxed types can be null. Third, primitives are generally faster and more memory-efficient.
Problem 1: incorrect comparison
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
// What does this return?
naturalOrder.compare(new Integer(42), new Integer(42));
This comparator uses == on boxed primitives, which compares identity. It returns 1.
Problem 2: runtime error
public class Unbelievable {
static Integer i;
public static void main(String[] args) {
if (i == 42) {
System.out.println("Hello!");
}
}
}
This prints nothing and throws NullPointerException because i is null. When you mix primitives and boxed types, Java unboxes the boxed value. The fix is to declare i as int.
Problem 3: performance regression
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++>) {
sum += i;
}
return sum;
}
Boxing and unboxing inside the loop slows performance significantly.
When to use boxed primitives
Use boxed primitives as collection elements, keys, or values because collections cannot store primitives. Use them as type parameters for generics and when invoking methods via reflection.
Item 62. Avoid strings where other types are more appropriate
Avoid strings where other types are more appropriate
Strings are easy to use, so they are often misused.
Item 63. Beware the performance of string concatenation
Beware the performance of string concatenation
Concatenating n strings takes time proportional to \({ n }^{ 2 }\). Strings are immutable, so each concatenation copies both sides, which is expensive.
Item 64. Refer to objects by their interfaces
Refer to objects by their interfaces
If a suitable interface exists, declare parameters, return values, variables, and fields using that interface type.
// Good: interface type
Set<Fruit> fruitSet = new LinkedHashSet<>();
// Bad: concrete class type
LinkedHashSet<Fruit> fruitSet = new LinkedHashSet<>();
Interface types let you swap implementations later for performance or features. For example, switching from HashMap to EnumMap improves speed and iteration order. But EnumMap works only when keys are enums, so a general alternative is LinkedHashMap.
If the implementation adds special behavior beyond the interface contract, be careful. Code that assumes ordering with LinkedHashSet can break if you replace it with HashSet.
If no suitable interface exists, use a class type. Value classes like String and BigInteger are examples. Objects provided by class-based frameworks (for example, classes in java.io) also require class types. So do classes like PriorityQueue that expose special methods not present in interfaces.
Item 65. Prefer interfaces to reflection
Prefer interfaces to reflection
Java reflection lets you access arbitrary classes at runtime.
Reflection is powerful, but it has major downsides. It bypasses compile-time checks, increases runtime failures, makes code verbose and hard to read, and reduces performance. The author reports a 11x slowdown in experiments.
If you are unsure whether to use reflection, you probably should not. If you do use it, limit it. Use reflection only for instance creation, then refer to the instance through an interface or superclass.
Reflection is appropriate when you must deal with classes, methods, or fields that may not exist at runtime. This is useful when supporting multiple versions of external packages. Compile against the oldest supported version and access newer APIs via reflection, accepting that they may not exist at runtime.
Item 66. Use native methods judiciously
Use native methods judiciously
The Java Native Interface (JNI) allows Java programs to call native methods. A native method is written in a native programming language.
Item 67. Optimize judiciously
Optimize judiciously
Write good programs, not just fast programs
Do not sacrifice solid structure for performance. Good programs outlast fast hacks. Consider performance at design time, because architectural flaws can limit performance and may require a rewrite to fix.
Avoid designs that limit performance
The hardest design decisions to change after release are how components communicate with each other and with external systems. These choices often limit performance and can be difficult or impossible to change later.
Consider performance impact when you design APIs
If public methods allow internal data modification, you force defensive copies. If you choose inheritance instead of composition, you lock yourself into a superclass and inherit its performance characteristics, making it difficult to adopt faster implementations later.
Measure before and after each optimization
Profiling tools show where to focus optimization work. Start by examining your algorithms.
Item 68. Adhere to generally accepted naming conventions
Adhere to generally accepted naming conventions
Make standard naming rules a habit. Javaโs naming rules are well defined. If a long-standing local rule conflicts, do not follow it blindly. Use common sense.