명불허전 이펙티브 자바. 허세 부리려고 원서로 읽었는데 너무 오래걸렸습니다. 천천히 읽게 되어서 오히려 기억에 많이 남는 것 같지만, 필요할 때 되돌아볼 수 있게 기억할만한 부분만 발췌했습니다. 이미 한 번 읽어보신 분들만 도움이 될 정도로만....

Creating and Destroying Objects
Methods Common to All Objects
Classes and Interfaces
Generics
Enums and Annotations
Lambdas and Streams

Item 1: Consider static factory methods instead of constructors

  • static factory method의 장점(= 생성자와의 차이)
    1. 이름이 있다.
    2. 호출되더라도 객체 생성을 하지 않을 수 있다.
    3. subtype 객체를 리턴할 수 있다.
    4. input parameter에 따라 다양한 subtype 객체를 리턴할 수 있다.
    5. 리턴할 객체가 존재하지 않아도 작성할 수 있다.
      • A fifth advantage of static factories is that the class of the returned object need not exist when the class containing the method is written. Such flexible static factory methods from the basis of service provider frameworks, like the Java Database Connectivity API (JDBC). A service provider framework is a system in which providers implement a service, and the system makes the implementations available to clients, decoupling the clients from the implementations.
  • 한계
    1. static factory method만 있고 public or protected 생성자가 없는 객체는 상속이 불가능해진다.
      1. 상속대신 합성Composition을 강제한다는 점에서 장점으로 작용할 수 있음.
    2. static factory method를 객체 사용자가 찾기 어렵다. 생성자와 다르게 Javadoc에서 static factory method를 특별취급하지 않기 때문. 아래 naming convention을 지키면 그나마 찾기 쉽다.
      • from - A type-conversion method
      • of - An aggreation method
      • valueOf - from, of의 역할을 모두 할 수 있음.
      • create or newInstance - 호출할 때마다 매번 다른 객체를 리턴함.
      • instance or getInstance - create, newInstance 와 같지만 호출할 때마다 매번 다른 객체를 리턴한다는 보장은 없음.
      • newType - newInstance와 동일하지만 static factory method가 리턴하는 타입이 이 메서드를 가지고 있는 타입이 아닐 때.
        • e.g.) BufferedReader br = Files.newBufferedReader(path);
      • getType - getInstance와 동일하지만 static factory method가 리턴하는 타입이 이 메서드를 가지고 있는 타입이 아닐 때.
        • e.g.) FileStore fs = Files.getFileStore(path);
      • type - getTypenewType의 alternative.
        • e.g.) List<Complaint> litany = Collections.list(legacyLitany);

Item 2: Consider a builder when faced with many constructor parameters

유명해서 생략... lombok의 @Builder 참조. 책에는 Generic Builder 예제가 있다.

  • 장점
    • 객체를 immutable하게 생성 가능.
    • 생성자에 동일한 타입의 파라미터가 있을 때 발생할 수 있는 실수를 원천적으로 방지.

Item 3: Enforce the singleton property with a private constructor or an enum type

제목이 곧 내용. 여기에 더해서 static field를 private으로 선언하고 public static Type getInstance()등의 메서드를 통해 실제 field명을 알 수 없게 보호할 수 있다. Reflection 공격을 방어할 수 있음.

더 강력한 방법은 singleton 객체를 enum을 사용해서 만드는 것. 언어차원에서 singleton을 보장한다.

Item 4: Enforce noninstantiability with a private constructor

이것도 마찬가지로 제목이 곧 내용. 그러나 private 생성자를 사용하는 것만으로는 reflection을 통한 객체 생성을 막을 수 없으므로 생성자에서 throw new AssertionError(); 로 에러가 발생하게 만들면 완벽하다.

Item 5: Prefer dependency injection to handwiring resources

의존성 주입은 정말 유명하니... 책 ⟨오브젝트⟩의 8장: 의존성 관리하기 참조.

Item 6: Avoid creating unnecessary objects

  • new String("bikini");처럼 String객체의 생성자를 부르는 짓 하지 말기...

  • 정규표현식 객체를 static하게 생성해서 재사용하기. [String#matches(String regex)](https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#matches(java.lang.String)) 를 호출하는 것보단 Pattern 객체를 생성해서 재사용하자.

  • boxed primitives를 사용하면 불필요한 객체를 생성하게 되는 경우가 있음. boxed대신 그냥 primitives를 선호하자

    public static long sum() {
      Long sum = 0L; // boxed primitives
      for (long i = 0; i<= Integer.MAX_VALUE; i++)
        sum += i; // jesus...
    
      return sum;
    }

Item 7: Eliminate obsolete object references

  • Whenever a class manages its own memory, the programmer should be alert for memory leaks.
  • Another common source of memorrry leaks is caches.
  • A third common source of memory leaks is listeners and other callbacks.

Item 8: Avoid finalizers and cleaners

  • finalizer는 Java 9 에서 Deprecated.
  • cleaners는 finalizers봐 안전하지만 역시 최대한 쓰지 않는게 좋다.
  • finalizer를 사용하면 finalizer atttacks 공격에 당할 수 있다.
  • 대신 클래스가 AutoCloseable 인터페이스를 구현하고, 객체 사용이 끝나면 close 메서드를 부르는 방식을 사용하면 좋다. 이 경우 try-with-resources 구문을 사용해 자동으로 close 메서드가 불리도록 할 수 있음.

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

제목이 곧 내용... SonarLint에서 경고해준다. IDE 플러그인 적극 사용 권장.

Item 10: Obey the general contract when overriding equals

아래 케이스중 하나라도 걸리면 equals 메서드를 override할 필요가 없다. 꼭 필요한 곳에만 equals를 override해야 한다.

  • Each instance of the class is inherently unique.
  • There is no ned for the class to provide a "logical equality" test.
  • A superclass has already overridden equals, and the superclass behavior is appropriate for this class.
    • e.g.) Set#equals implemented in AbstractSet. Also List and AbstractList, Map and AbstractMap.
  • The class is private or package-private, and you are certain that its equals method will never be invoked.

만약 override한다면 아래의 equivalence relation을 만족해야 함.

  • Reflexive: x.equals(x) must return true.
  • Symmetric: if x.equals(y) == true then y.equals(x) == true
  • Transitive: if x.equals(y) == true && y.equals(z) == true then x.equals(z) == true
  • Consistent: xy의 값이 변하지 않는 한 x.equals(y)는 동일한 값(true or false)를 리턴한다.
  • x.equals(null) == false

보통 Reflexive는 어기기가 더 어렵고.. equals method를 작성한 뒤 Symmetric, Transitive, Consistent를 모두 만족하는지 스스로에게 질문하자.

  • There is no way to extend an instantiable class and add a value component while preserving the equals contract, unless you're willing to forgo the benefits of object-oriented abstraction.
    • 상속 대신 합성Composition 사용하면 해결할 수 있음

속편하게 lombok의 @EqualsAndHashCode를 애용하자.

Item 11: Always override hashCode when you override equals

IntelliJ에서 자주 봤던 경고문의 내용. 역시 lombok의 [@EqualsAndHashCode](https://projectlombok.org/features/EqualsAndHashCode)를 애용하자.

Item 12: Always override toString

  • Providing a good toString implementation makes your class much more pleasant to use and makes systems using the class easier to debug.
  • When practical, the toString method should return all of the interesting information contained in the object.
  • Whether or not you decide to specify the format, you should clearly document your intentions.
  • Whenther or not you speicfy the format, provide programmatic access to the information contained in the value returned by toString.
  • You should write a toString method in any abstract class whose subclasses sharee a common string representation.

Item 13: Override clone judiciously

Cloneable인터페이스의 사용법은 일반적이지 않다. 쓰지 말고 copy constructor나 copy factory를 사용하자.

  • So what does Cloneable do, given that it contains no methods? It determines the behavior of Object's clone method returrns a field-by-field copy of the object; otherwise it throws CloneNotSupportedException. This is a highly atypical use of interfaces and not one to be emulated. Norrmally, implementing an interface says something about what a class can do for its clients. In this case, it modified the behavior of a protected method on a superclass.
  • Though the specification doesn't say it, in practice, a class implementing Cloneable is expected to provide a properly functioning public clone method. In order to achieve this, the class and all of its superclasses must obeey a complex, unenforceable, thinly documented protocol. The resulting mechanism is fragile, dangerous, and extralinguistic: it creates objects without calling a constructor.
  • Immutable classes should never povide a clone method because it would merely encourage wasteful copying.
  • Like serialization, the Cloneable architecture is incompatible with normal use of final fields referring to mutable objects, except in cases where the mutable objects may be safely shared between an obejct and its clone. In order to make a class cloneable, it may be necessary to remove final modifiers from some fields.
  • A better approach to obejct copying is to provide a copy constructor or copy factory.
// Copy constructor
public Yum(Yum yum) { ... };

// Copy factory
public static Yum newInstance(Yum yum) { ... };
  • Given all the problems associated with Cloneable, new interfaces should not extend it, and new extendable classes should not implement it.

Item 14: Consider implementing Comparable

동일한 객체에 대해서 compareTo 메서드는 0을 리턴해야 함. 그렇지 않아도 정렬은 잘 되지만, HashMapTreeMap의 동작이 달라질 수 있음. HashMapequals도 객체의 동일성을 검사하고 TreMapcompareTo로 동일성을 검사하기 때문이다.

직접 >, <같은 비교 연산자를 사용하지 말고 Double.compare, Long.compare등 boxed primitive의 static method를 사용하는 것이 좋다.

Java 8에서 추가된 comparator construction methods 를 쓰면 비교 메서드를 간결하게 구현할 수 있다.

  • In Java 8, the Comparator interface was outfitted with a set of comparator construction methods, which enable fluent construction of comparators.
// Comparable with comparator construction methods
private static final Comparator<PhoneNumber> COMPARATOR =
    comparingInt((PhooneNumber pn) -> pn.areaCode)
      .thenComparingInt(pn -> pn.prefix)
      .thenComparingInt(pn -> pn.lineNum);

public int compareTo(PhoneNumber pn) {
    return COMPARATOR.compare(this, pn)
}

Item 15: Minimize the accessibility of classes and members

접근 지정자에 대해서 많이 잘못 알고 있는 사실 중 하나는 default 접근 지정자(package private)가 protected보다 폐쇄적이라는 것이다.

  • private - The member is accessible only from the top-level class where it is declared.
  • package-private - The member is accessible from any class in the package where it is declared. Technically known as default access, this is the access level you get if no access modifier is specified (except for interface members, which are public by default).
  • protected - The member is accessible from subclasses of the class where it is declared (subject to few restrictions) and from any class in the package where it is declared.
  • public - The member is accessible from anywhere.

변수를 선언할 때 final 키워드를 사용해도, 변수가 mutable한 객체를 가리키고 있다면 final은 의미가 없다.

  • It is critical that thses fields contain either primitive values or references to immutable objects(Item 17). A field containing a reference to a mutable object has all the disadvantages of a nonfinal field.
  • Note that a nonzero-length array is always mutable, so it is wrong for a class to have a public static final array field, or an accessor that returns such a field.
// Potential security hole!
public static final Thing[] VALUES = { ... };

// Alternative approach 1
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
  Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

// Alternative approach 2
private static final Thing[] PRIVATE_VALUES = { ... };
****public static final Thing[] values() {
  return PRIVATE_VALUES.clone();
}

Item 16: In public classes, use accessor methods, not public fields

  • if a class is accessible outside its package, provide accessor methods to preserve the flexibility to change the class's internal representation (even for final fields).
  • if a class is a package-private or is a private nested class, there is nothing inherently wrong with exposing its data fields.

Item 17: Minimize mutability

  1. Don't provide methods that modify the object's state (known as mutators).
  2. Ensure that the class can't be extended.
  3. Make all fields final.
  4. Make all fields private.
  5. Ensure exclusive access to any mutable components.
  • Note that the method names are prepositions (such as plus) rather than verbs (such as add). This emphasizes the fact that methods don't change the values of the objects.
  • If you write a class whose security depends on the immutability of a BigInteger or BigDecimal argument from an untrusted client, you must check to see that the argument is a "real" BigInteger or BigDecimal, rather than an instance of an untrusted subclass. If it is latter, you must defensively copy it under the assumption that it might be mutable (Item 50):
public static BigInteger safeInstance(BigInteger val) {
  return val.getClass() == BigIntegr.class ?
    val : new BigInteger(val.toByteArray());
}

Item 18: Favor composition over inheritance

  • To summarize, inheritance is powerful, but it is problematic because it violates encapsulation. It is appropriate only when a genuine subtype relationship exists between the subclass and the superclass. Even then, inheritance may lead to fragility if the subclass is in different package from the superclass and the superclass is not designed for inheritance. To avoid this fragility, use composition and forwarding instead of inheritance, especially if an appropriate interface to implement a wrapper class exists. Not only are wrapper classes more robust than subclasses, they are also more powerful.

⟨오브젝트⟩의 11장: 합성과 유연한 설계 참고.

  • 상속 쓰지 마라.
  • 상속 쓰려면 Template method pattern으로만 사용하기.

Item 19: Design and document for inheritance or else prohibit it.

  • The only way to test a class designed for inheritance is to write subclasses.
    • Experience shows that three subclasses are usually sufficient to test and extendable class.
    • One or more of these subclasses should be written by someone other than the superclass author.
  • Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result.
  • Designing a class for inheritance requires great effort and places substantial limitations on the class.
  • The best solution to this problem is to prohibit subclassing in classes that are not designed and documented to be safely subclassed.

상속용 클래스를 만들려면 설계부터 탄탄해야 하고 문서화도 철저히 해야 한다. 상속받는 클래스도 문서화를 자세히 해야 함. 자식 클래스에 영향을 주는 부모 클래스의 구현 디테일까지 자식 클래스에 문서화해야 한다.

Item 20: Prefer interfaces to abstract classes

진리다..

  • By convention, skeletal implementation classes are called AbstractInterface, where Interface is the name of the interface they implement. For example, the Collections Framework provides a skeletal implementation to go along wth each main collection interface: AbstractCollection, AbstractSet, AbstractList, and AbstractMap.
  • To summarize, an interface is generally the best way to define a type that permits multiple implementations. If you export a nontrivial interface, you should strongly consider providing a skeletal implementation to go with it. To the extent possible, you should provide the skeletal implementation via default methods on the interface so that all implementators of the interface can make use of it. That said, restrictions on inteerfaces typically mandate that a skeletal implementation take the form of an abstract class.

Item 21: Design interfaces for posterity

  • It is not always possible to write a default method that maintains all invariants of every concivable implementation.
  • In the presence of default methods, existing implementations of an interface may compile without error or warning but fail at runtime.
  • Using default methods to add new methods to existing interfaces should be avoided unless the need is critical, in which case you should think long and hard about whether an existing interface implementation might be broken by your default method implementation. Default methods are, however, extremely useful for providing standard method implementations when an interface is created, to ease the task of implementing the interface (Item 20).
  • While it may be possible to correct some interface flaws after an interface is released, you cannot count on it.

Item 22: Use interfaces only to define types

  • The constant interface pattern is a poor use of interfaces.

Item 23: Prefer class hierarchies to taggd classes

// Tagged class - vastly inferior to a class hierarchy!
class Figure {
  enum Shapre { RECTANGLE, CIRCLE };
  ...
}
  • Tagged classes are verbose, error-prone, and ineffecient.
  • A tagged class is just a pallid imitation of a class hierarchy.
// Class hierarchy replacement for a tagged class
abstract class Figure {
  abstract double area();
}

class Circle extends Figure {
  ...
  @Override double area() { ... }
}

class Rectagle extends Figure {
  ...
  @Override double area() { ... }
}

Item 24: Favor static member classes over nonstatic

  • If you declare a member class that does not require access to an enclosing instance, always put the static modifier in its declaration, making it a static rather than a nonstatic member class.
  • Anonymous classes have enclosing instances if and only if they occur in a nonstatic context.

Item 25: Limit source files to a single top-level class

  • The lesson is clear: Never put multiple top-level clases or interfaces in a single source file. Following this rule guarantees that you can't have multiple definitions for a single class at compile time.

Item 26: Don't use raw types

  • If you use raw types, you lose all the safety and expressiveness benefits of generics.
  • You lose safety if you use a raw type such as List, but not if you use a parameterized type such as List<Object>.
  • For quick reference, the terms introduced in this item (and a few introduced later in this chapter) are summarized in the following table:
Term Example Item
Parameterizeed type List<String> Item 26
Actual type parameter String Item 26
Generic type List<E> Item 26, 29
Formal type parameter E Item 26
Unbounded wildcard type List<?> Item 26
Raw type List Item 26
Bounded type parameter <E extnds Number> Item 29
Recursive type bound <T extends Comparable<T>> Item 30
Bounded wildcard type List<? extends Number> Item 31
Generic method static <E> List<E> as List(E[] a) Item 30
Type token String.class Item 33

Item 27: Eliminate unchecked warnings

  • Eliminate every unchecked warning that you can.
  • If you can't eliminate a warning, but you can prove that the code that provoked the warning is typesafe, then (and only then) suppress the warning with an @SuppressWarnings("unchecked") annotation.
  • You might be tempted to put the annotation on the entire method, but don't. Instead, declare a local variable to hold the return value and annotate its declaration.
  • Every time you use a @SuppressWarnings("unchecked") annotation, add a comment saying why it is safe to do so.

Item 28: Prefer lists to arrays

  • In summary, arrays and generics have very different type rules. Arrays are covariant and reified; gnerics are invariant and erased. As a consequence, arrays provide runtime type safety but not compile-time type safety, and vice versa for generics. As a rule, arrays and generics don't mix well. If you find yourself mixing them and getting compile-time errors or warnings, your first impulse should be to replace the arrays with lists.

Item 29: Favor generic types

Stack을 가지고 설명. Stack대신 Stack<E>를 사용해서 typesafe한 Stack 만들기.

스택 내부에서 배열을 사용하는데, 배열과 generic을 같이 사용하게되면 필연적으로 @SuppressWarnings("unchecked")를 붙이게 된다.

  • In summary, generic types are safer and easier to use than types that require casts in client code. When you design new types, make sure that they can be used without such casts. This will often mean making the types generic. If you have any existsing types that should be generic but aren't, generify them. This will make lif easier for new users of these types without breaking existing clients (Item 26).

Item 30: Favor generic methods

  • The type parameter list, which declares the type parameters, goes between a method's modifiers and its return type.
// Generic method
public static **<E>** Set<E> union(Set<E> s1, Set<E> s2) {
  Set<E> result = new HashSet<>(s1);
  result.addAll(s2);
  return result;
}
  • The type bound <E extends Comparable<E>> may be read as "any type E that can be compared to itself", which corresponds more or less precisely to the notion of mutual comparability.

Item 31: Use bounded wildcards to increase API flexibility

  • PECS stands for producer- extends, consumer-super.
// Wildcard type for a parameter that serves as an E producer
// src는 임의의 타입 E 혹은 E를 상속받은 타입(서브타입)을 담을 수 있는 자료구조여야 한다.
public void pushAll(Iterable<? extends E> src) {
  for (E e : src)
    push(e);
}

// Wildcard type for a paramter that serves as an E consumer
// dst는 임의의 타입 E 혹은 E의 수퍼타입을 담을 수 있는 자료구조여야 한다.
public void popAll(Collection<? super E> dst) {
  while (!isEmpty())
    dst.add(pop());
}
  • Do not use bounded wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code.
  • If the user of a class has to think about wildcard types, there is probably something wrong with its API.

아래는 책 오브젝트의 부록 A에 실려있는 공변성, 반공변성, 무공변성에 대한 내용

먼저 S가 T의 서브타입이라고 하자. 이 때 프로그램의 어떤 위치에서 두 타입 사이의 치환 가능성을 다음과 같이 나눠볼 수 있다.

  • 공변성(covariant): S와 T 사이의 서브타입 관계가 그대로 유지된다. 이 경우 해당 위치에서 서브타입인 S가 슈퍼타입인 T대신 사용될 수 있다. 우리가 흔히 이야기하는 리스코프 치환 원칙은 공변성과 관련된 원칙이라고 생각하면 된다.
    • 리턴 타입 공변성: 메서드를 구현한 클래스의 타입 계층 방향과 리턴 타입의 타입 계층 방향이 동일한 경우
      • 슈퍼타입 대신 서브타입을 반환한다 → 더 강력한 사후조건을 정의한다. 문제 없음.
  • 반공변성(contravariant): S와 T 사이의 서브타입 관계가 역전된다. 이 경우 해당 위치에서 슈퍼타입인 T가 서브타입인 S대신 사용될 수 있다.
    • 파라미터 타입 반공변성: 부모 클래스에서 구현된 메서드를 자식 클래스에서 오버라이딩할 때 파라미터 타입을 부모 클래스에서 사용한 파라미터의 슈퍼타입으로 지정할 수 있는 특성
      • 서브타입 대신 슈퍼타입을 파라미터로 받는다 → 더 약한 사후조건사전조건을 정의한다. 문제 없음.
  • 무공변성(invariant): S와 T 사이에는 아무런 관계도 존재하지 않는다. 따라서 S대신 T를 사용하거나 T 대신 S를 사용할 수 없다.

공변성과 반공변성의 지원 여부는 언어에 따라 다르다.

  • 자바는 리턴 타입 공변성을 지원하지만 C#은 리턴 타입 공변성을 지원하지 않는다.
  • 자바는 파라미터 타입 반공변성을 지원하지 않는다.

위의 PECS 코드에서

  • producer는 임의의 타입 E를 상속받는 객체를 담을 수 있는 자료구조를 받아야 한다.
    • producer는 값을 제공하는 '서버'의 입장. 사후조건은 강화될 수만 있고 약화될 수 없다 → 임의의 타입 E 또는 E의 서브타입을 제공해야 한다.
    • 리턴 타입 공변성과 동일함.
  • consumer는 임의의 타입 E의 슈퍼타입 객체가 담겨져 있는 자료구조를 받아야 한다.
    • consumer는 값을 사용하는 '클라이언트'의 입장. 사전조건은 약화될 수만 있고 강화될 수 없다 → 임의의 타입 E또는 E의 수퍼타입을 받아야 한다.
    • 파라미터 타입 반공변성과 동일함.

Item 32: Combine generics and varargs judiciously

  • In summary, varargs and generics do not interact well because the varargs facility is a leaky abstraction built atop arrays, and arrays have dirrefernt type rules from generics. Though generic varargs parameters are not typesafe, they are legal. If you choose to write a method with a generic (or parameterized) varargs parameter, first ensure that the method is typesafe, and the annotate it with @SafeVarargs so it is not unpleasant to use.

Item 33: Consider typesafe heterogeneous containers

  • System.out.printf등 print format method에서 \n 대신 %n 사용하기.
  • In summary, the normal use of generics, exemplified by the collections APIs, restricts you to a fixed number of type parameters per container. You can get around this restriction by placing the type parameter on the key rather than the container. You can use Class objects as keys for such typesafe heterogeneous containers. A class object used in this fashion is called a type token. You can also us a custom key type. For example, you could have a DatabaseRow type representing a database row (the container), and a generic type Column<T> as its key.

Item 34: Use enum instead of int constants

// Enum type with constantpspecific class bodies and data
public enum Operation {
  PLUS("+") {
    public double apply(double x, double y) { return x + y; }
  }
  MINUS("-") {
    public double apply(double x, double y) { return x - y; }
  }
  TIMES("*") {
    public double apply(double x, double y) { return x * y; }
  }
  DIVIDE("/") {
    public double apply(double x, double y) { return x / y; }
  }

  private fianl String symbol;

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

  @Override public String toString() { return symbol; }

  public abstract double apply(double x, double y);
}
  • Use enums any time you need a set of constants whose members are known at compile time.
  • It is not necessary that the set of constants in an enum type stay fixed for all time. The enum feature was specifically designed to allow for binary compatible evolution of enum types.
  • In summary, the advantages of enum types over int constants are compelling. Enums are more readable, safer, and more powerful. Many enums require no explicit constructors or members, but other benefit from associating data with each constant and providing methods whose behavior is affected by this data. Fewer enums benefit from associating multiple behaviors with a single method. In this relatively rare case, prefer constant-specific methods to enums that switch on their own values. Consider the strategy enum pattern if some, but not all, enum constants share common behaviors.

Item 35: Use instance fields instead of ordinals

  • The Enum specification has this to say about ordinal: "Most programmers will have no use for this method. It is designed for use by general-purpose enum-based data structures such as EnumSet and EnumMap." Unless you are writing code with this character, you are best off avoiding the ordinal method entirely.

Item 36: Use EnumSet instead of bit fields

  • Each EnumSet is represented as a bit vector. If the underlying enum type has sixty-four or fewer elements - and most do - the entire EnumSet is represented with a single long, so its performance is comparable to that of a bit field.
  • In summary, just because an enumerated type will be used in sets, there is no reason to represent it with bit fields. The EnumSet class combines the conciseness and performance of bit fields with all the many advantages of enum types described in Item 34. The one real disadvantage of EnumSet is that it is not, as of Java 9, possible to create an immutable EnumSet, but this will likely be remedied in an upcoming release. In the meantime, you can wrap an EnumSet with Collections.unmodifiableSet, but conciseness and performance will suffer.

Item 37: Use EnumMap instead of ordinal indexing

Here is nested Enum example. For details read the book.

  • In summary, it is rarely appropriate to use ordinals to index into arrays: use EnumMap instead. If the relationship you are repressenting is multidimensional, use EnumMap<..., EnumMap<...>>. This is a special cases of the general principle that application programmers should rarely, if ever, use Enum.ordinal (Item 35).

Item 38: Emulate extensible enums with interfaces

// Emulated extensible enum using an interface
public interface Operation {
  double apply(double x, double y);
}

public enum BasicOperation implements Operation {
  ...
}
  • In summary, while you cannot write an extensible enum type, you can emulate it by writing an interface to accompany a basic enum type that implements the interface. This allows clients to write their own enums (or other types) that implement the interface. Instances of these types can then be used whenever instances of the basic enum type can be used, assuming APIs are written in terms of the interface.

Item 39: Prefer annotations to naming patterns

  • There is simply no reason to use naming patterns when you can use annotations instead. That said, with the exception of toolsmiths, most programmers will have no need to define annotation types. But all programmers should use the predefined annotation types that Java provides (Itms 40, 27). Also, consider using the annotations provided by your IDE or static analysis tools. Such annotations can improve the quality of the diagnostic information provided by these tools. Note, however, that these annotations have yet to be standardized, so you may have some work to do if you switch tools or if a standard emerges.

Item 40: Consistently use the Override annotation

  • In summary, the compiler can protect you from a great many errors if you use the Override annotation on every method declaration that you believe to override a supertype declaration, with one exception. In concrete classes, you need not annotate methods that you believe to override abstract method declarations (though it is not harmful to do so).

Item 41: Use marker interfaces to define types.

  • A marker interface is an interface that contains no method declarations but merely designates (or "marks") a class that implements the interface as having some property.
  • Marker interfaces define a type that is implemented by instances of the marked class; marker annotations do not.
  • Another advantage of marker interfaces over marker annotations is that they can be targeted more precisely.
  • The chief advantage of marker annotations over marker interfaces is that they are part of the larger annotation facility.
  • Marker interfaces and marker annotations both have their uses. If you want to define a type that does not have any new methods associated with it, a marker interface is the way to go. If you want to mark program elements other than classes and interfaces or to fit the marker into a framework that already makes heavy use of annotation types, then a marker annotation is the correct choice. If you find yourself writing a marker annotation type whose target is ElementType.TYPE, take the time to figure out whether it really should be an annotation type or whether a marker interface would be more appropriate.

Item 42: Prefer lambdas to anonymous classes

  • Historically, interfaces (or, rarely, abstract classes) with a single abstract method were used as functional types.
// Enum with function object fields & constant-sspecific behavior
public enum Operation {
  PLUS  ("+", (x, y) -> x + y),
  MINUS ("-", (x, y) -> x - y),
  TIMES ("*", (x, y) -> x * y),
  DIVIDE("/", (x, y) -> x / y),

  private final String symbol;
  private final DoubleBinaryOperator op;

  Operation(String symbol, DoubleBinaryOperator op) {
    this.symbol = symbol;
    this.op = op;
  }

  @Override public String toString() {
    return symbol;
  }

  public double apply(double x, double y) {
    return op.applyAsDouble(x, y);
  }
}
  • Unlike methods and classes, lambdas lack names and documentation; if a computation isn't self-explanatory, or exceeds a few lines, don't put it in a lambda. One line is sideal for a lambda, and three line is a reasonable maximum. If you violate this rule, it can cause serious harm to the readability of your programs. If a lambda is long or difficult to read, either find a way to simplify it or refactor your program to eliminate it. Also, the arguments passed to enum constructors are evaluated in a static context. Thus, lambdas in enum constructors can't access instance members of the enum. Constant-specific class bodies are still the way to go if an enum type has constant-specific behavior that is difficult to understand, that can't be implemented in a few lines, or that requires access to instance fields or methods.
  • Likewise, you might think that anonymous classes are obsolete in the era of lambdas. This is closer to the truth, but there are a few things you can do with anonymous classes that you can't do with lambdas. Lambdas are limited to functional interfaces. If you want to create an instance of an abstract class, you can do it with an anonymous class, but not a lambda. Similarly, you can use anonymous classes to create instances of interfaces with multiple abstract methods. Finally, a lambda cannot obtain a reference to itself. In a lambda, the this keyword refers to the enclosing instance, which is typically what you want. In anonymous class, the this keyword refers to the anonymous class instance. If you need access to the function object from within its body, then you must use an anonymous class.
  • Lambdas share with anonymous classes the property that you can't reliably erialize and deserialize them across implementations. Therefore, you should rarely, if ever, serialize a lambda (or an anonymous class instance). If you have a function object that you want to make serializable, such as a Comparator, use an instance of a private static nested classs (Item 24).
  • In summary, as of Java 8, lambdas are by far the best way to represent small function objects. Don't use anonymous classes for function objects unless you have to create instances of types that aren't functional interfaces. Also, remember that lambdas make it so easy to represent small function objects that it opens the door to functional programming techniques that were not previously practical in Java.

Item 43: Prefer method reference to lambdas

Method Ref Type Example Lambda Equivalent
Static Integer::parseInt str -> Integer.praseInt(tr)
Bound Instant.now()::isAfter Instant then = Instant.now(); t -> then.isAfter(t)
Unbound String::toLowerCase str -> str.toLowerCase()
Class Constructor TreeMap<K,V>::new () -> new TreeMap<K,V>()
Array Constructor int[]::new len -> new int[len]
  • In summary, method references often provide a more succinct alternative to lambdas. Where method references are shorter and clearer, use them; where they aren't, stick with lambads

Item 44: Favor the use of standard functional interfaces

  • If one of the standard functional interfaces does the job, you should generally use it in preference to a purpose-built functional interface.
Interface Function Signature Example
UnaryOperator<T> T apply (T t) String::toLowerCase
BinaryOperator<T> T apply(T t1, T t2) BigInteger:add
Predicate<T> boolean test(T t) Collection::isEmpty
Function<T,R> R apply(T t) Arrays::asList
Supplier<T> T get() Instant::now
Consumer<T> void accept(T t) System.out::println
  • Most of the standard functional interfaces exist only to provide support for primitive types. Don't be tempted to use basic functional interfaces with boxed primitives instead of primitive functional interfaces. While it works, it violates the advice of Item 61, "prefer primitive types to boxed primitives." The performance consequences of using boxed primitives for bulk operations can be deadly.
  • Always annotate your functional interfaces with the @FunctionalInterface annotation.
  • It is generally best to use the standard interfaces provided in java.util.function.Function, but keep your eyes open for the relatively rare cases where you would be better off writing your own functional interface.

Item 45: Use streams judiciously

  • Stream pipelines are evaluated lazily: evaluation doesn't start until the terminal operation is invoked, and data elements that aren't required in order to complete the terminal operation are never computed. This lazy evaluation is what without a terminal operation is a silent no-op, so don't forget to include one.
  • In summary, some tasks are best accomplished with streams, and others with iteration. Many tasks are best accomplished by combining the two approaches. There are no hard and fast rules for choosing which approach to use for a task, but there are some useful heuristics. In many cases, it will be clear which approach to use; in some cases, it won't. If you're not sure whether a task is better served by streams or iteration, try both and see which works better.

Item 46: Prefer side-effect-free functions in streams

Side effect는 terminal operation에서만 허용하자. 여기서도 side effect가 없으면 더 좋음.

  • The forEach operation should be used only to report the result of a stream computation, not to perform the computation.
  • In summary, the essence of programming stream pipelines is side-effect-free function objects. This applies to all of the many function objects passed to streams and related objects. The terminal operation forEach should only be used to report the result of a computation performed by a stream, not to perform the computation. In order to use streams properly, you have to know about collectors. The most important collector factories are toList, toSet, toMap, groupingBy, and joining.

Item 47: Prefer Collection to Stream as a return type

StreamIterable을 상속하지 않기 때문.

  • If, in a future Java release, the Stream interface declaration is modified to extend Iterable, then you should feel free to return streams because they will allow for both stream processing and iteration.

Item 48: Use caution when making streams parallel

  • The moral of this story is simple: Do not parallelize stream pipelines indiscriminately. The performance consequences may be disastrous.
  • As a rule, performance gains from parallelism are best on streams over ArrayList, HashMap, HashSet, and ConcurrentHashMap instances; arays; int ranges; and long ranges.
  • Not only can parallelizing a stream lead to poor performance, including liveness failures; it can lead to incorrect resultss and unpredictable behavior (safety failures).
  • It's important to remember that parallelizing a stream is strictly a peformance optimization. AS is the case for any optimization, you must test the performance before and after the change to ensure that it is worth doing(Item 67). Ideally, you should perform the test in a realistic system setting. Normally, all parallel stream pipelines in a program run in a common fork-join pool. A single misbehaving pipeline can harm the performance of others in unrelated parts of the system.

If it sounds like the odds are stacked against you when parallelizing stream pipelines, it's because they are. An acquaintance who maintains a multimillion-line codebase that makes heavy use of streams found only a handful of places where parallel streams were effective. This does not mean that you should refrain from parallelizing streams. Under the right circumstances, it is possible to achieve near-linear speedup in the number of processor cores simply by adding a parallel call to s stream pipeline. Certain domains, such as machine learning and data processing, are particularly amenable to those speedups.

  • If you believe that parallelism may be justified, ensure that your code remains correct when run in parallel, and do careful performance measurements under realistic conditions. If your code remains correct and these experiments bar out your suspicion of increased performance, then and only then parallelize the stream in production code.