28 Dec 2020
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.
28 Dec 2020
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)
28 Dec 2020
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.
28 Dec 2020
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
28 Dec 2020
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
- 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
- Build a skeleton through the interface.
- 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(); }
}
- 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!