EffectiveJava

[EffectiveJava] 4장 클래스와 인터페이스

15. 클래스와 멤버의 접근 권한을 최소화하라

// 보안 허점이 숨어 있다. 해결 방법은 두 가지가 있다.
public static final Thing[] VALUES = { ... };
// 첫 번째 : public 배열을 private으로 만들고 public 불변 리스트를 추가하여 해결한다.
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = 
			  Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES))
// 두 번째 : private 배열로 만들고 public 메서드를 추가하는 방어적 복사
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
    return PRIVATE_VALUES.clone();
}

16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

// 16-1 이처럼 퇴보한 클래스는 public이어서는 안 된다!
public class Item16 {
    public double x;
    public double y;
}
// 16-2 접근자와 변경자(mutator) 메서드를 활용해 데이터를 캡슐화한다.
public class Point {
    private double x;
    private double y;

    public Item16(double x, double y) {
        this.x = x;
        this.y = y;
    }

    **public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }**
}
// 16-3 불변 필드를 노출한 public 클래스 - 과연 좋은가?
public final class Time{
    private static final int HOURS_PER_DAY = 24;
    private static final int MINUTES_PER_HOUR = 60;
    
    public final int hour;
    public final int minute;   

    // final은 불변인데 Time생성자를 여러 번 호출하면 바꿀 수 있다. 
    public Time(int hour, int minute){
        if(hour < 0 || hour >= HOURS_PER_DAY)
            throw new IllegalArgumentException("시간 : " + hour)
        if(hour < 0 || hour >= MINUTES_PER_HOUR)
            throw new IllegalArgumentException("분 : " + minute)
        this.hour = hour;
        this.minute = minute;
    }
		... // 나머지 코드 생략
}

17. 변경 가능성을 최소화하라

// 17-1 불변 복소수 클래스
public class Complex {
    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }

    public double realPart() {
        return re;
    }

    public double imaginaryPart() {
        return im;
    }

    public Complex plus(Complex c) {
        return new Complex(re + c.re, im + c.im);
    }

    public Complex minus(Complex c) {
        return new Complex(re - c.re, im - c.im);
    }

    public Complex times(Complex c) {
        return new Complex(re * c.re - im * c.im, re * c.im + im * c.re);
    }

    public Complex dividedBy(Complex c) {
        double tmp = c.re * c.re + c.im * c.im;
        return new Complex((re * c.re + im * c.im) / tmp, (im * c.re - re * c.im) / tmp);
    }

    @Override public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof Complex))
            return false;
        Complex c = (Complex) o;

				// == 대신 compare를 사용하는 이유는 63쪽을 확인하라.
        return Double.compare(c.re, re) == 0 && Double.compare(c.im, im) == 0;
    }

    @Override public int hashCode() {
        return 31 * Double.hashCode(re) + Double.hashCode(im);
    }

    @Override public String toString() {
        return "(" + re + " + " + im + "i)";
    }
}
public static final Complex ZERO = new Complex(0,0);
public static final Complex ONE = new Complex(1,0);
public static final Complex I = new Complex(0,1);
BigInteger moby = ...;
moby = moby.flipBit(0);
BigInteger moby = ...;
moby.flipBit(0);
// 17-2 생성자 대신 정적 팩터리를 사용한 불변 클래스
public class Complex {
    private final double re;
    private final double im;

    public Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
		
		public static Complex valueOf(double re, double im){
			return new Complex(re,im);
		}
		... // 나머지 코드는 생략
}
public static BigInteger safeInstance(BigInteger val) {
    return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray());
}

18. 상속보다는 컴포지션을 사용하라

// 18-1 잘못된 예 - 상속을 잘못 사용했다!
import java.util.Collection;
import java.util.HashSet;

public class InstrumentedHashSet<E> extends HashSet<E> {

    //추가된 원소의 수
    private int addCount = 0;

    public InstrumentedHashSet() {}

    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }

    @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;
    }
}
InstrumentedHashSet<String> s = new InstrumentedHashSet**<>();**
s.addAll**(**List.of("틱","탁탁","펑"));
// 18-2 래퍼 클래스 - 상속 대신 컴포지션을 사용했다.
import java.util.Collection;
import java.util.Set;

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;
    }
}
// 18-3 재사용할 수 있는 전달 클래스
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;

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

    public ForwardingSet(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.retainAll(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(); }
}
Set<Instant> times = new InstrumentedSet<>(new TreeSet<>(cmp));
Set<E> s = new InstrumentedSet<>(new HashSet<>(INIT_CAPACITY));
static void walk(Set<Dog> dogs){
    InstrumentedSet<Dog> iDogs = new InstrumentedSet<>(dogs);
    ... // 이 메서드에서는 dogs 대신 iDog를 사용한다.
}

19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라

public class Super {
		 // 잘못 된 예 - 생성자가 재정의 가능 메서드를 호출한다.
     public Super() {
         overrideMe();
     }
     public void overrideMe() {
     }
 }
 public final class Sub extends Super {
		 // 초기화되지 않은 final 필드. 생성자에서 초기화한다.
     private final Instant instant;
     Sub() {
         instant = Instant.now();
     }

     // 재정의 가능 메서드. 상위 클래스의 생성자가 호출한다.
     @Override public void overrideMe() {
         System.out.println(instant);
     }

     public static void main(String args[]) {
         Sub sub = new Sub();
         sub.overrideMe();
     }
 }

20. 추상 클래스보다는 인터페이스를 우선하라

interface Singer {
    AudioClip sing(String s);
}

interface Songwriter {
    String compose(int chartPosition);
}
interface SingerSongwriter extends Singer, Songwriter {
    AudioClip strum();
    void actSensitive();
}
// 20-1 골격 구현을 사용해 완성한 구체 클래스
static List<Integer> intArrayAsList(int[] a) {
        Objects.requireNonNull(a);

        // 다이아몬드 연산자를 이렇게 사용하는건 자바 9부터 가능하다.
        // 더 낮은 버전을 사용한다면 <Integer>로 수정하자.
        return new AbstractList<Integer>() {
            @Override
            public Integer get(int i) {
                return a[i]; // 오토박싱(아이템 6)
            }

            @Override
            public Integer set(int i, Integer val) {
                int oldVal = a[i];
                a[i] = val;     // 오토언박싱
                return oldVal;  // 오토박싱
            }
            
            @Override
            public int size() {
                return a.length;
            }

        };
    }
// 20-2 골격 구현 클래스
public abstract class AbstractMapEntry<K,V> implements Map.Entry<K,V>{

    // 변경 가능한 엔트리는 이 메서드를 반드시 재정의해야 한다.
    @Override public V setValue(V value){
        throw new UnsupportedOperationException();
    }

    // Map.Entry.equals의 일반 규약 구현한다.
    @Override public boolean equals(Object o){
        if(o == this) return true;
        if(!(o instanceof Map.Entry)) return false;
        Map.Entry<?,?> e = (Map.Entry) o;
        return Objects.equals(e.getKey(), getKey())
                && Objects.equals(e.getValue(), getValue());
    }

    // Map.Entry.hashCode 일반 규약 구현한다.
    @Override public int hashCode() {
        return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
    }

    @Override public String toString(){
        return getKey() + "=" +getValue();
    }

}

21. 인터페이스는 구현하는 쪽을 생각해 설계하라

// 21-1 자바 8의 Collection 인터페이스에 추가된 디폴트 메서드
default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean result = false;
    final Iterator<E> each = iterator();
    for(Iterator<E> it = iterator(); it.hasNext();){
        if(filter.test(it.next())){
            it.remove();
            result = true;
        }
    }
    return result;
}

22. 인터페이스는 타입을 정의하는 용도로만 사용하라

// 22-1 상수 인터페이스 안티패턴 - 사용금지!
public class PhysicalConstants {
  // 아보가드로 수 (1/몰)
  static final double AVOGADROS_NUMBER = 6.022_140_857e23;

  // 볼츠만 상수 (J/K)
  static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;

  // 전자 질량(kg)
  static final double ELECTRON_MASS = 9.109_383_56e-31;
}
// 22-2 상수 유틸리티 클래스
package effectivejava.chapter4.item22.constantutilityclass;

public class PhysicalConstants {
      private PhysicalConstants() {} // 인스턴스화 방지

      // 아보가드로 수 (1/몰)
      static final double AVOGADROS_NUMBER = 6.022_140_857e23;

      // 볼츠만 상수 (J/K)
      static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;

      // 전자 질량(kg)
      static final double ELECTRON_MASS = 9.109_383_56e-31;
  }
// 22-3 정적 임포트를 사용해 상수 이름만으로 사용하기
import static effectivejava.chapter4.item22.constantutilityclass.PhysicalConstants.*;

public class Test {
    double atoms(double mols){
        return AVOGADROS_NUMBER * mols ;
    }
    ...
    // PhysicalConstants를 빈번히 사용한다면 정적 임포트가 값어치를 한다.
}

23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라

// 23-1 태그 달린 클래스 - 클래스 계층구조보다 훨씬 나쁘다!
public class Figure {
    enum Shape {RECTANGLE, CIRCLE};

    // 태그 필드 - 현재 모양을 나타낸다.
    final Shape shape;

    // 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다.
    double length;
    double width;

    // 다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다.
    double radius;

    // 원용 생성자
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    // 사각형 생성자
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch(shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError(shape);
        }
    }

}
// 23-2 태그 달린 클래스를 클래스 계층구조로 변환
abstract class Figure {
    abstract double area();
}

class Circle extends Figure {
    final double radius;
    Circle(double radius) { this.radius = radius; }

    @Override
    double area() {
        return Math.PI * (radius * radius);
    }
}

class Rectangle extends Figure {
    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    double area() {
        return length * width;
    }
class Square extends Rectangle {
		Square(double side){
				super( side, side );
		}
}

24. 멤버 클래스는 되도록 static으로 만들라

// 24-1 비정적 멤버 클래스의 흔한 쓰임 - 자신의 반복자 구현
public class MySet<E> extends AbstractSet<E>{
      ... // 생략
    @Override public Iterator<E> iterator() {
          return new MyIterator();
    }

      private class MyIterator implements Iterator<E> {
          ...
    }
}

25. 톱레벨 클래스는 한 파일에 하나만 담으라

public class Main{
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }
}
// 25-1 두 클래스가 한 파일(Utensil.java)에 정의되었다. -따라 하지 말 것!
class Utensil{
    static final String NAME = "pan";
}
class Dessert {
    static final String NAME = "cake";
}
// 25-2 두 클래스가 한 파일(Dessert.java)에 정의되었다. -따라 하지 말 것!
class Utensil{
    static final String NAME = "pot";
}
class Dessert {
    static final String NAME = "pie";
}
// 25-3 톱레벨 클래스들을 정적 멤버 클래스로 바꿔본 모습
public class Test {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }

    private static class Utensil {
        static final String NAME = "pan";
    }

    private static class Dessert {
        static final String NAME = "cake";
    }
}

Leave a Reply