티스토리 뷰
아이템2, 생성자에 매개변수가 많다면 빌더를 고려하라
public class NutritionFacts {
private final int servingSize; // (ml, 1회 제공량) 필수
private final int servings; // (회, 총 n회 제공량) 필수
private final int calories; // (1회 제공량당) 선택
private final int fat; // (g/1회 제공량) 선택
private final int sodium; // (mg/1회 제공량) 선택
private final int carbohydrate; // (g/1회 제공량) 선택
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings,int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
}
매개변수가 많아질수록 해당 클래스의 인스턴스를 만들 때 필요한 생성자의 개수가 많아진다.
빌더패턴을 사용한다면 생성자를 만들고 코드를 관리하는 불필요한 노동을 줄일 수 있다. 아래 코드는 읽기도 쓰기도 쉽다. (과거에는 빌더를 직접 생성해줬어야하나 현재는 @Builder Annotaion을 붙이는 것만으로도 손쉽게 빌더를 생성할 수 있다.)
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// 필수 매개변수
private final int servingSize;
private final int servings;
// 선택 매개변수 - 기본값으로 초기화한다.
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) { calories = val; return this; }
public Builder fat(int val){ fat = val; return this; }
public Builder sodium(int val){ sodium = val; return this; }
public Builder carbohydrate(int val){ carbohydrate = val; return this; }
public NutritionFacts build() {return new NutritionFacts(this);}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
Bulider를 사용할 경우 아래 코드처럼 필요한 매개변수만 설정하면서 객체를 생성할 수 있다.
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)calories(100).sodium(35).carbohydrate(27).build();
아이템4, 인스턴스화를 막으려거든 private 생성자를 사용하라
java.lang.Math와 java.util.Arrays처럼 단순히 정적 메서드와 정적 필드만을 담은 클래스가 필요할 때가 있다. 이렇게 정적 멤버만 담은 유틸리비 클래스는 인스턴스로 만들어 쓰려고 설계한 게 아니다.
*인스턴스 : 클래스로부터 생성된 오브젝트, 설계(클래스)가 실체화된 것
만약 생성자를 명시하지 않으면 컴파일러가 자동으로 기본 생성자를 만들어줄 것이고 사용자는 개발자의 의도를 오해할 수 있다.
이럴 때 private 생성자를 통해 클래스가 인스턴스화가 되는 것을 막아주고 상속을 불가능하게 한다.(모든 생성자는 상위 클래스의 생성자를 호출하게 되는데, 이를 private로 선언했으니 하위 클래스가 상위 클래스의 생성자에 접근할 수 없게 된다.)
아이템6, 불필요한 객체 생성을 피하라
String s = new String("bikini");
String s = "bikini";
위 코드는 실행될 때마다 String 인스턴스를 새로 만든다.
아래 코드는 하나의 String 인스턴스를 사용한다.
주어진 문자열이 유효한 로마 숫자인지 확인하는 메서드를 작성한다고 해보자. 다음은 정규표현식을 활용한 가장 쉬운 해법이다.
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
String matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해서 사용하기엔 적합하지 않다.
왜냐하면 matches()의 내부에서 만드는 정규표현식용 Pattern 인스턴스는 한 번 쓰고 버려져셔 곧바로 가비지 컬렉션 대상이 되기 때문이다.
성능을 개선하려면 정규표현식을 표현하는 (불변할, 앞으로 바뀔 예정이 없을) Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 이 인스턴스를 재사용한다.
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
아이템7 다 쓴 객체 참조를 해제하라
스택을 간단히 구현한 다음 코드를 보자. 메모리 누수가 일어나는 위치는 어디인가?
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size];
}
/**
* 원소를 위한 공간을 적어도 하나 이상 확보한다.
* 배열 크기를 늘려야 할 때마다 대략 두 배씩 늘린다.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
이 코드에서는 스택이 커졌다가 줄어들었을 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 않는다. 이 스택이 그 객체들의 다 쓴 참조를 여전히 가지고 있기 때문이다.
객체 참조 하나를 살려두면 가비지 컬렉터는 그 객체뿐 아니라 그 객체가 참조하는 모든 객체를 회수해가지 못한다.
단 몇 개의 객체가 매우 많은 객체를 회수되지 못하게 할 수 있고 잠재적으로 성능에 악영향을 줄 수 있다.
해법은 간단하다. 해당 참조를 다 썼을 때 null 처리 (참조 해제)하면 된다.
예시의 스택 클래스에서는 각 원소의 참조가 더 이상 필요 없어지는 시점은 스택에서 꺼내질 때다. 그리고 아래는 pop 메서드를 제대로 구현한 모습이다.
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
다 쓴 참조를 null처리하면 다른 이점도 따라온다. 만약 null 처리된 참조를 실수로 사용하려 하면 프로그램은 즉시 NullPointerException을 던지며 종료된다. (프로그램 오류는 가능한 한 조기에 발견하는 게 좋다.)
메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년간 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 디버깅 도구를 동원해야만 발견되기도 한다. 그래서 이런 종류의 문제는 예방법을 익혀두는 것이 매우 중요하다.
아이템9 try-finally 보다는 try-with-resource를 사용하라
자바 라이브러리에서는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다. InputStream, OutputStream, java.sql.Connection 등이 좋은 예다. 전통적으로 자원이 제대로 닫힘을 보장하는 수단은 try-finally다.
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
여기서 자원을 하나 더 사용하는 예는 아래와 같다.
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
다음은 try-with-resources를 사용해 두 예제를 재작성한 예다.
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
try-with-resource 버전이 짧고 읽기 수월할 뿐 아니라 문제를 진단하기도 훨씬 좋다.
firstLineOfFile 메서드를 생각해보자.
readLine과 (코드에는 나타나지 않는) close 호출 양쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고 readLine에서 발생한 예외가 기록된다.
*꼭 회수해야 하는 자원을 다룰 때는 try-finall 말고, try-with-resource를 사용하자. 예외는 없다. 코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다. try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도, try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있다.
'프로그래밍 > 책' 카테고리의 다른 글
[이펙티브 자바, Effeective Java] 7장 람다와 스트림 (0) | 2022.01.04 |
---|---|
[이펙티브 자바, Effeective Java] 6장 열거 타입과 어노테이션 (0) | 2021.12.28 |
[이펙티브 자바, Effeective Java] 5장 제네릭 (0) | 2021.12.22 |
[이펙티브 자바, Effeective Java] 4장 클래스와 인터페이스 (1) | 2021.12.20 |
[이펙티브 자바, Effeective Java] 3장 모든 객체의 공통 메서드 (1) | 2021.12.08 |
- Total
- Today
- Yesterday
- jenkins webhook
- oracle.jdbc.driver.T4CConnection.isValid(I)Z
- github webhook
- 스프링부트 젠킨스
- HTML
- nginx 톰캣 설정
- 토이프로젝트 회고
- webhook
- 국비 프로젝트
- slack
- 젠킨스 웹훅
- nginx 내장톰캣 설정
- spring boot
- oracle
- springboot
- 깃허브 웹훅 젠킨스
- 젠킨스 자동 배포
- 오라클
- nginx 내장톰캣 연결
- GitHub
- 스프링부트 자동배포
- nginx to springboot tomcat
- java.lang.AbstractMethodError
- CI/CD
- spring boot jenkins
- 오라클 계정 오류
- nginx to tomcat
- github webhook jenkins
- nginx to 내장톰캣
- springboot jpa
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |