-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: EffectiveJavaItem4 - 인스턴스화를 막으려거든 private 생성자를 사용해라 정리
- Loading branch information
Showing
3 changed files
with
168 additions
and
81 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
--- | ||
published: true | ||
title: "Effective Java Item 3 - 생성자나 열거 타입으로 싱글턴임을 보증하라" | ||
categories: | ||
- Java | ||
--- | ||
|
||
생성자를 private로 만들고 public static final 필드로 인스턴스를 만들어높음 | ||
|
||
오퍼레이션 자체가 외부 API 거쳐서 호출하는 것이 일수도 있고 연산이 오래 걸리는 경우 굉장히 비효율적이다. 만약 인터페이스가 있다면 가짜 대역을 만들 수 있음. | ||
-> 근데 모킹하면 되는거 아닌가? 좀 더 찾아봐야 겠다. | ||
|
||
간결함이 장점이다. | ||
|
||
--- | ||
리플랙션 -> 싱글턴이 깨짐 | ||
getDeclaredConstructor -> 선언되어 있는 기본생성자라서 접근 지시지와 상관없이 private 생성자도 접근 가능함. | ||
setAccessible(true)로 설정을 하지 않으면 private여서 생성자를 호출할 수 없게 된다. | ||
|
||
getConstructor -> 그냥 public 한 생성자에만 접근할 수 있음 | ||
|
||
~~~java | ||
private static boolean created; | ||
|
||
private Elvis() { | ||
if (created) { | ||
throw new UnsupportedOperationException("can't be created by constructor"); | ||
} | ||
} | ||
|
||
~~~ | ||
|
||
static도 다시 한번 공부 ㄱㄱ | ||
|
||
그래서 리플렉션 쓰면 싱글턴이 깨지고 private 생성자로 막아야 함. 그런데 이렇게 하면 첫번째 장점이였던 간결함이 사라짐. | ||
|
||
두 번째 문제는 직렬화, 역직렬화 하면 여러 인스턴스가 생성된다. | ||
|
||
직렬화 역직렬화를 쓰려면 Serializable을 구현해야 함. | ||
|
||
그리고 직렬화 할 때 생성자가 사용이 된다? -> 이건 더 찾아봐야 할 듯. | ||
|
||
readResolve() -> 더 찾아봐야 할듯. 오버라이딩 아닌 오버라이딩 같은 메서드?? | ||
readResolve()를 재정의 해서 기존에 있던 인스턴스를 반환하도록 만드는 것이다. | ||
|
||
--- | ||
|
||
~~~java | ||
public static final INSTANCE = new Elvis(); | ||
private static final INSTANCE = new Elvis(); | ||
|
||
public static Elvis getInstance() { | ||
return INSTANCE; | ||
} | ||
~~~ | ||
|
||
단점은 테스트, 리플렉션, 직렬화역직렬화 똑같은 문제를 가지고 있음. 해결책은 전 단계에서 다 해결했음. | ||
|
||
장점은 클라이언트 코드가 getInstace()를 계속 쓰면서 행위, 동작을 바꿀 수 있음. | ||
|
||
클라이언트 코드는 바뀌지 않고 새로운 인스턴스를 만들도록 할 수 있음. 클라이언트 코드를 덜 건든다. | ||
|
||
~~~java | ||
public static Elvis getInstance() { | ||
rturn new Elvis(); | ||
} | ||
~~~ | ||
|
||
정적 팩터리를 제네릭 싱글턴 팩토리로 만들 수 있다. | ||
인스턴스는 같지만 타입은 다르게 쓸 수 있음(원하는 타입으로 형 변환을 할 수 있다) equals를 사용하면 두 비교가 같음. 그런데 == 연산자를 사용하면 타입이 다르기 때문에 false가 뜬다. | ||
|
||
래퍼런스가 같고 같은 객체임에도 불구하고 (해쉬코드도 같음) 그럼에도 == 비교는 할 수 없다. | ||
|
||
Q. 여기에는 왜 T가 한번 더 들어갔을까요? -> 이걸 잘 설명해야 제네릭을 이해한거임. | ||
비록 T 라는 타입 이름은 같지만 스콥이 다름 static 스콥. | ||
~~~java | ||
public static <T> MetaElvis<T> getInstance() { | ||
return (MetaElvis<T>) INSTANCE; | ||
} | ||
~~~ | ||
|
||
장점 3. 메서드 참조를 공급자로 사용할 수 있다. | ||
|
||
--- | ||
|
||
열거 타입으로 싱글턴임을 보장하라. | ||
|
||
생성자를 private로 만들 필요도 없고 public, static, field를 정의안해도 됨. | ||
|
||
리플랙션, 직렬화, 역직렬화에 굉장히 안정적이다. | ||
|
||
enum은 태생, 근본적으로 new 이렇게 만들 수 없게 함. 리플렉션 API를 통해서 enum에 컨스럭터를 가져오려 해도 getConstructor 이렇게 가져오려고 해도 내부적으로 막아놓음. | ||
|
||
--- | ||
인스턴스 메서드 vs 스테릭 메서드 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
--- | ||
published: true | ||
title: "Effective Java Item 4 - 인스턴스화를 막으려거든 private 생성자를 사용하라" | ||
categories: | ||
- Java | ||
--- | ||
|
||
|
||
|
||
## Abstract를 쓰면 안되는 이유 | ||
상속받은 클래스로 인스턴스를 생성할 수 있고 잘못된 의도를 전달할 수 있습니다. | ||
|
||
아래는 Spring이 제공하는 유틸리티 클래스입니다. 추상클래스로 만들었지만 아래처럼 상속을 받으면 인스턴스화가 가능합니다. | ||
|
||
~~~java | ||
// Spring Util Class | ||
public abstract class PatternMatchUtils { | ||
public static boolean simpleMatch(@Nullable String[] patterns, String str) { | ||
if (patterns != null) { | ||
for (String pattern : patterns) { | ||
if (simpleMatch(pattern, str)) { | ||
return true; | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
public class DefaultUtilityClass extends PatternMatchUtils { | ||
public static void main(String[] args) { | ||
DefaultUtilityClass utilityClass = new DefaultUtilityClass(); | ||
utilityClass.simpleMatch("test", "test"); | ||
} | ||
} | ||
~~~ | ||
|
||
두번째로 추상클래스로 만들면 '상속을 받으라는 의도인가?' 라는 생각이 들 수 있습니다. 따라서 위처럼 상속을 받은 후 인스턴스를 생성할 수 있는 위험이 있습니다. | ||
|
||
|
||
## private 생성자를 사용하는 방법 | ||
|
||
위의 두가지 문제를 해결하기 위해서는 private 생성자와 AssertionError()을 사용해야 합니다. private 생성자를 사용해서 외부에서 인스턴스를 만드는 행위를 막고 AssertionError()를 사용해서 내부에서 인스턴스를 만드는 행위를 막을 수 있습니다. 주석으로 설명을 더해준다면 더욱 좋을 것 같습니다. | ||
|
||
~~~java | ||
public class UtilityClass { | ||
// 인스턴스를 만들면 안됩니다. | ||
private UtilityClass() { | ||
throw new AssertionError(); | ||
} | ||
} | ||
~~~ | ||
|
||
|
||
왜하면 상속 받으면 뚫림. | ||
|
||
abstract를 써서 상속을 해서 써야하나? 라는 착각을 만들 수 있는 여지가 있다. | ||
|
||
그래서 private로 생성자를 막아두자! | ||
|
||
super에 있는 생성자를 더 이상 호출할 수 없으니까 상속 자체가 막히게 된다. | ||
|
||
throw new AssertionError(); 를 써서 막아야 함. -> 예외를 처리하기 위한게 아니라 만나면 안되는 상황임. 혹시라도 이 상황을 만난다면 무조건 에러다! 이 생성자는 쓰이면 안된다. 라는 메시지임. | ||
|
||
단점 -> 몼쓰게 만들려고 왜 코드를 만들지? | ||
|
||
유틸리티 클래스?? | ||
|
||
스프링에서 유틸리티 클래스를 abstract로 해놓았는데 이러면 생성자가 뚫릴 수 있음. | ||
|
||
|
||
|
||
|