item 34 JihoonKim - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

[item34] int μƒμˆ˜ λŒ€μ‹  μ—΄κ±° νƒ€μž…μ„ μ‚¬μš©ν•˜λ‹€

μ •μˆ˜ μ—΄κ±° νŒ¨ν„΄

public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;

public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
  • νƒ€μž… μ•ˆμ „ν•˜μ§€ μ•Šλ‹€.
     APPLE_FUJI == ORANGE_NAVEL // κ²½κ³  λ©”μ‹œμ§€ ν•˜λ‚˜ λœ¨μ§€ μ•ŠλŠ”λ‹€.
    
  • ν¬ν˜„λ ₯도 쒋지 μ•Šλ‹€.
  • namespaceλ₯Ό μ§€μ›ν•˜μ§€ μ•Šμ•„ 접두어λ₯Ό μ“°λŠ” λ“±μ˜ λ°©λ²•μœΌλ‘œ 이름 μΆ©λŒμ„ 방지해야 ν•œλ‹€
  • μ»΄νŒŒμΌν•˜λ©΄ κ·Έ 값이 ν΄λΌμ΄μ–ΈνŠΈ νŒŒμΌμ— κ·ΈλŒ€λ‘œ μƒˆκ²¨μ§„λ‹€.(JSL-13.1)
    • μƒμˆ˜μ˜ 값이 λ°”λ€Œλ©΄ ν΄λΌμ΄μ–ΈνŠΈλ„ λ°˜λ“œμ‹œ λ‹€μ‹œ μ»΄νŒŒμΌν•΄μ•Ό 함
  • μ •μˆ˜ λŒ€μ‹  λ¬Έμžμ—΄ μƒμˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” λ³€ν˜• νŒ¨ν„΄(λ¬Έμžμ—΄ μ—΄κ±° νŒ¨ν„΄, string enum pattern)도 μ‘΄μž¬ν•œλ‹€

μ—΄κ±° νƒ€μž…μ˜ νŠΉμ§•

  • μ™„μ „ν•œ ν˜•νƒœμ˜ 클래슀(C, C++, C#의 enumκ³ΌλŠ” λ‹€λ₯΄λ‹€)
  • μƒμˆ˜ ν•˜λ‚˜λ‹Ή μžμ‹ μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό ν•˜λ‚˜μ”© λ§Œλ“€μ–΄ public static final ν•„λ“œλ‘œ κ³΅κ°œν•œλ‹€
  • λ°–μ—μ„œ μ ‘κ·Όν•  수 μžˆλŠ” μƒμ„±μžλ₯Ό μ œκ³΅ν•˜μ§€ μ•Šμ•„ 사싀상 final
  • ν΄λΌμ΄μ–ΈνŠΈκ°€ μΈμŠ€ν„΄μŠ€λ₯Ό 직접 μƒμ„±ν•˜κ±°λ‚˜ ν™•μž₯ν•  수 μ—†λ‹€
  • μ—΄κ±° νƒ€μž… μ„ μ–ΈμœΌλ‘œ λ§Œλ“€μ–΄μ§„ μΈμŠ€ν„΄μŠ€λ“€μ€ λ”± ν•˜λ‚˜μ”©λ§Œ 쑴재
  • μ»΄νŒŒμΌνƒ€μž„ νƒ€μž… μ•ˆμ „μ„± 제곡
    • λ§€κ°œλ³€μˆ˜λ‘œ 건넀받은 μ°Έμ‘°λŠ” null λ˜λŠ” ν•΄λ‹Ή enum 쀑 ν•˜λ‚˜(λ‹€λ₯Έ νƒ€μž…μΌ 경우 컴파일 μ—λŸ¬)
  • namespace μ‘΄μž¬ν•˜μ—¬ 이름이 같은 μƒμˆ˜λ„ 곡쑴 κ°€λŠ₯
  • ν•„λ“œμ˜ μ΄λ¦„λ§Œ 곡개되기 λ•Œλ¬Έμ— enum에 μƒμˆ˜ μΆ”κ°€λ‚˜ μˆœμ„œ 변경이 μžˆλ”λΌλ„ λ‹€μ‹œ μ»΄νŒŒμΌν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.
  • enum의 toString()은 좜λ ₯에 μ ν•©ν•œ λ¬Έμžμ—΄μ„ λ‚΄μ€€λ‹€.
  • μž„μ˜μ˜ λ©”μ„œλ“œλ‚˜ ν•„λ“œλ₯Ό μΆ”κ°€ν•  수 있고, μž„μ˜μ˜ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ²Œ ν•  μˆ˜λ„ μžˆλ‹€.

데이터와 λ©”μ„œλ“œλ₯Ό κ°–λŠ” μ—΄κ±° νƒ€μž…

public enum Planet {
	MERCURY(3.302e+23, 2.439e6),
	VENUS(4.869e+24, 6.052e6),
	EARTH(5.975e+24, 6.378e6),
	MARS(6.419e+23, 3.393e6),
	JUPITER(1.899e+27, 7.149e7),
	SATURN(5.685e+26, 6.027e7),
	URANUS(8.683e+25, 2.556e7),
	NEPTUNE(1.024e+26, 2.447e7);

	// λͺ¨λ“  field final
	private final double mass;            // μ§ˆλŸ‰(λ‹¨μœ„: ν‚¬λ‘œκ·Έλž¨)
	private final double radius;          // λ°˜μ§€λ¦„(λ‹¨μœ„: λ―Έν„°)
	private final double surfaceGravity;  // ν‘œλ©΄μ€‘λ ₯(λ‹¨μœ„: m / s^2)

	// 쀑λ ₯μƒμˆ˜ (λ‹¨μœ„: m^3 / kg s^2)
	private static final double G = 6.67300E-11;

	// μƒμ„±μž
	Planet(double mass, double radius) {
		this.mass = mass;
		this.radius = radius;
		this.surfaceGravity = G * mass / (radius * radius);
	}

	public double surfaceWeight(double mass) {
		return mass * surfaceGravity;
	}
}
  • μ—΄κ±° νƒ€μž… μƒμˆ˜ 각각을 νŠΉμ • 데이터와 μ—°κ²°μ§€μœΌλ €λ©΄ μƒμ„±μžμ—μ„œ 데이터λ₯Ό λ°›μ•„ μΈμŠ€ν„΄μŠ€ ν•„λ“œμ— μ €μž₯ν•˜λ©΄ λœλ‹€.
  • λͺ¨λ“  fieldλŠ” final이어야 ν•œλ‹€. (참고둜, fieldλ₯Ό private으둜 두고 public μ ‘κ·Όμž λ©”μ„œλ“œλ₯Ό λ‘λŠ” 게 λ‚«λ‹€)
  • μžμ‹  μ•ˆμ— μ •μ˜λœ μƒμˆ˜λ“€μ˜ 값을 배열에 λ‹΄μ•„(μΈμŠ€ν„΄μŠ€ λ°°μ—΄) λ°˜ν™˜ν•˜λŠ” 정적 λ©”μ„œλ“œ values()λ₯Ό μ œκ³΅ν•œλ‹€. (μ„ μ–Έλœ μˆœμ„œ)
  • μƒμˆ˜ 이름을 μž…λ ₯λ°›μ•„ κ·Έ 이름에 ν•΄λ‹Ήν•˜λŠ” μƒμˆ˜(μΈμŠ€ν„΄μŠ€)λ₯Ό λ³€ν™˜ν•΄μ£ΌλŠ” valueOf(String)이 μžλ™ μƒμ„±λœλ‹€.
  • 각 μ—΄κ±° νƒ€μž… κ°’μ˜ toString()은 μƒμˆ˜ 이름을 λ¬Έμžμ—΄λ‘œ λ°˜ν™˜ν•œλ‹€
  • μƒμˆ˜κ°€ 제거되면 ν•΄λ‹Ή μƒμˆ˜λ₯Ό μ°Έμ‘°ν•˜κ³  있던 ν΄λΌμ΄μ–ΈνŠΈλŠ” μ»΄νŒŒμΌν•  λ•Œ 였λ₯˜κ°€ λ°œμƒν•  것이닀.(μ •μˆ˜ μ—΄κ±° νŒ¨ν„΄μ—μ„œλŠ” 이런 κ±Έ κΈ°λŒ€ν•  수 μ—†λ‹€)

μƒμˆ˜λ³„ λ©”μ„œλ“œ κ΅¬ν˜„

// 쒋지 μ•Šμ€ ν˜•νƒœ
public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;
	
	// μƒμˆ˜κ°€ λœ»ν•˜λŠ” μ—°μ‚° μˆ˜ν–‰
	public double apply(double x, double y) {
		switch(this) {
			case PLUS:   return x + y;
			case MINUS:  return x - y;
			case TIMES:  return x * y;
			case DIVIDE: return x / y;
		}
		throw new AssertionError("μ•Œ 수 μ—†λŠ” μ—°μ‚°: " + this);
	}
}
  • throw문에 도달할 일이 μ—†μ§€λ§Œ μƒλž΅ν•˜λ©΄ μ»΄νŒŒμΌλ˜μ§€ μ•ŠλŠ”λ‹€.
  • μƒμˆ˜κ°€ μƒˆλ‘œ μΆ”κ°€λ˜μ—ˆμ„ λ•Œ case문이 μΆ”κ°€λ˜μ§€ μ•ŠμœΌλ©΄ λŸ°νƒ€μž„μ— 였λ₯˜κ°€ λ°œμƒν•œλ‹€.
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 final String symbol;
	
	// μƒμ„±μž
	Operation(String symbol) { this.symbol = symbol; }
    public abstract double apply(double x, double y);
	@override public String toString() { return symbol; }
}

public static void main(String[] args) {
	double x = Double.parseDouble(args[0]);
	double y = Double.parseDouble(args[1]);
	for (Operation op : Operation.values())
		System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
  • apply()κ°€ 좔상 λ©”μ„œλ“œλΌμ„œ μž¬μ •μ˜ν•˜μ§€ μ•ŠμœΌλ©΄ 컴파일 μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.
  • μ—΄κ±° νƒ€μž… μƒμˆ˜λΌλ¦¬ μ½”λ“œλ₯Ό κ³΅μœ ν•˜κΈ° μ–΄λ ΅λ‹€λŠ” 단점이 μžˆλ‹€.

μ „λž΅(strategy) μ—΄κ±° νƒ€μž… νŒ¨ν„΄

// 쒋지 μ•Šμ€ ν˜•νƒœ
enum PayrollDay {
	MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

	private static final int MINS_PER_SHIFT = 8 * 60;

	int pay(int minutesWorked, int payRate) {
		int basePay = minutesWorked * payRate;
		int overtimePay;
		switch(this) {
			case SATURDAY: case SUNDAY: // 주말
				overtimePay = basePay / 2;
				break;
			default: // 주쀑
				overtimePay = minutesWorked <= MINS_PER_SHIFT ? 0 : 
					(minutesWorked - MINS_PER_SHIFT) * payRate / 2;
		}
		return basePay + overtimePay;
	}
}
  • κ°„κ²°ν•˜λ‚˜, 관리 κ΄€μ μ—μ„œ μœ„μ—„ν•œ μ½”λ“œ
  • μƒˆλ‘œμš΄ 값을 enum에 μΆ”κ°€ν•˜λ €λ©΄ case문을 μžŠμ§€ 말고 쌍으둜 λ„£μ–΄μ€˜μ•Ό ν•˜λŠ” 것
  • μƒμˆ˜λ³„ λ©”μ„œλ“œ κ΅¬ν˜„μ€ μ½”λ“œκ°€ μž₯ν™©ν•΄μ Έ 가독성이 크게 떨어지고 였λ₯˜ λ°œμƒ κ°€λŠ₯성이 λ†’λ‹€
// μ „λž΅(strategy) μ—΄κ±° νƒ€μž… νŒ¨ν„΄ 적용
enum PayrollDay {
	MONDAY(WEEKDAY), TUESDAY(WEEKDAY), WEDNESDAY(WEEKDAY), 
	THURSDAY(WEEKDAY), FRIDAY(WEEKDAY),
	
	SATURDAY(WEEKEND), SUNDAY(WEEKEND);

	private final PayType payType;

	PayrollDay(PayType payType) {
		this.payType = payType;
	}

	int pay(int minutesWorked, int payRate) {
		return payType.pay(minutesWorked, payRate);
	}

	// nested class
	enum PayType {
		WEEKDAY {
			int overtimePay(int minutesWorked, int payRate) {
				return minutesWorked <= MINS_PER_SHIFT ? 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2;
			}
		},

		WEEKEND {
			int overtimePay(int minutesWorked, int payRate) {
				return minutesWorked * payRate / 2;
			}
		};

		abstract int overtimePay(int minutesWorked, int payRate);
		private static final int MINS_PER_SHIFT = 8 * 60;

		int pay(int minutesWorked, int payRate) {
			int basePay = minutesWorked & payRate;
			return basePay + overtimePay(minutesWorked, payRate);
		}
	}
}
  • μž”μ—… μˆ˜λ‹Ή 계산을 private 쀑첩 enum νƒ€μž…μΈ PayType에 μœ„μž„ν•œλ‹€.
  • switchλ¬Έλ³΄λ‹€λŠ” λ³΅μž‘ν•˜μ§€λ§Œ 더 μ•ˆμ „ν•˜κ³  μœ μ—°ν•˜λ‹€

switch문을 μ΄μš©ν•œ κΈ°λŠ₯ μˆ˜ν–‰

// Thirdpartyμ—μ„œ κ°€μ Έμ˜¨ Operation μ—΄κ±° νƒ€μž…μ„ μ΄μš©ν•΄μ•Ό ν•  λ•Œ
public static Operation inverse(Operation op) {
	switch(op) {
		case PLUS:   return Operation.MINUS;
		case MINUS:  return Operation.PLUS;
		case TIMES:  return Operation.DIVIDE;
		case DIVIDE: return Operation.TIMES;
		
		default: throw new AssertionError("μ•Œ 수 μ—†λŠ” μ—°μ‚°: " + op);
	}
}
  • switch문이 μ—΄κ±° νƒ€μž…μ˜ μƒμˆ˜λ³„ λ™μž‘μ„ κ΅¬ν˜„ν•˜λŠ” 데 μ ν•©ν•˜μ§€ μ•ŠμœΌλ‚˜, μƒμˆ˜λ³„ λ™μž‘μ„ ν˜Όν•©μ— 넣을 λ•ŒλŠ” 쒋은 선택이 될 수 μžˆλ‹€.

핡심 정리

  • μ—΄κ±° νƒ€μž…μ΄ μ •μˆ˜ μƒμˆ˜λ³΄λ‹€ 더 읽기 쉽고 μ•ˆμ „ν•˜κ³  κ°•λ ₯ν•˜λ‹€
  • 각 μƒμˆ˜λ₯Ό νŠΉμ • 데이터와 μ—°κ²°μ§“κ±°λ‚˜ μƒμˆ˜λ§ˆλ‹€ λ‹€λ₯΄κ²Œ λ™μž‘ν•˜κ²Œ ν•  수 μžˆλ‹€.
  • ν•˜λ‚˜μ˜ λ©”μ„œλ“œκ°€ μƒμˆ˜λ³„λ‘œ λ‹€λ₯΄κ²Œ λ™μž‘ν•΄μ•Ό ν•  λ•ŒλŠ” switchλ¬Έ λŒ€μ‹  μƒμˆ˜λ³„ λ©”μ„œλ“œ κ΅¬ν˜„μ„ μ‚¬μš©ν•˜μž
  • μƒμˆ˜ 일뢀가 같은 λ™μž‘μ„ κ³΅μœ ν•œλ‹€λ©΄ μ „λž΅ μ—΄κ±° νƒ€μž… νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μž
  • ν•„μš”ν•œ μ›μ†Œλ₯Ό μ»΄νŒŒμΌνƒ€μž„μ— λ‹€ μ•Œ 수 μžˆλŠ” μƒμˆ˜ 집합이라면 항상 μ—΄κ±° νƒ€μž…μ„ μ‚¬μš©ν•˜μž
    • E.g. νƒœμ–‘κ³„ ν–‰μ„±, 체슀 말, 메뉴 μ•„μ΄ν…œ, μ—°μ‚° μ½”λ“œ, λͺ…령쀄 ν”Œλž˜κ·Έ λ“±
    • μ •μ˜λœ μƒμˆ˜ κ°œμˆ˜κ°€ μ˜μ›νžˆ λΆˆλ³€μΌ ν•„μš”λŠ” μ—†λ‹€. (μƒμˆ˜ 좔가돼도 λ°”μ΄λ„ˆλ¦¬ ν˜Έν™˜ κ°€λŠ₯)

μ°Έκ³