// 보안 허점이 숨어 있다. 해결 방법은 두 가지가 있다.
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-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";
}
}