Java Effective Chapter 9 - mariamaged/Java-Android-Kotlin GitHub Wiki

Java - Effective Chapter 9

Item 57: Minimize the Scope of Local Variables

The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used.

  • If a variable is declared before it is used, it is just clutter - one more thing to distract the reader who is trying to figure out what the program does.
    • By the time the variable is used, the reader might not remember the variable's type or initial value.
  • Declaring a local variable prematurely can cause its scope not only to begin too early but also to end too late.
    • The scope of a local variable extends from the point where it is declared to the end of the enclosing block.
    • If a variable is declared outside of the block in which it is used, it remains visible after the program exists that block.
  • Nearly every local variable declaration should contain an initializer.
    • If you don't have enough information to initialize a variable sensibly, you should postpone the declaration until you do.
    • One exception to this rule concerns try catch statements.
    • If a variable is initialized to an expression whose evaluation throws a checked exception, the variable must be initialized inside a try block.
    • If the value must be used outside of the try block, then it must be declared before the try block, where it cannot yet be "sensibly" initialized.
  • Keep methods small and focused.

Item 58: Prefer for-each loops to Traditional for Loops

enter image description here
  enter image description here

  • These idioms are better than while loops, but they aren't perfect.
  • The iterator and index variables are both a clutter-all you need are the elements.
  • Furthermore, they represent opportunities for error.
     
  • The for-each loop solves all of these problems.
  • It gets rids of the clutter and the opportunity for error by hiding the iterator or index variable.
  • The resulting idiom applies equally to collections and arrays, easing the process of switching the implementation type of a container from one to the other:
     

enter image description here

  • The advantages of the for-each loop over the traditional for loops are even greater when it comes to nested iteration.
  • Here is a common mistake that people make when doing nested iteration:
enum Suit {CLUB, DIAMOND, HEART, SPADE}  
enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING}  
  
public class Effective {  
    static Collection<Suit> suits = Arrays.asList(Suit.values());  
    static Collection<Rank> ranks = Arrays.asList(Rank.values());  
    public static void main(String[] args) {  
        List<Card> deck = new ArrayList<>();  
        for(Iterator<Suit> i = suits.iterator(); i.hasNext();)  
            for(Iterator<Rank> j = ranks.iterator(); j.hasNext();)  
                deck.add(new Card(i.next(), j.next()));  
    }  
}
// Solution - NOT THE BEST
for(Iterator<Suit> i = suits.iterator(); i.hasNext();) {
	Suit suit = i.next();
	for(Iterator<Rank> j = ranks.iterator(); j.hasNext();) 
		deck.add(new Card(suit, j.next());
}		
// Solution - BEST USING FOREACH LOOP
```java
for(Suit suit : suits)
	for(Rank rank: ranks)
		deck.add(new Card(suit, rank);
  • Consider this ill-conceived attempt to print all the possible rolls of a pair of dice:
enum Face {ONE, TWO, THREE, FOUR, FIVE, SIX}  
public class Effective1 {  
    public static void main(String[] args) {  
        Collection<Face> faces = EnumSet.allOf(Face.class);  
        for(Iterator<Face> i = faces.iterator(); i.hasNext();)  
            for(Iterator<Face> j = faces.iterator(); j.hasNext();)  
                System.out.println(i.next() + " "+ j.next());  
    }    
}
  • Unfortunately, there are three common situations where you can't use for-each:
    • Destructive filtering- If you need to traverse a collection removing selected elements, then you need to use an explicit iterator so that you can call its remove method.
      • You can often avoid explicit traversal by using Collection's removeIf, added in Java 8.
    • Transforming- If you want to traverse a list or array and replace some or all of the values of its elements, then you need the list iterator or array index in order to replace the value of an element.
    • Parallel iteration- If you need to traverse multiple collections in parallel, then you need explicit control over the iterator or index variable so that iterators or index variables can be advanced in lockstep.
       
  • Not only does the for-each loop let you iterate over collections and arrays, it lets you iterate over any object that implements the Iterable interface, which consists of a single method.
public interface Iterable<E> {
	// Returns an iterator over the elements in this iterable 
	Iterator<E> iterator;
}

Item 59: Know and use the Libraries

  • Suppose you want to generate random integers between zero and some upper bound.
// Common but deeply flawed
static Random rnd = new Random();
static int random(int n) {
	return Math.abs(rnd.nextInt()) % n;
}
  1. First fault: if n is a small power of 2, the sequence of random numbers will repeat itself after a fairly short period.
  2. Second fault: if n is not a power of 2, some numbers will, on average, be returned more frequently than others.
    • If n is large, this effect can be quite pronounced.
    • This is powerfully demonstrated by the following program, which generates a million random numbers in a carefully chosen range and then prints out how many of the numbers fell in the lower half of the range.
    • If the random method worked properly, the program would print a number close to half a million, but if you run it, it doesn't happen.
  3. Third fault: it can, on random occasions, fail catastrophically, returning a number outside the specified range.
    • This is so because the method attempts to map the value returned by rnd.nextInt() to a non-negative int by calling Math.abs().
    • If nextInt() returns Integer.MIN_VALUE, Math.abs() will also return Integer.MIN_VALUE, and the remainder operator will also return a negative number, assuming n is not a power of 2.
public static void main(String[] args) {
	int n = 2 * (Integer.MAX_VALUE / 3);
	int low = 0;
	for(int i = 0; i < 1000000; i++) 
		if(random(n) < n/2) 
			low++:
	System.out.println(low);		
}
  • The solution is Random.nextInt(int).
  • As of Java 7, you should no longer use Random.
    • For most uses, the random number generator of choice is now ThreadLocalRandom.
    • It produces higher quality random numbers, and it's very fast.
       
  • A second advantage of using standard libraries is that you don't have to waste your time writing ad hoc solutions to problems that are only marginally related to your work.
  • A third advantage of using standard libraries is that their performance tends to improve over time, with no effort on your part.
  • A fourth advantage of using libraries is that they tend to gain functionality over time.
    • If the library is missing something, the developer community will make it known, and the missing functionality may get added in subsequent release.
  • A final advantage of using the standard libraries is that you place your code in the mainstream.
    • Such code is more easily readable, maintainable, and reusable by the multitude of developers.

Item 60: Avoid double and float if exact answers are required

The float and double types are designed primarily for scientific and engineering calculations.

  • They perform binary floating-point arithmetic, which was carefully designed to furnish accurate approximations quickly over a broad range of magnitudes.
  • They do not, however, provide exact results and should not be used where exact results are required.

The float and double types are particularly ill-suited for monetary calculations.

  • For example, suppose you have $1.03 in your pocket, and you spend 42c.
  • How much money do you have left?
  • Unfortunately, it prints out 0.6100000000000001.
System.out.println(1.03 - 0.42);
  • Suppose you have a dollar in your pocket, and you buy nine washers priced at ten cents each.
  • How many change do you get?
  • According to this program fragment, you get $0.09999999999999998.
System.out.println(1.00 - 9 * 0.10);
  • Suppose you have a dollar in your pocket, and you see a shelf with a row of delicious candies priced at 10c, 20c, 30c, and so forth.
  • You buy one of each candy, starting with the one that costs 10c, until you can't afford to buy the next candy on the shelf.
  • How many candies do you buy, and how much change do you get?
  • You'll find that you can afford 3 pieces of candy, and you have $0.3999999999999999 left.
public static void main(String[] args) {
	double funds = 1.00;
	int itemsBought = 0;
	for(double price = 0.10; funds>=price; price+=0.10) {
		funds -= price;
		itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}
  • Solution using BigDecimal:
public static void main(String[] args) {
	final BigDecimal TEN_CENTS = new BigDecimal(".10");
	int itemsBought = 0;
	BigDecimal funds = new BigDecimal("1.00");
	for(BigDecimal price = TEN_CENTS; 
	funds.compareTo(price) >= 0; 
	price = price.add(TEN_CENTS)) {
		funds = funds.subtract(price);
		itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
Disadvantages of BigDecimal
  • It is a lot less convenient than using a primitive arithmetic type.
  • It is a lot slower.

Item 61: Prefer Primitive Types to Boxed Primitives

Every primitive type has a corresponding reference type, called a boxed primitive, called a boxed primitive.

  • The boxed primitives corresponding to int, double, and boolean are Integer, Double, and Boolean.
  • First: Two boxed primitives instances can have the same value and different identities.
  • Second: Primitive types have only fully functional values, whereas each boxed primitive has one nonfunctional value,which is null, in addition to all the functional values of the corresponding primitive type.
  • Last: Primitives are more time-and space-efficient than boxed primitives.

Applying the == operator to boxed primitives is almost always wrong.

Applying the < operator causes the Integer instances to be auto-unboxed.

Wrong Comparator

enter image description here

Correct Comparator

enter image description here

Comparison 1

enter image description here

  • i is an Integer, not an int, and like all nonconstant object reference fields, its initial value is null.

In nearly every case when you mix primitives and boxed primitives in an operation, the boxed primitive is auto-unbocked.

  • If a null object reference is auto-unboxed, you get a NullPointerException.

Comparison 2

enter image description here

  • This program is much slower than it should be because it accidentally declares a local variable (sum) to be the boxed primitive type Long instead of the primitive type long*.
  • The program compiles without error or warning, and the variable is repeatedly boxed and unboxed, causing the observed performance degradation.

Uses of boxed primtivies
  1. As elements, keys, and values in collections.
    • You can't put primitives in collections, so you're forced to use boxed primitives.
  2. You must use boxed primitives as type parameters in parameterized types and methods, because the language does not permit you to use primitives.
    • For example, you cannot declare a variable to be of type ThreadLocal, so you must use ThreadLocal<Integer> instead.

Item 62: Avoid strings Where Other Types Are More Appropriate

Because strings are so common and so well supported by the language, there is a natural tendency to use strings for purposes other than those for which they were designed.

  • Strings are poor substitutes for other value types.
  • When a piece of data comes into a program from a file, from the network, or from keyboard input, it is often used in String form.
  • There is natural tendency to leave it that way, but this tendency is justified only if the data really is textual in nature.
     
  • Numeric data: should be translated into the appropriate numeric type, such as int, float, or BigInteger.
  • Answer to a yes-no question data: should be translated into an appropriate enum type or a boolean.
     
  • String are poor substitutes for enum types.
    • Enums make far better enumerated type constants than strings.
  • Strings are poor substitutes for aggregate types.
    • If an entity has multiple components, it is usually a bad idea to represent it as a single string.
       

enter image description here

  • If the character used to separate fields occurs in one of the fields, chaos may result.
  • To access individual fields, you have to parse the string, which is slow, tedious, and error-prone.
  • A better approach is simply to write a class to represent the aggregate, often a private static member class.

Item 63: Beware the Performance of String Concatenation

  • It is fine for generating a single line of output or constructing the string representation of a small, fixed size object, but it doesn't scale.

Using the string concatenation operator repeatedly to concatenate n strings requires time quadratic in n.

  • This is an unfortunate consequence of the fact that strings are immutable.

String concatenation vs StringBuilder

String concatenation
// Inappropriate use of string concatenation - performs poorly!
public String statement() {
	String result = "";
	for(int i = 0; i < numItems(); i++) {
		result += lineForItem(i);
}
	return result;
}
  • The method performs abysmally if the number of items is large.

StringBuilder
public String statement() {
   StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
   for(int i = 0; i< numItems(); i++) {
   	b.append(lineForItem(i));
}
   return b;
}
  • A lot of work has gone into making string concatenation faster since Java 6.
  • If new newItems returns 100 and lineForItem returns an 80-character string.
    • The second method runs 6.5 times faster than the first one.
    • The second is linear.
    • Note that the second method preallocates a StringBuilder large enough to hold the entire result, eliminating the need for automatic growth.
    • Even if it is detuned to use a default-sized StringBuilder, it is still 5.5 times faster.

Item 64: Refer to Objects by their Interfaces

If an appropriate interface types exist, then parameters, return values, variables, and fields should all be declared using interface type.

  • Get in the habit of writing this:
Set<Son> sonSet = new LinkedHashSet<>();
  • Not this:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
  • If you get into the habit of using interfaces as types, your program will be much more flexible.
    • If you want to switch implementations, all you have to do is to change the class name in the constructor (or use a different static factory).
Set<Son> sonSet = new HashSet<>();
  • There is one caveat:
    • If the original implementation offered some special functionality not required by the original contract of the interface and the code depended on that functionality, then it is critical that the new implementation provides the same functionality.
    • LinkedHashSet --> HashSet change loses the original ordering policy.
  • Why would you want to change an implementation type?
    • The second implementation offers better performance than the original, or because it offers desirable functionality that the original implementation lacks.
    • HashMap --> EnumMap change provides better performance and iteration order consistent with the natural order of the keys, but you can only use an EnumMap if the key is an enum type.
       

Case 1

  • If an object belongs to such a class-based framework, it is preferable to refer to it by the relevant base class which is often abstract, rather than by its implementation class.
  • Many java.io classes such as OutputStream fall into this category.

Case 2

  • A final case in which there is no appropriate interface type is that of classes that implement but also provide extra methods not found in the interface.
  • For example, PriorityQueue has a comparator method that is not present on the Queue interface.
  • Such a class should be used to refer to its instances only if the program relies on the extra methods, and this should be very rare.

Item 65: Prefer Interfaces to Reflection

The core reflection facility, java.lang.reflect, offers programmatic access to arbitrary classes.

  • Given a Class object, you can obtain Constructor, Method, and Field instances representing the constructors, methods, and fields of the class represented by the Class instance.
  1. These objects provide programmatic access to the class's member names, field types, method signatures, and so on.
  2. Moreover, Constructor, Method, and Field instances let you manipulate their underlying counterparts reflectively:
    • You can construct instances.
    • Invoke methods.
    • Access fields. by invoking methods on the Constructor, Method, and Field instances.

This power, however, comes at a price.

  • You lose all the benefits of compile-time type checking, including exception checking.
    • If a program attempts to invoke a non existent or inaccessible method reflectively, it will fail at runtime unless you've taken special precautions.
  • The code required to perform reflective access is clumsy and verbose.
    • It is tedious to write and difficult to read.
  • Performance suffers.
    • Reflective method invocation is much slower than normal method invocation.

Example

public static void main(String[]) {
// Translate the class name into a Class object.
Class<? extends Set<String>> cl = null;
try {
	// Unchecked class.
	cl = (Class<? extends Set<String>>) Class.forName(args[0]);
}
catch(ClassNotFoundException e) {
	fatalError("Class not found.");
}

// Get the constructor.
Constructor<? extends Set<String>> cons = null;
try {
	cons = cl.getDeclaredConstructor();
}
catch (NoSuchMethodException e) { 
fatalError("No parameterless constructor"); 
}

// Instantiate the set.
Set<String> s = null;
try{
s = cl.newInstance();
}
catch(IllegalAccessException e) {
	fatalError("Constructor not accessible");
}
catch(InstantiationException e) {
	fatalError("Class not instantiable");
}
catch(InvocationTargetException e) {
fatalErorr("Constructor threw " + e.getCause());
}
catch(ClassCastException e) {
	fatalError("Class does not implement Set");
}
// Exercise the set
s.addAll(Arrays.asList(args).subList(1, args.length));
}
private static void fatalError(String msg) {
	System.err.println(msg);
	System.exit(1);
}
  • This toy program could easily be turned into a generic set tester that validates the specified Set implementation by aggressively manipulating one or more instances and checking that they obey the Set contract.

Disadvantages
  1. The example can generate six different exception at runtime, all of which would have been compile-time errors if reflective instantiation were not used.
  2. It takes 25 lines of tedious code to generate an instance of a class from it name.

  • If you compile this program, you'll get an unchecked cast warning.
  • This warning is legitimate, in that the cast to Class<? extends Set<String>> will succeed even if the named class is not a Set implementation, in which case the program will throw a ClassCastException when it instantiates the class.
⚠️ **GitHub.com Fallback** ⚠️