명불허전 이펙티브 자바. 허세 부리려고 원서로 읽었는데 너무 오래걸렸습니다. 천천히 읽게 되어서 오히려 기억에 많이 남는 것 같지만, 필요할 때 되돌아볼 수 있게 기억할만한 부분만 발췌했습니다. 이미 한 번 읽어보신 분들만 도움이 될 정도로만....
- Item 1: Consider static factory methods instead of constructors
- Item 2: Consider a builder when faced with many constructor parameters
- Item 3: Enforce the singleton property with a private constructor or an enum type
- Item 4: Enforce noninstantiability with a private constructor
- Item 5: Prefer dependency injection to handwiring resources
- Item 6: Avoid creating unnecessary objects
- Item 7: Eliminate obsolete object references
- Item 8: Avoid finalizers and cleaners
- Item 9: Prefer
try-with-resources
totry-finally
- Item 10: Obey the general contract when overriding
equals
- Item 11: Always override
hashCode
when you overrideequals
- Item 12: Always override
toString
- Item 13: Override
clone
judiciously - Item 14: Consider implementing
Comparable
- Item 15: Minimize the accessibility of classes and members
- Item 16: In public classes, use accessor methods, not public fields
- Item 17: Minimize mutability
- Item 18: Favor composition over inheritance
- Item 19: Design and document for inheritance or else prohibit it.
- Item 20: Prefer interfaces to abstract classes
- Item 21: Design interfaces for posterity
- Item 22: Use interfaces only to define types
- Item 23: Prefer class hierarchies to taggd classes
- Item 24: Favor static member classes over nonstatic
- Item 25: Limit source files to a single top-level class
- Item 26: Don't use raw types
- Item 27: Eliminate unchecked warnings
- Item 28: Prefer lists to arrays
- Item 29: Favor generic types
- Item 30: Favor generic methods
- Item 31: Use bounded wildcards to increase API flexibility
- Item 32: Combine generics and varargs judiciously
- Item 33: Consider typesafe heterogeneous containers
- Item 34: Use enum instead of
int
constants - Item 35: Use instance fields instead of ordinals
- Item 36: Use
EnumSet
instead of bit fields - Item 37: Use
EnumMap
instead of ordinal indexing - Item 38: Emulate extensible enums with interfaces
- Item 39: Prefer annotations to naming patterns
- Item 40: Consistently use the
Override
annotation - Item 41: Use marker interfaces to define types.
- Item 42: Prefer lambdas to anonymous classes
- Item 43: Prefer method reference to lambdas
- Item 44: Favor the use of standard functional interfaces
- Item 45: Use streams judiciously
- Item 46: Prefer side-effect-free functions in streams
- Item 47: Prefer Collection to Stream as a return type
- Item 48: Use caution when making streams parallel
- static factory method의 장점(= 생성자와의 차이)
- 이름이 있다.
- 호출되더라도 객체 생성을 하지 않을 수 있다.
- subtype 객체를 리턴할 수 있다.
- input parameter에 따라 다양한 subtype 객체를 리턴할 수 있다.
- 리턴할 객체가 존재하지 않아도 작성할 수 있다.
- 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.
- 한계
- static factory method만 있고 public or protected 생성자가 없는 객체는 상속이 불가능해진다.
- 상속대신 합성Composition을 강제한다는 점에서 장점으로 작용할 수 있음.
- static factory method를 객체 사용자가 찾기 어렵다. 생성자와 다르게 Javadoc에서 static factory method를 특별취급하지 않기 때문. 아래 naming convention을 지키면 그나마 찾기 쉽다.
from
- A type-conversion methodof
- An aggreation methodvalueOf
-from
,of
의 역할을 모두 할 수 있음.create
ornewInstance
- 호출할 때마다 매번 다른 객체를 리턴함.instance
orgetInstance
-create
,newInstance
와 같지만 호출할 때마다 매번 다른 객체를 리턴한다는 보장은 없음.newType
-newInstance
와 동일하지만 static factory method가 리턴하는 타입이 이 메서드를 가지고 있는 타입이 아닐 때.- e.g.)
BufferedReader br = Files.newBufferedReader(path);
- e.g.)
getType
-getInstance
와 동일하지만 static factory method가 리턴하는 타입이 이 메서드를 가지고 있는 타입이 아닐 때.- e.g.)
FileStore fs = Files.getFileStore(path);
- e.g.)
type
-getType
과newType
의 alternative.- e.g.)
List<Complaint> litany = Collections.list(legacyLitany);
- e.g.)
- static factory method만 있고 public or protected 생성자가 없는 객체는 상속이 불가능해진다.
유명해서 생략... lombok의 @Builder 참조. 책에는 Generic Builder 예제가 있다.
- 장점
- 객체를 immutable하게 생성 가능.
- 생성자에 동일한 타입의 파라미터가 있을 때 발생할 수 있는 실수를 원천적으로 방지.
제목이 곧 내용. 여기에 더해서 static field를 private으로 선언하고 public static Type getInstance()
등의 메서드를 통해 실제 field명을 알 수 없게 보호할 수 있다. Reflection 공격을 방어할 수 있음.
더 강력한 방법은 singleton 객체를 enum을 사용해서 만드는 것. 언어차원에서 singleton을 보장한다.
이것도 마찬가지로 제목이 곧 내용. 그러나 private 생성자를 사용하는 것만으로는 reflection을 통한 객체 생성을 막을 수 없으므로 생성자에서 throw new AssertionError();
로 에러가 발생하게 만들면 완벽하다.
의존성 주입은 정말 유명하니... 책 ⟨오브젝트⟩의 8장: 의존성 관리하기 참조.
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; }
- 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.
- finalizer는 Java 9 에서 Deprecated.
- cleaners는 finalizers봐 안전하지만 역시 최대한 쓰지 않는게 좋다.
- finalizer를 사용하면 finalizer atttacks 공격에 당할 수 있다.
- 대신 클래스가
AutoCloseable
인터페이스를 구현하고, 객체 사용이 끝나면close
메서드를 부르는 방식을 사용하면 좋다. 이 경우try-with-resources
구문을 사용해 자동으로close
메서드가 불리도록 할 수 있음.
제목이 곧 내용... SonarLint에서 경고해준다. IDE 플러그인 적극 사용 권장.
아래 케이스중 하나라도 걸리면 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 inAbstractSet
. AlsoList
andAbstractList
,Map
andAbstractMap
.
- e.g.)
- 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
theny.equals(x) == true
- Transitive: if
x.equals(y) == true && y.equals(z) == true
thenx.equals(z) == true
- Consistent:
x
와y
의 값이 변하지 않는 한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
를 애용하자.
IntelliJ에서 자주 봤던 경고문의 내용. 역시 lombok의 [@EqualsAndHashCode](https://projectlombok.org/features/EqualsAndHashCode)
를 애용하자.
- 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.
Cloneable
인터페이스의 사용법은 일반적이지 않다. 쓰지 말고 copy constructor나 copy factory를 사용하자.
- So what does
Cloneable
do, given that it contains no methods? It determines the behavior ofObject
'sclone
method returrns a field-by-field copy of the object; otherwise it throwsCloneNotSupportedException
. 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 publicclone
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.
동일한 객체에 대해서 compareTo
메서드는 0을 리턴해야 함. 그렇지 않아도 정렬은 잘 되지만, HashMap
과 TreeMap
의 동작이 달라질 수 있음. HashMap
은 equals
도 객체의 동일성을 검사하고 TreMap
은 compareTo
로 동일성을 검사하기 때문이다.
직접 >
, <
같은 비교 연산자를 사용하지 말고 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)
}
접근 지정자에 대해서 많이 잘못 알고 있는 사실 중 하나는 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();
}
- 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.
- Don't provide methods that modify the object's state (known as mutators).
- Ensure that the class can't be extended.
- Make all fields final.
- Make all fields private.
- Ensure exclusive access to any mutable components.
- Note that the method names are prepositions (such as
plus
) rather than verbs (such asadd
). 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
orBigDecimal
argument from an untrusted client, you must check to see that the argument is a "real"BigInteger
orBigDecimal
, 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());
}
- 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으로만 사용하기.
- 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.
상속용 클래스를 만들려면 설계부터 탄탄해야 하고 문서화도 철저히 해야 한다. 상속받는 클래스도 문서화를 자세히 해야 함. 자식 클래스에 영향을 주는 부모 클래스의 구현 디테일까지 자식 클래스에 문서화해야 한다.
진리다..
- 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
, andAbstractMap
. - 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.
- 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.
- The constant interface pattern is a poor use of interfaces.
// 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() { ... }
}
- 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.
- 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.
- 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 asList<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 |
- 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.
- 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.
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).
- 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 typeE
that can be compared to itself", which corresponds more or less precisely to the notion of mutual comparability.
- 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의 수퍼타입을 받아야 한다.
- 파라미터 타입 반공변성과 동일함.
- 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.
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 aDatabaseRow
type representing a database row (the container), and a generic typeColumn<T>
as its key.
// 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.
- The
Enum
specification has this to say aboutordinal
: "Most programmers will have no use for this method. It is designed for use by general-purpose enum-based data structures such asEnumSet
andEnumMap
." Unless you are writing code with this character, you are best off avoiding theordinal
method entirely.
- Each
EnumSet
is represented as a bit vector. If the underlying enum type has sixty-four or fewer elements - and most do - the entireEnumSet
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 ofEnumSet
is that it is not, as of Java 9, possible to create an immutableEnumSet
, but this will likely be remedied in an upcoming release. In the meantime, you can wrap anEnumSet
withCollections.unmodifiableSet
, but conciseness and performance will suffer.
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, useEnum.ordinal
(Item 35).
// 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.
- 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.
- 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).
- 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.
- 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, thethis
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.
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
- 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.
- 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.
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 aretoList
,toSet
,toMap
,groupingBy
, andjoining
.
Stream
이 Iterable
을 상속하지 않기 때문.
- If, in a future Java release, the
Stream
interface declaration is modified to extendIterable
, then you should feel free to return streams because they will allow for both stream processing and iteration.
- 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
, andConcurrentHashMap
instances; arays;int
ranges; andlong
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.