Effective Java summary By Doo and Cheolho

Effective Java 3/E summary item 6 to 10 (Longer version)

You can find the shorter version of this summary here.

Item 6: Avoid creating unnecessary objects

  • An object can always be reused if it is immutable.
    • We can avoid creating unnecessary objects by using static factory methods - constructor is required to create a new object while static factory methods is not.
  • Even if an object is mutable, it can be reused if all mutable fields won’t be modified.
  • Be careful with “expensive” objects. For example, using String.matches in a loop could greatly reduce performance since it internally creates an expensive Pattern instance.
    • Lazy initialization could also help, but this complicates the implementation. Measure the improvement before and after the optimization.
  • Adapters or views can always reused since its only state is the backing object. (ex: every call to Map#keySet returns a functionally identical object)
  • Prefer primitives to boxed primitives and be careful with the unintentional autoboxing.

Item 7: Eliminate obsolete object references

  • Define variables in the narrowest possible scope.
  • Whenever a class manages its own memory, the programmer should be alert for memory leaks.
  • Caches are source of the leak too.
    • Using WeakHashMap may help. But use this class only when the lifetime of the entry depends on the entry’s key not the value.
    • Using LinkedHashMap deletes old entries so this could also be helpful.
  • Listeners and other callbacks are sources of a leak too. Maintaining only a weak reference to them can help.

Item 8: Avoid finalizers and cleaners

  • Finalizers are unpredictable, often dangerous, and generally unnecessary.
  • The Java 9 replacement for finalizers is cleaners. Cleaners are less dangerous than finalizers, but still unpredictable, slow, and generally unnecessary.
  • There is no guarantee that finalizers or cleaners will run at all.
  • There is a severe performance penalty for using finalizers and cleaners.
  • Finalizers have a serious security problem: they open your class up to finalizer attacks.
  • Just have your class implement AutoCloseable, and require its clients to invoke the close method on each instance when it is no longer needed.

Item 9: Prefer try-with-resources to try-finally

  • When a second exception occurs during handling a first exception occurred in a nested try-finally clauses, the first exception is hidden. Use try-with-resources can resolve this issue.

Item 10: Obey the general contract when overriding equals

You can find the shorter version of this summary here.

It is when a class has a notion of logical equality that differs from mere object identity, it is appropriate to override equals.

We don’t have to implement equals if:

  • Each instance of the class is inherently unique. (e.g. Thread)
  • There is no need for the class to provide a “logical equality” test. (e.g. Pattern)
  • There is no need for the class to provide a “logical equality” test.
  • The class is private or package-private, and you are certain that its equals method will never be invoked.

The equals method implements an equivalence relation. It has these properties:

  • Reflexive: For any non-null reference value x, x.equals(x) must return true.
  • Symmetric: For any non-null reference values x and y, x.equals(y) must return true if and only if y.equals(x) returns true.
  • Transitive: For any non-null reference values x, y, z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) must return true.
  • Consistent: For any non-null reference values x and y, multiple invocations of x.equals(y) must consistently return true or consistently return false, provided no information used in equals comparisons is modified.
  • For any non-null reference value x, x.equals(null) must return false.

Notes:

  • There is no way to extend an instantiable class and add a value component while preserving the equals contract,
  • Do not break the Liskov subsitution principle: any important property of a type should also hold for all its subtypes so that any method written for the type should work equally well on its subtypes
  • Whether or not a class is immutable, do not write an equals method that depends on unreliable resources. (Consistency)
  • Always override hashCode when you override equals.
  • Don’t try to be too clever. It is generally a bad idea to take any form of aliasing into account. For example, the File class shouldn’t attempt to equate symbolic links referring to the same file.

Effective Java 3/E summary chanpter 3 (Shorter version)

Chanpter 6 : Lambda and Stream

You can find the longer version of this summary here.

  • Java 8, Android API 24 or higher

Lambda

  • Use Lambda instaed of an anonymous class
  • Anonymous class < Lambda < Method reference
    • If the readability seems to be poor, use Lambda instaed of Method reference
  • Standard functional interface (java.util.function)
    • 43 in total : 6 basic types can be extended
      • UnaryOperator : 1 argument, argument type = return type
      • BinaryOperator : 2 arguments, argument type = return type
      • Predicate : 1 argument, return booelan
      • Function : 1 argument, argument type != return type
      • Supplier : Returns a value without arguments
      • Consumer : 1 argument, return void
      • Transform 3 pieces each for int, long, and double
      • 9 transformations from Function to SrcToRes
      • transformation that takes 2 arguments (Predicate, Function, Consumer)
      • 2 arguments + return basic type Fuction
      • 1 argument + argument basic type Consumer
      • Supplier that returns Boolean

Stream

  • Replace loop (for, while)
  • Change the loop to Stream and consider readability
  • Cannot be changed to Stream when
    • local variables need to be modified
    • return, break, continue, exception is implemented
  • Use java.util.stream.Collectors methods for reducing side-effects
    • toList, toSet, toMap, groupingBy, joining
  • Best return type is Collection!
    • Collection can be both (Iterable, Stream).
    • Tremendous sequence can be replaced to AbstractCollecion.
  • For parallel(), consider whether (the number of elements * the number of implemented codes > Hundreds of thousands)

Effective Java 3/E summary chanpter 3 (Shorter version)

Chanpter 5 : Enum and Annotation

You can find the longer version of this summary here.

Enum

  • Enum : instance control (declared as public static final), singleton, type stability
  • Method and field can be added to enum type. -> Receives data from the constructor and stores it in the instance (adds a value when creating each enum field)
public enum Operation {
    PLUS("+") {
        double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        double apply(double x, double y) {
            return x / y;
        }
    };
    private final String symbol;

    Operation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
    public abstract double apply(double x, double y);
}
  • Use EnumSet instead of bit fields
  • Use EnumMap instead of ordinal indexing
  • Emulate extensible enums with interfaces

Precautions for Enum

  • Prohibit use of ordinal(). Instead, it is marked as an instance field

Annotation

  • Used for an explicit purpose rather than a function.
  • Always use @Override when overriding
  • If you need a type definition functionality, use the marker interface.

Effective Java 3/E summary chanpter 3 (Shorter version)

Chanpter 4 : Generic

You can find the longer version of this summary here.

When to use Generics?

  • If something is expressed as Object, use Generics.
  • If there is a class or method in which type conversion occurs, use Generics. (For methods)
  • When using Generics, the type is specified at compile time and the client doesn’t need type-casting.
  • When using a container of a variable type, consider using a type-safe heterogeneous container with Class<T> as the type token.

Characteristics of Generics

  • Invariant : Generic classes declared as different types are of different types.
  • Type check at compile time.
  • Type information is erased at runtime.

Precautions for Generics

  • Do not use the raw type because it exists due to compatibility with existing Java (it is difficult to find a bug at compile time)
  • Do not use Generics with arrays
    • Generics don’t reify, whereas arrays reify. (The array type is unknown at runtime)
  • Since variable arguments are arrays, make sure to be type-safe to use with Generics and attach @SafeVarargs.

Use of Wildcards

  • Using wildcards makes the public API much more flexible. (Client doesn’t need type declaration)
  • If type parameter appears only once in the method declaration, replace it with a wildcard.
  • Recursive type limitation ```java

<E extends Comparable> // Any type E can be compared. <T extends Builder> // Any type T implement Builder. ```

  • PECS: producer-extends, consumer-super

Effective Java 3/E summary chanpter 3 (Shorter version)

Chanpter 3 : Class and Interface

You can find the longer version of this summary here.

Class principles

Minimize the possibility of change (if there is no obvious reason, follow the rules below)

  • Do not provide methods to change the state of an object (Setter)
  • Non-extensible final class
  • All fields private final
  • Perform defensive copying in constructor, accessor, and readObject methods

Class design pattern

  1. Does this class need inheritance?
    • NO, just implement the class by following the rules above.
  • Yes, Favor composition over inheritance. Follow the steps below.

Steps to create an inheritable class

  1. Build a skeleton through the interface.
  2. Create a delivery class. (Wrapper class)

Note : Doesn’t need to implement wrapper class in Kotlin. Kotlin provides delegate keyword.

public class ForwardingSet<E> implements Set<E> { 
    private final Set<E> s; 

    public Forwarding(Set<E> s) { this.s = s; }

    public void clear() { s.clear(); } 
    public boolean contains(Object o) { return s.contains(o); } 
    public boolean isEmpty() { return s.isEmpty(); }
    public int size() { return s.size(); } 
    public Iterator<E> iterator() { return s.iterator(); } 
    public boolean add(E e) { return s.add(e); } 
    public boolean remove(Object o) { return s.remove(o); }
    public boolean containsAll(Collection<?> c) { return s.containsAll(c); } 
    public boolean addAll(Collection<? extends E> c) { return s.addAll(c); } 
    public boolean removeAll(Collection<?> c) { return s.removeAll(c); } 
    public boolean retainAll(Collection<?> c) { return s.retainsAll(c); } 
    public Object[] toArray() { return s.toArray(); } 
    public <T> T[] toArray(T[] a) { return s.toArray(a); } 
    @Override public boolean equals(Object o) { return s.equals(o); } 
    @Override public int hashCode() { return s.hashCode(); } 
    @Override public String toString() { return s.toString(); } 
}
  1. Define only the required methods by inheriting the delivery class.
public class InstrumentedSet<E> extends ForwardingSet<E> { 
    private int addCount = 0; 

    public InstrumentedSet(Set<E> s) { super(s); } 
    
    @Override public boolean add(E e) { 
        addCount++; 
        return super.add(e); 
    } 
    @Override public boolean addAll(Collection<? extends E> c) { 
        addCount += c.size(); 
        return super.addAll(c); 
    } 
    public int getAddCount() { return addCount; } 
}

Reasons for prohibiting inheritance

  • Broken encapsulation (parent affects children)
  • Multiple inheritance not possible
  • Equals cannot be implemented

Interface

  • The purpose of the interface is to tell the client what the instance can do.
  • Interface is difficult to modify, so design creating a new interface.
  • Use default methods when creating a new interface. (Java 8 or higher)

Use of class

  • Tagged classes are used in hierarchical structure by using abstract class.
  • Implement as static as possible by preventing member classes from referencing outer classes.
  • Limit source files to a single top-level class!