diff --git a/Item10_14.md b/Item10_14.md new file mode 100644 index 00000000..cd62ccea --- /dev/null +++ b/Item10_14.md @@ -0,0 +1,351 @@ +## Item 10. equals 는 일반 규약을 지켜 재정의(override)하라 + +- overriding equals 를 권장하지 않는 경우 + +> 1. inherently unique 한 경우 : 각 instance 가 value 가 아닌 activity 에 의미가 있는 경우 (Thread) +> 2. instance 간의 logical equality 를 검사할 필요가 없다 : Object.equals (물리적으로 같은 instance 일때 equal 로 판단) +> 3. 이 class 의 superclass 에 정의된 equals가 활용가능(적합)하다 : Set, Map 의 구현체들 +> 4. package-private, private class 들 : equals 를 호출할 일이 없는 경우 + +- 그럼 언제 재정의 하라는 거? + +> logical equality 검사가 필요한 데, 이 class 의 superclass.equals 에 적절하게 구현되어 있지 않은 경우 +``` +i.e) List.contains(x) 시 x class 내부 필드 중 unique 기준을 일부로 선별하고 싶을 때 + +value class 라 할지라도 동일 value 를 가진 instance 가 1개인 것이 보장된다면 override 불필요(Enum, String 등) +``` + +- equals method 의 규약 (specification for 'Object') + +``` +for any non-null x, y, z +``` +> 1. Reflexive : x.equals(x) +> 2. Symmetric : if x.equals(y) than y.equals(x) +> 3. Transitive : if x.equals(y) and y.equals(z) than x.equals(z) +> 4. Consistent : x.equals(y) must consistently return true or consistently return false +> 5. x.equals(null) == false + + +- symmetry 를 어기는 경우 + +``` Java +// Broken - violates symmetry! +// CaseInsensitiveString 을 String 과 억지로 호환 시키려는 시도가 문제를 불러옴 +public final class CaseInsensitiveString { + private final String s; + ... + @Override public boolean equals(Object o) { + if (o instanceof CaseInsensitiveString) + return s.equalsIgnoreCase( + ((CaseInsensitiveString) o).s); + if (o instanceof String) // One-way interoperability! + return s.equalsIgnoreCase((String) o); + return false; + } + ... +} +``` +**equals 규약을 어길 시, collection 등 당 규약 기반으로 구현된 자료구조 코드에서 jdk 버젼 마다 같은 결과임을 보장할 수 없다.** + +- Transitivity 를 어기는 경우 + +``` Java +public class ColorPoint extends Point { // Point(int x, int y) + private final Color color; // enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET } + ... + // Broken - violates transitivity! (page 42) + @Override public boolean equals(Object o) { + if (!(o instanceof Point)) + return false; + + // If o is a normal Point, do a color-blind comparison + if (!(o instanceof ColorPoint)) + return o.equals(this); // Point 내부의 x, y만 비교 + + // o is a ColorPoint; do a full comparison + return super.equals(o) && ((ColorPoint) o).color == color; + } + ... +} +``` + +- 일반적인 OOP 의 상속 구조에서 LSP(자식 클래스는 최소한 자신의 부모 클래스에서 가능한 행위는 수행할 수 있어야 한다.) 를 만족하며 equals 를 구현할 방법은 없음. +- 상속보다 컴포지션(Point, Color 를 member field로)을 사용하는 방법으로 해결이 가능하다. + +``` Java +public class ColorPoint { + private final Point point; + private final Color color; + ... + + @Override public boolean equals(Object o) { + if (!(o instanceof ColorPoint)) + return false; + ColorPoint cp = (ColorPoint) o; + return cp.point.equals(point) && cp.color.equals(color); + } + ... +``` + +- equals 의 판단시 'unreliable resources' 를 사용하지 말 것 (memory 상의 deterministic 한 값이 아닌 외부의 값들) +``` +이를테면 api 등 외부통신을 통해 결정되는 값 등 +``` + +- x.equals(null) 의 경우 false 가 반환되게 명시적으로 코드 작업 (NPE 가 발생 하지 않도록) +- instanceof 를 활용하면 자연스럽게 가능하다. +``` Java +@Override public boolean equals(Object o) { + if (!(o instanceof MyType)) // o=null 이면 false 가 반환됨. 즉 if (o == null) 같은 코드 불필요 + return false; + MyType mt = (MyType) o; + ... +} +``` + +- 즉 정리하면 equals 의 양질 구현법은 +> 1. == 로 동일 object reference 인지 선검사(성능향상) +> 2. instanceof 로 올바른 비교대상 class 인지 검사 +> 3. 비교대상 class 로 casting +> 4. input 객체의 핵심 필드들이 모두 일치 하는지 확인 +> 5. float, double 은 '근사값이므로' == 말고 Float.compare(x,y), Double.compare(x,y) 로 비교 + +- 결론 : 어지간하면 equals 재구현할 필요 없다. 아니면 IDE기능이나 AutoValue 써라. 정 손으로 구현하고 싶다면 상기 규약들에 대한 test case를 꼼꼼하게 짜둬라. + + +## Item 11. equals 재정의(override) 시 hashCode 도 재정의하라 + +- hashCode method 의 규약 (specification for 'Object') + +> 1. hashCode 는 동일 어플리케이션 안에서, equals 에 영향을 주는 내부 필드가 변경되지 않는 한 동일 값을 반환한다. +> 2. if x.equals(y) than x.hashCode() == y.hashCode() +> 3. Not reqiured to x.hashCode() != y.hashCode() even if x.equals(y) == false (but recommended for performance of hash tables) +``` Java +// 서로 다른 value 가 hash collision 발생시, linked list 등으로 연결되어 put/get 성능에 영향( > O(1) )을 끼칠 수 있다. +// 2번 규약의 경우, 지켜지지 않을 시 Hashing 을 활용하는 자료구조에서 문제가 발생한다. +Map m = new HashMap<>(); +m.put(new PhoneNumber(707, 867, 5309), "Jenny"); + +m.get(new PhoneNumber(707, 867, 5309); // null +``` + +- hashCode 구현의 형태 +``` Java +// 전통(?)적인 hashCode 구현 +// 31은 홀수+소수이고 31 * i = (i << 5) - i 처럼 VM내부 시프트 최적화에 좋아서 쓴다고 한다. +// 소수를 쓰는 이유는 bucketize 시 분배가 잘 되는 값이라서라고 한다. +@Override public int hashCode() { + int result = Short.hashCode(areaCode); + result = 31 * result + Short.hashCode(prefix); + result = 31 * result + Short.hashCode(lineNum); + return result; +} + +// java 7+ 에 제공되는 hash. 성능이 아쉽다고 책에 나와잇으나 실제로 jdk 소스 보면 위쪽 코드와 비슷하다. +@Override public int hashCode() { + return Objects.hash(lineNum, prefix, areaCode); // Arrays.hashCode(values); +} +// Objects.hash(Arrays.hashCode) 내부 구현 +public static int hashCode(Object a[]) { + if (a == null) + return 0; + int result = 1; + for (Object element : a) + result = 31 * result + (element == null ? 0 : element.hashCode()); + return result; +} + + +// lazy initialization +private int hashCode; // Automatically initialized to 0 +@Override public int hashCode() { + int result = hashCode; + if (result == 0) { + result = Short.hashCode(areaCode); + result = 31 * result + Short.hashCode(prefix); + result = 31 * result + Short.hashCode(lineNum); + hashCode = result; + } + return result; +} +``` + +- toString() 이 구현되지 않앗다면, stringfy(println, log 등) 시 'class_fullname@hashCode' 포맷으로 출력된다. +``` +// hashCode 가 미구현 되어 잇다면 Object.hashCode() 값이 @ 뒤에 나온다. +effectivejava.chapter3.item11.PhoneNumber@adbbd +effectivejava.chapter3.item11.PhoneNumber@b0147 +... +``` + +**hashCode 에 활용될 필드는, 결과 hash 가 높은 cardinality 를 가지도록 잘 선별해야 성능에 유리하다** +**hashCode 출력 값을 클라이언트가 직접 의존하는 것은 안좋다. 필드 추가/삭제에 영향이 크기 때문** + + +## Item 12. toString 을 항상 재정의(override)하라 + +- toString() 은 logging 시 개발자의 디버깅 효율을 높혀주는 데 유리하게 활용될 수 잇다. +``` Java +System.out.println("Failed to connect to " + phoneNumber); // Failed to connect to PhoneNumber@163b91 + +// PhoneNumber class +@Override public String toString() { + return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum); +} + +System.out.println("Failed to connect to " + phoneNumber); // Failed to connect to 010-1577-1577 + +``` + +- 주요 필드(해당 value 를 대표할 수 잇는 필드)의 경우 toString 에 추가해 주는 것이 좋다. +``` +value class 라면 equals 조건에 쓰이는 필드 전부, list class 라면 목록의 개수 등 +``` + +- toString() 결과에 대해 '고정 포맷'을 제공하는 것인지, 임의 변경 가능한 것인지 명확히 전달해줄 것 (클라가 멋대로 파싱해서 쓰지 않도록) +- toString() 에 표시되는 필드에 대해서는 전부 getter 를 제공 하는 것을 권장 +- AutoValue? Lombok? : + - https://codeburst.io/lombok-autovalue-and-immutables-or-how-to-write-less-and-better-code-returns-2b2e9273f877 + - https://medium.com/@vgonzalo/dont-use-lombok-672418daa819 + + + +## Item 13. clone 재정의(override)는 주의하여 진행하라 + + +- clone method 의 규약 (specification for 'Object') (허술하다고 한다) + +> 1. x.clone() != x +> 2. x.clone().getClass() == x.getClass() +> 3. x.clone().equals(x) +> 4. 관례상 super.clone 의 결과를 반환하는 것으로 한다. (상속시 하위 클래스에서 문제가 발생하지 않도록) + +``` Java +@Override public PhoneNumber clone() { + try { + return (PhoneNumber) super.clone(); // protected native Object clone() throws CloneNotSupportedException; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); // Can't happen + } +} + +// via Object.clone() +... +* the corresponding fields of this object, as if by assignment; the +* contents of the fields are not themselves cloned. Thus, this method +* performs a "shallow copy" of this object, not a "deep copy" operation. +... + +public static void main(String[] args) { + PhoneNumber pn = new PhoneNumber(707, 867, 5309); + Map m = new HashMap<>(); + m.put(pn, "Jenny"); + System.out.println(m.get(pn.clone())); +} +``` +- Object.clone() 의 copy 방법은 기본적으로 **shallow copy** 이다. +``` Java +// field 중 object reference 형태인 값은 reference 만 복사됨. +// 즉 xPrime = x.clone() 후 xPrime 의 해당 필드내부를 수정하면 x 에도 영향이 감 +// 마찬가지로 내부에 element collection 을 보유하는 경우 deep copy 를 원한다면 명시적 구현이 필요함 +public class Stack implements Cloneable { + private Object[] elements; + private int size = 0; + private static final int DEFAULT_INITIAL_CAPACITY = 16; + ... + @Override public Stack clone() { + try { + Stack result = (Stack) super.clone(); + result.elements = elements.clone(); // array 자체 내부에도 clone() 이 존재한다고 함. 2983 ms / 100Mil elements + // 성능 궁금해서 한번 비교해봄. 2848 ms / 100Mil elements + //result.elements = Arrays.copyOf(elements, elements.length); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + ... +} + +// Java LinkedList 의 clone() 구현 : shallow copy 이므로 완전분리 복제를 예상하고 사용하면 위험함. +/** + * Returns a shallow copy of this {@code LinkedList}. (The elements + * themselves are not cloned.) + * + * @return a shallow copy of this {@code LinkedList} instance + */ +public Object clone() { + LinkedList clone = superClone(); + + // Put clone into "virgin" state + clone.first = clone.last = null; + clone.size = 0; + clone.modCount = 0; + + // Initialize clone with our elements + for (Node x = first; x != null; x = x.next) + clone.add(x.item); + + return clone; +} +``` + +- 잠정 결론 : clone() 재구현을 할거면 정확히 내부 복제 매커니즘을 이해하고 해라. 어설프게 하면 피본다. + +## Item 14. Comparable 을 구현할지 고려하라 + +- Object 의 필수 specification 이 아니므로 custom class 에서 활용을 위해서는 구현이 필요 +- Comparable 가 구현된 class 는 natural order 를 가진다고 볼 수 있다. +- sort operation 또는 sorting 이 기반이 되는 collection 자료구조에서 활용됨 + +``` +for all x, y, z +``` +> 1. sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) +> 2. if (x.compareTo(y) > 0 && y.compareTo(z) > 0) than x.compareTo(z) > 0. +> 3. if x.compareTo(y) == 0 than sgn(x.compareTo(z)) == sgn(y.compareTo(z)) +> 4. (optional but strongly recommended) (x.compareTo(y) == 0) == (x.equals(y)) + +- 일반적인 compare 부호 순서 +``` Java +// Java 7+ 부터는 각 자료형의 compare 메서드를 활용하여 구현하는 것을 추천 +// Integer.compare +public static int compare(int x, int y) { + return (x < y) ? -1 : ((x == y) ? 0 : 1); +} + +``` + +- compareTo 구현 예시 + +``` Java +// 전통적 구현 방식 +public int compareTo(PhoneNumber pn) { + int result = Short.compare(areaCode, pn.areaCode); + if (result == 0) { + result = Short.compare(prefix, pn.prefix); + if (result == 0) + result = Short.compare(lineNum, pn.lineNum); + } + return result; +} + +// comparingInt 활용 +private static final Comparator COMPARATOR = + comparingInt((PhoneNumber pn) -> pn.areaCode) + .thenComparingInt(pn -> pn.prefix) + .thenComparingInt(pn -> pn.lineNum); +public int compareTo(PhoneNumber pn) { + return COMPARATOR.compare(this, pn); +} + +// 유의 - integer overflow 발생 가능성이 있으므로 값의 차이를 활용하는 방법은 비추천 (hashing 등) +static Comparator hashCodeOrder = (o1, o2) -> o1.hashCode() - o2.hashCode(); + +// 추천 방법 +static Comparator hashCodeOrder = (o1, o2) -> Integer.compare(o1.hashCode(), o2.hashCode()); +``` + + diff --git a/image/legacy_pop.png b/image/legacy_pop.png new file mode 100644 index 00000000..a1c97c3f Binary files /dev/null and b/image/legacy_pop.png differ diff --git a/image/pop_with_null_marking.png b/image/pop_with_null_marking.png new file mode 100644 index 00000000..a448d165 Binary files /dev/null and b/image/pop_with_null_marking.png differ diff --git a/src/effectivejava/chapter2/item2/hierarchicalbuilder/Pizza.java b/src/effectivejava/chapter2/item2/hierarchicalbuilder/Pizza.java index 77925cda..c547b0fe 100644 --- a/src/effectivejava/chapter2/item2/hierarchicalbuilder/Pizza.java +++ b/src/effectivejava/chapter2/item2/hierarchicalbuilder/Pizza.java @@ -21,7 +21,8 @@ public T addTopping(Topping topping) { // Subclasses must override this method to return "this" protected abstract T self(); } - + + // defensive copy for keep invariant Pizza(Builder builder) { toppings = builder.toppings.clone(); // See Item 50 } diff --git a/src/effectivejava/chapter2/item3/staticfactory/Elvis.java b/src/effectivejava/chapter2/item3/staticfactory/Elvis.java index 1f767166..0ce10880 100644 --- a/src/effectivejava/chapter2/item3/staticfactory/Elvis.java +++ b/src/effectivejava/chapter2/item3/staticfactory/Elvis.java @@ -9,10 +9,4 @@ private Elvis() { } public void leaveTheBuilding() { System.out.println("Whoa baby, I'm outta here!"); } - - // This code would normally appear outside the class! - public static void main(String[] args) { - Elvis elvis = Elvis.getInstance(); - elvis.leaveTheBuilding(); - } } diff --git a/src/effectivejava/chapter2/item3/staticfactory/ElvisTest.java b/src/effectivejava/chapter2/item3/staticfactory/ElvisTest.java new file mode 100644 index 00000000..67c779b5 --- /dev/null +++ b/src/effectivejava/chapter2/item3/staticfactory/ElvisTest.java @@ -0,0 +1,40 @@ +package effectivejava.chapter2.item3.staticfactory; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class ElvisTest { + // This code would normally appear outside the class! + public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException { + Elvis elvis = Elvis.getInstance(); + elvis.leaveTheBuilding(); + + Elvis elvis2 = Elvis.getInstance(); + System.out.println(elvis); + System.out.println(elvis2); + System.out.println(); + + // Elvis e = new Elvis(); error + + // invoke private constructor with reflection + Constructor con = Elvis.class.getDeclaredConstructors()[0]; + con.setAccessible(true); + Elvis elvisInvoked = (Elvis) con.newInstance(); + System.out.println(elvis); + System.out.println(elvisInvoked); + + elvisInvoked.leaveTheBuilding(); + + /* + + Whoa baby, I'm outta here! + effectivejava.chapter2.item3.staticfactory.Elvis@2471cca7 + effectivejava.chapter2.item3.staticfactory.Elvis@2471cca7 + + effectivejava.chapter2.item3.staticfactory.Elvis@2471cca7 + effectivejava.chapter2.item3.staticfactory.Elvis@5fe5c6f + Whoa baby, I'm outta here! + + */ + } +} diff --git a/src/effectivejava/chapter2/item7/Stack.java b/src/effectivejava/chapter2/item7/Stack.java index d27d83d6..734215f1 100644 --- a/src/effectivejava/chapter2/item7/Stack.java +++ b/src/effectivejava/chapter2/item7/Stack.java @@ -31,7 +31,7 @@ private void ensureCapacity() { elements = Arrays.copyOf(elements, 2 * size + 1); } -// // Corrected version of pop method (Page 27) + // Corrected version of pop method (Page 27) // public Object pop() { // if (size == 0) // throw new EmptyStackException(); @@ -40,12 +40,27 @@ private void ensureCapacity() { // return result; // } - public static void main(String[] args) { + public static void main(String[] args) throws InterruptedException { Stack stack = new Stack(); - for (String arg : args) - stack.push(arg); - while (true) - System.err.println(stack.pop()); + System.out.println("push"); + + //for (String arg : args) + for(int i=0;i< 0x02ffffff ;i++) + stack.push(String.valueOf(i)); + + Thread.sleep(10000); + + System.out.println("pop"); + + int sleepPeriod = 10000; + int counter = 0; + while (true) { + stack.pop(); + counter++; + if(counter % sleepPeriod == 0) + Thread.sleep(100); + } + //System.err.println(stack.pop()); } }