일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- delayed message plugin
- 교육기획팀
- RESTClient
- ddd
- 자동처리
- 큐시즘
- 교육기획팀원
- 도메인 주도 개발 시작하기
- GitHub Actions
- 영속성
- cicd
- 밋업프로젝트
- scheduling messages with rabbitmq
- 객체지향 쿼리 언어
- 한국대학생it경영학회
- kusitms
- 30기
- Spring Batch
- rabbitmq-delayed-message-exchange
- JPA
- Spring
- reactive operaton
- JPQL
- jdbc
- java
- 최범균
- 이펙티브자바
- 자바 ORM 표준 JPA 프로그래밍
- springboot
- Domain Driven Design
Archives
- Today
- Total
코딩은 마라톤
[이펙티브자바] 아이템 22, 23, 24 요약 정리 본문
아이템 22. 인터페이스는 타입을 정의하는 용도로만 사용하라
인터페이스는 자신을 구현할 클래스의 인스턴스를 참조할 수 있는 "타입" 역할을 한다.
클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에게 알려주는 것!
위 용도로 인터페이스를 사용해야 한다.
안티 패턴 - 상수 인터페이스
static final 필드로만 가득 찬, 메서드는 없는 인터페이스
public interface NumberConstants {
static final double ONE_NUMBER = 1.111111;
static final double TWO_NUMBER = 2.222222;
static final double THREE_NUMBER = 3.333333;
}
- 클래스 내부에서 사용하는 상수는 외부 인터페이스가 아닌 내부 구현에 해당한다.
따라서 상수 인터페이스를 구현하는 것은 내부 구현을 클래스의 API로 노출하는 행위다. - 클라이언트 코드가 내부 구현에 해당하는 상수들에 종속되게 한다.
- 상수를 더이상 쓰지 않더라도 바이너리 호환성을 위해 상수 인터페이스를 구현하고 있어야 한다.
상수 공개 방법
- 특정 클래스나 인터페이스와 강하게 연관된 상수라면 그 클래스나 인터페이스 자체에 추가한다.
- ex) Integer와 Double에 선언된 MIN_VALUE, MAX_VALUE 상수
- 열거 타입으로 나타내기 적합한 상수라면 열거 타입으로 만들어 공개한다.
- 인스턴스화 할 수 없는 유틸리티 클래스에 담아 공개한다.
public class NumberConstants {
// 유틸 클래스이므로 인스턴스화 방지
private NumberConstants() {}
public static final double ONE_NUMBER = 1.111111;
public static final double TWO_NUMBER = 2.222222;
public static final double THREE_NUMBER = 3.333333;
}
- 유틸 클래스에서 정의된 상수를 사용하려면 클래스 이름까지 함께 명시하여 사용한다.
-> NumberConstants.ONE_NUMBER... - 유틸 클래스의 상수를 자주 사용하면 정적 임포트(static import)를 하여 클래스 이름을 생략한다.
아이템 23. 태그 달린 클래스보다는 클래스 계층 구조를 활용하라
public class Figure {
enum Shape {RECTANGLE, CIRCLE};
// 태그 필드
final Shape shape;
// 사각형 필드
double length;
double width;
// 원용 필드
double radius;
// 사각형 생성자
public Figure(double length, double width) {
this.shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
// 원 생성자
public Figure(double radius) {
this.shape = Shape.CIRCLE;
this.radius = radius;
}
double area() {
switch (shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
태그 달린 클래스
두 가지 이상 의미를 표현할 수 있으며, 그중 현재 표현하는 의미를 태그 값으로 알려주는 클래스
단점
- 가독성이 나쁘다. -> 열거 타입 선언, 태그 필드, switch문 등 쓸데 없는 코드가 많다. (+ 메모리 많이 사용)
- 필드들을 final로 선언하려면 해당 의미에 쓰이지 않는 필드들까지 생성자에서 초기화해야 한다.
- 또 다른 의미를 추가하려면 코드를 수정해야 한다. (관여되는 모든 코드 수정해야 함)
- 인스턴스의 타입(열거)만으로 현재 나타내는 의미를 알 수 없다.
개선
태그 달린 클래스를 클래스 계층구조로 바꾸자.
- 계층 구조의 루트가 될 추상 클래스를 정의하고, 태그 값에 따라 동작이 달라지는 메서드들을 추상 메서드로 선언한다.
- 태그 값에 상관 없이 동작이 일정한 메서드들을 루트 클래스에 일반 메서드로 추가한다.
- 결과 : 루트 클래스에선 추상 메서드만 남게 된다.
- 루트 클래스를 확장한 구체 클래스를 의미별로 정의한다.
- 루트 클래스가 정의한 추상 메서드를 각자 의미에 맞게 구현한다.
// 추상클래스
// 루트 클래스
abstract class Figure {
// 태그에 따라 달라지는 메서드
abstract double area();
}
// 구체 클래스 : 원
class Circle extends Figure {
final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * (radius * radius);
}
}
// 구체 클래스 : 사각형
class Rectangle extends Figure {
final double length;
final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
}
장점
- 간결하고 명확하다. (쓸데없는 코드 모두 사라짐)
- 살아 남은 필드는 모두 final 이다.
- 각 클래스의 생성자가 모든 필드를 남김 없이 초기화하고 추상 메서드를 모두 구현했는지 컴파일러가 확인해준다.
- 루트 클래스의 코드를 건들지 않고 독립적으로 계층 구조를 확장하고 함께 사용할 수 있다.
- 타입이 의미별로 따로 존재하기 때문에 변수의 의미를 명시하거나 제한할 수 있고, 특정 의미만 매개변수로 받을 수 있다.
아이템 24. 멤버 클래스는 되도록 static으로 만들라
중첩 클래스 (nested class)
- 다른 클래스 안에 정의된 클래스
- 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다.
- 종류
- 정적 멤버 클래스, <내부 클래스 3가지> { (비정적) 멤버 클래스, 익명 클래스, 지역 클래스 }
정적 멤버 클래스
- 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스
- ex) 계산기와 연산 종류(정적 멤버 클래스)
Calculator(클래스)의 정적 멤버 클래스인 Operation(열거 타입)
Calculator.Operation.PLUS, Calculator.Operation.MINUS와 같이 연산 참조 가능
class Outer {
private final String house;
private final String door;
public static class Builder {
private final String house;
private String door = "";
public Builder(String house) {
this.house = house;
}
public Builder door(String d) {
this.door = d;
return this;
}
Outer build() {
return new Outer(this);
}
}
private Outer(Builder builder) {
house = builder.house;
door = builder.door;
}
}
Outer outer = new Outer.Builder("아파트").door("문 4개").build();
비정적 멤버 클래스
- 정적 멤버 클래스와 차이 : static 유무
- 바깥 클래스의 인스턴스 없이 존재할 수 없다.
- 비정적 멤버 클래스의 인스턴스 메서드에서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스의 참조를 가져올 수 있다.
- 정규화된 this : 클래스명.this 형태로 바깥 클래스의 이름을 명시하는 용법
public class Outer {
String house = "아파트";
public void build() {
Inner inner = new Inner();
inner.build();
}
public class Inner {
public void build() {
System.out.println(Outer.this.house);
}
}
}
결론
멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자.
static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 된다. => 참조 저장시 시간과 공간 소비, GC가 바깥 클래스의 인스턴스를 수거하지 못하는 경우 발생 (메모리 누수)
익명 클래스
- 이름이 없다.
- 바깥 클래스의 멤버도 아니다. 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다. (한번만 사용 가능)
- 코드의 어디서든 만들 수 있다.
- 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다.
- 정적 문맥에서라도 상수 변수 이외의 정적 멤버는 가질 수 없다.
- 익명 클래스에 새롭게 정의된 필드와 메서드는 익명 클래스 내부에서만 사용 가능하다.
- 람다가 나오면서 익명 클래스를 대체했다.
public class Anonymous {
public void log() {
}
}
public class Main {
public static void main(String[] args) {
Anonymous anonymous = new Anonymous() {
// 내부 클래스에서만 사용 가능
public void removeLog() {
log.info("---------");
}
@Override
public void log() {
log.info("log");
removeLog();
}
};
//anonymous.removeLog(); 호출 불가
anonymous.log();
}
}
지역 클래스
- 가장 드물게 사용된다.
- 지역변수를 선언할 수 있는 곳에서 어디서든 선언할 수 있다. (유효 범위도 지역 변수와 같다.)
- 멤버 클래스처럼 이름이 있고 반복해서 사용할 수 있다.
- 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있다.
- 정적 멤버는 가질 수 없으며 가독성을 위해 짧게 작성해야 한다.
public class Outer {
private String outer = "outer";
public void do() {
// 지역변수 자리
class Local {
public void localDo() {
String local = "local";
System.out.println(outer + local);
}
}
Local local = new Local();
local.localDo();
}
}
최종
- 메서드 밖에서도 사용해야 하거나 메서드 안에 정의하기에 너무 길다면 멤버 클래스로 만든다.
- 멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조한다면 비정적으로
- 그렇지 않다면 정적으로 만든다.
- 중첩 클래스가 한 메서드 안에서만 쓰이면서
- 그 인스턴스를 생성하는 지점이 단 한 곳이고
해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 이미 있다면
익명클래스로 만들고 - 그렇지 않다면 지역 클래스로 만들자.
- 그 인스턴스를 생성하는 지점이 단 한 곳이고
'Language > Java' 카테고리의 다른 글
[이펙티브자바] 아이템 26, 27, 28 요약 정리 (0) | 2024.06.07 |
---|---|
[이펙티브자바] 아이템 25 요약 정리 (0) | 2024.06.07 |
[이펙티브자바] 아이템 19, 20, 21 요약 정리 (2) | 2024.06.05 |
[이펙티브자바] 아이템 16, 17, 18 요약 정리 (0) | 2024.06.03 |
[이펙티브자바] 아이템 13, 14, 15 요약 정리 (0) | 2024.06.02 |