From eb601e292502970858299268a325abf56a0ce95b Mon Sep 17 00:00:00 2001 From: Mellona Date: Thu, 7 Nov 2024 04:43:11 +0000 Subject: [PATCH] =?UTF-8?q?GITBOOK-183:=20item=2041=20:=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98=ED=95=98=EB=A0=A4=EB=8A=94=20=EA=B2=83=EC=9D=B4=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=EC=9D=B4=EB=9D=BC=EB=A9=B4=20=EB=A7=88?= =?UTF-8?q?=EC=BB=A4=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4?= =?UTF-8?q?=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- developLog/SUMMARY.md | 1 + .../java/effective-java/6/item-41.md | 308 ++++++++++++++++++ 2 files changed, 309 insertions(+) create mode 100644 developLog/programming-lanuage/java/effective-java/6/item-41.md diff --git a/developLog/SUMMARY.md b/developLog/SUMMARY.md index 64babe4..c2ed96c 100644 --- a/developLog/SUMMARY.md +++ b/developLog/SUMMARY.md @@ -71,6 +71,7 @@ * [item 38 : 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라](programming-lanuage/java/effective-java/6/item-38.md) * [item 39 : 명명 패턴보다 애너테이션을 사용하라](programming-lanuage/java/effective-java/6/item-39.md) * [item 40 : @Override 애너테이션을 일관되게 사용하라](programming-lanuage/java/effective-java/6/item-40-override.md) + * [item 41 : 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라](programming-lanuage/java/effective-java/6/item-41.md) * [스터디에서 알아가는 것](programming-lanuage/java/effective-java/undefined.md) * [모던 자바 인 액션](programming-lanuage/java/modern-java-in-action/README.md) * [chaper 1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?](programming-lanuage/java/modern-java-in-action/chaper-1.-8-9-10-11.md) diff --git a/developLog/programming-lanuage/java/effective-java/6/item-41.md b/developLog/programming-lanuage/java/effective-java/6/item-41.md new file mode 100644 index 0000000..9459404 --- /dev/null +++ b/developLog/programming-lanuage/java/effective-java/6/item-41.md @@ -0,0 +1,308 @@ +# item 41 : 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라 + +## 1. **마커 인터페이스와 마커 애너테이션의 비교 및 활용** + +### **1) 마커 인터페이스란?** + +자바 프로그래밍에서 `마커 인터페이스(marker interface)`란 메서드를 하나도 담고 있지 않으면서, 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 말한다. 대표적인 예로 `Serializable` 인터페이스를 들 수 있다. 이 인터페이스를 구현한 클래스의 인스턴스는 `ObjectOutputStream`을 통해 직렬화할 수 있음을 나타낸다. + +### 2) 마커 애너테이션 + +반면에 `마커 애너테이션(marker annotation)`은 자바 5부터 도입된 애너테이션 기능을 활용하여, 클래스나 메서드 등에 특정 속성을 부여하는 데 사용된다. 예를 들어 `@Deprecated` 애너테이션은 해당 요소가 더 이상 사용되지 않음을 표시한다. + +많은 개발자들이 마커 애너테이션이 등장하면서 마커 인터페이스는 구식이 되었다고 생각할 수 있지만, 실제로는 마커 인터페이스가 여전히 유용한 경우가 있다. + +## **2. 마커 인터페이스의 장점** + +> 마커 어노테이션이 나왔는데 사용해야할 이유가 있을까? + +마커 인터페이스는, 두 가지 면에서 애너테이션보다 장점을 가진다. + +### 1) 마커 인터페이스는 **타입**이기 때문에 애너테이션을 사용했다면 런타임에나 발견될 오류를 **컴파일 타임에 잡을 수 있다.** + +> **즉, 타입으로 사용할 수 있다** + +마커 인터페이스는 이를 구현한 클래스의 인스턴스들을 구분하는 **타입**으로 사용할 수 있다. 이는 마커 애너테이션으로는 불가능한 부분입니다. 타입으로 활용할 수 있다는 것은 컴파일 타임에 타입 체크를 통해 오류를 사전에 방지할 수 있다는 뜻이다. + +예를 들어, 다음과 같이 `Serializable`을 구현한 클래스의 인스턴스를 받아들이는 메서드를 작성할 수 있다. + +이렇게 하면 `Serializable`을 구현하지 않은 객체를 인수로 전달하려고 할 때 컴파일 오류가 발생한다. 즉, **런타임이 아닌 컴파일 타임에 오류를 발견할 수 있어 안정성을 높일 수 있다.** + +```java +public void saveObject(Serializable obj) { + // 객체를 저장하는 로직 +} +``` + +{% hint style="info" %} +**마커 인터페이스**는 어엿한 타입이기 때문에, 마커 애너테 이션을 사용했다면 런타임에야 발견될 오류를 컴파일타임 에 잡을 수 있다 +{% endhint %} + +하지만 자바의 직렬화는 컴파일 타임 오류 검출의 이점을 살리지 못했다. + +![](https://velog.velcdn.com/images/semi-cloud/post/dc4c393f-681a-4dd1-a0a6-dc78da03d3ce/image.png) + +`Serializable` 이 아닌 상위 타입인 `Object` 를 객체로 받도록 설계되어 런타임에야 문제가 발생했다는 것을 알 수 있기 때문이다. + +### **2) 적용 대상을 더 정밀하게 지정할 수 있다** + +> 마커 인터페이스는 특정 클래스 계층에만 적용되도록 제한할 수 있다. + +마크 애너테이션은 적용 대상( `@Target` )을 `Element.Type` 으로 선언한 애너테이션은 모든 타입(클래스, 인터페이스, 열거 타입, 애너테이션)에 달 수 있지만, 부착할 수 있는 타입을 더 세밀하게 제한할 수는 없다. + +반면, **마크 인터페이스**는 그냥 마킹하고 싶은 클래스에서만 그 인터페이스를 구현하면 마킹된 타입은 자동으로 그 인터페이스의 하위 타입임이 보장된다. + +```java +public interface SpecialFeature extends BaseFeature { + // 마커 인터페이스이므로 메서드 없음 +} +``` + +이렇게 하면 `BaseFeature`를 구현한 클래스들 중에서 `SpecialFeature`를 구현한 클래스만을 대상으로 특정 로직을 적용할 수 있다. + +**Set 인터페이스에 대한 생각** + +> `Set 인터페이스`도 일종의 제약이 있는 마커 인터페이스로 볼 수 있다. + +Set은 **Collection의 하위 타입에만 적용할 수 있으며**, Collection이 정의한 메서드 외에는 새로 추가한 것이 없다. 보통은 Set 을 마커 인터페이스로 생각하지 않는데, add, equals, hashCode 등 Collection 의 메서드 몇 개의 규약을 살짝 수정했기 때문이다. `add`, `equals`, `hashCode` 등의 메서드에 대한 동작 규약을 변경하여 **집합(set)의 특성**을 구현했다. 하지만 다시 말하지만 추가적인 메서드는 정의하지 않았다. + +하지만 특정 인터페이스 의 하위 타입에만 적용할 수 있으며, 아무 규약에도 손대지 않은 마커 인터페이스는 충분히 있음직하다. 이런 마커 인터페이스는 객체의 특정 부분을 불 변식(invariant)으로 규정하거나, 그 타입의 인스턴스는 다른 클래스의 특정 메서드가 처리할 수 있다는 사실을 명시하는 용도로 사용할 수 있을 것이다 + +## **3. 마커 애너테이션의 장점** + +### **1) 애너테이션 프로세싱의 지원** + +마커 애너테이션은 자바의 강력한 애너테이션 프로세싱 기능을 활용할 수 있다. 이는 리플렉션(reflection)을 통해 런타임에 애너테이션이 달린 요소를 쉽게 찾고 처리할 수 있게 해준다. + +또한, 애너테이션은 **클래스뿐만 아니라 메서드, 필드, 모듈, 패키지 등 다양한 프로그램 요소에 적용**할 수 있다. 따라서 클래스와 인터페이스 외의 요소에 마킹해야 할 때는 마커 애너테이션을 사용할 수밖에 없다. + +## **4. 마커 인터페이스 vs 마커 애너테이션: 언제 사용해야 할까?** + +* **타입으로 활용해야 하는 경우**: 마커 인터페이스를 사용한다. 이를 통해 컴파일 타임에 타입 체크를 할 수 있고, 해당 마커 인터페이스를 매개변수나 반환 타입으로 사용하여 코드의 안정성을 높일 수 있다. +* **클래스와 인터페이스 외의 요소에 적용해야 하는 경우**: 마커 애너테이션을 사용한다. 메서드나 필드, 모듈 등에 마킹해야 할 때는 애너테이션이 유일한 선택지이다. +* **애너테이션을 적극 활용하는 프레임워크와 통합해야 하는 경우**: 마커 애너테이션을 사용하는 것이 일관성을 유지하는 데 도움이 된다. 예를 들어, 스프링 프레임워크에서는 다양한 애너테이션을 통해 설정과 의존성 주입을 관리하므로, 마커 애너테이션을 사용하는 것이 적절하다. + +## **5. 사용 예제** + +### **1) 마커 인터페이스 사용 예제** + +```java +// 마커 인터페이스 정의 +public interface Archivable { + // 메서드 없음 +} + +// Archivable을 구현한 클래스 +public class Document implements Archivable { + private String content; + // 기타 멤버 +} + +// Archivable을 구현하지 않은 클래스 +public class Image { + private byte[] data; + // 기타 멤버 +} + +// Archivable 타입의 객체만을 처리하는 메서드 +public void archive(Archivable item) { + // 아카이브 로직 +} +``` + +위 코드에서 `archive` 메서드는 `Archivable` 인터페이스를 구현한 객체만을 인수로 받는다. 따라서 `Document` 객체는 전달할 수 있지만, `Image` 객체는 컴파일 타임에 오류가 발생한다. + +### **2) 마커 애너테이션 사용 예제** + +```java +// 마커 애너테이션 정의 +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Test { +} + +// 애너테이션을 적용한 메서드 +public class Calculator { + @Test + public void testAddition() { + // 테스트 코드 + } +} + +// 애너테이션 프로세서를 통해 @Test 애너테이션이 달린 메서드를 실행 +public class TestRunner { + public static void main(String[] args) { + for (Method m : Calculator.class.getDeclaredMethods()) { + if (m.isAnnotationPresent(Test.class)) { + // 테스트 메서드 실행 로직 + } + } + } +} +``` + +위 예제에서 `@Test` 마커 애너테이션은 메서드에 적용되며, 리플렉션을 통해 해당 애너테이션이 달린 메서드를 찾아 실행할 수 있다. + +> 마커 인터페이스와 마커 애너테이션은 각각의 장단점이 있으므로 상황에 맞게 선택하는 것이 중요 + +1. **컴파일 타임 안전성**: 마커 인터페이스는 타입 시스템을 활용하므로, 컴파일 타임에 오류를 발견할 수 있다. 이는 대규모 프로젝트에서 매우 중요하며, 버그를 사전에 방지할 수 있다. +2. **리플렉션 및 메타데이터 활용**: 마커 애너테이션은 런타임에 메타데이터로 활용할 수 있어, 동적인 기능 구현에 유리하다. 특히 테스트 프레임워크나 DI(Dependency Injection) 프레임워크에서 유용하다. +3. **적용 범위**: 마커 인터페이스는 클래스와 인터페이스에만 적용할 수 있지만, 마커 애너테이션은 더 다양한 프로그램 요소에 적용할 수 있다. +4. **의도와 명확성**: 마커 인터페이스를 구현한다는 것은 해당 클래스가 **특정 타입의 일부임**을 명시적으로 나타낸다. 반면에 마커 애너테이션은 해당 요소가 **특정 속성을 가짐**을 나타내지만, 타입으로서의 의미는 없다.. + +> **애너테이션 기반의 프레임워크를 많이 사용하는 현대의 개발 환경에서는 마커 애너테이션의 활용도가 높아지고 있다**. 하지만 타입 안정성과 명확한 의도를 전달하기 위해서는 마커 인터페이스를 사용하는 것이 좋을 때도 있다. + +**언어의 특징과 개발 도구의 발전에 따라 코딩 스타일과 패턴이 변화하지만, 기본 원칙은 변하지 않는다는 것이다**. 마커 인터페이스와 마커 애너테이션은 각각의 목적과 장점이 있으므로, **프로젝트의 요구 사항과 팀의 코딩 컨벤션에 맞게 적절히 활용하는 것이 중요**하다. + +또한, **타입 시스템을 적극적으로 활용하여 안정적인 코드를 작성하는 것의 중요성하다**. 특히 대규모 시스템에서는 작은 오류가 큰 문제로 이어질 수 있으므로, 컴파일 타임에 잡을 수 있는 오류는 최대한 그 단계에서 해결하는 것이 좋다. + +**추가적인 고민** + +* **자바 8 이후의 기능과의 조합**: 람다 표현식이나 스트림 API와 마커 인터페이스 또는 마커 애너테이션을 어떻게 조합하여 활용할 수 있을지 고민해볼 수 있다. +* **다른 언어에서의 적용 사례**: 코틀린이나 스칼라 같은 JVM 언어에서 마커 인터페이스와 마커 애너테이션은 어떻게 활용되는지 비교해보는 것도 흥미로울 것 같다. +* **미래의 방향성**: 애너테이션 프로세싱과 리플렉션의 성능 이슈, 그리고 모듈 시스템의 발전에 따라 마커 인터페이스와 마커 애너테이션의 사용 패턴이 어떻게 변화할지 예측해보는 것도 의미 있을 것이다. + +## 6. 스프링에서의 사용 + +### **1) 마커 애너테이션과 마커 인터페이스의 차이점** + +* **마커 애너테이션**: 클래스, 메서드, 필드 등에 애너테이션을 부여하여 특정 속성을 나타낸다. 스프링에서는 빈 스캐닝이나 AOP 등을 활용할 때 주로 사용한다. +* **마커 인터페이스**: 메서드 없이 인터페이스를 정의하고, 이를 구현함으로써 클래스에 특정 타입을 부여한다. + +### **2) 스프링에서 마커 인터페이스의 사용** + +스프링에서는 마커 인터페이스를 사용하는 경우가 상대적으로 적지만, 필요에 따라 활용할 수 있다. 예를 들어, 특정 타입의 빈만을 처리하고자 할 때 마커 인터페이스를 사용할 수 있다. + +**예시: 마커 인터페이스 사용** + +```java +// 마커 인터페이스 정의 +public interface SpecialService { + // 메서드 없음 +} + +// 마커 인터페이스를 구현한 클래스 +@Service +public class MySpecialService implements SpecialService { + // 구현 내용 +} + +// 마커 인터페이스를 구현하지 않은 클래스 +@Service +public class RegularService { + // 구현 내용 +} +``` + +**특정 타입의 빈만 가져오기** + +```java +@Autowired +private ApplicationContext applicationContext; + +public void processSpecialServices() { + // SpecialService 타입의 빈만 가져옴 + Map beans = applicationContext.getBeansOfType(SpecialService.class); + for (SpecialService service : beans.values()) { + // SpecialService를 구현한 빈에 대한 처리 로직 + } +} +``` + +이렇게 하면 `SpecialService` 인터페이스를 구현한 빈들만을 대상으로 로직을 수행할 수 있다. + +#### **스프링에서 마커 애너테이션의 사용** + +하지만 스프링에서는 마커 인터페이스보다 **마커 애너테이션을 더 많이 사용**한다. 이는 애너테이션이 더 유연하고, 다양한 프로그램 요소에 적용할 수 있기 때문이다. + +**예시: 마커 애너테이션 사용** + +```java +// 마커 애너테이션 정의 +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface SpecialComponent { +} + +// 마커 애너테이션을 적용한 클래스 +@SpecialComponent +@Service +public class MySpecialService { + // 구현 내용 +} + +// 마커 애너테이션을 적용하지 않은 클래스 +@Service +public class RegularService { + // 구현 내용 +} +``` + +**특정 애너테이션이 붙은 빈만 가져오기** + +```java +@Autowired +private ApplicationContext applicationContext; + +public void processSpecialComponents() { + // 모든 빈 이름 가져오기 + String[] beanNames = applicationContext.getBeanDefinitionNames(); + for (String beanName : beanNames) { + Object bean = applicationContext.getBean(beanName); + // 클래스에 @SpecialComponent 애너테이션이 붙어 있는지 확인 + if (bean.getClass().isAnnotationPresent(SpecialComponent.class)) { + // @SpecialComponent가 붙은 빈에 대한 처리 로직 + } + } +} +``` + +또는 스프링 설정에서 **빈 스캐닝 시 필터링**을 통해 특정 애너테이션이 붙은 클래스만 빈으로 등록할 수 있다. + +```java +@Configuration +@ComponentScan( + basePackages = "com.example", + includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = SpecialComponent.class) +) +public class AppConfig { +} +``` + +### **3) 마커 인터페이스 vs 마커 애너테이션: 스프링에서의 활용** + +* **마커 인터페이스** + * 컴파일 타임에 타입 체크 가능 + * 특정 타입의 빈만 주입받을 수 있음 + * 인터페이스를 구현해야 하므로, 클래스가 이미 다른 클래스를 상속하고 있다면 다중 상속이 불가능한 자바의 특성상 제한이 있음 +* **마커 애너테이션** + * 클래스 외에도 메서드, 필드 등에 적용 가능 + * 리플렉션을 통해 런타임에 다양한 처리가 가능 + * 스프링의 빈 스캐닝, AOP 등과의 연동이 용이 + +스프링에서 **특정 빈을 식별하거나 처리하기 위해서는 마커 애너테이션을 사용하는 것이 더 일반적이며 편리하다**. 마커 인터페이스는 컴파일 타임 타입 체크 등의 장점이 있지만, 스프링의 기능과 결합하여 사용하기에는 마커 애너테이션이 더 적합하다. + +따라서 제가 이전에 제시한 예시는 마커 인터페이스보다는 **마커 애너테이션을 활용한 예시**이며, 스프링에서는 이러한 방식을 통해 특정 클래스를 식별하고 처리한다. + +#### **추가적인 설명** + +* **마커 인터페이스의 사용 제한** + * 마커 인터페이스는 클래스에만 적용 가능하며, 인터페이스를 구현해야 하므로 다중 상속의 제약이 있다. + * 스프링에서 빈으로 등록할 때는 클래스패스 스캐닝을 통해 애너테이션 기반으로 처리하는 경우가 많다. +* **마커 애너테이션의 장점** + * 다양한 프로그램 요소에 적용 가능 + * AOP 등의 기능과 결합하여 부가적인 처리를 쉽게 추가할 수 있음 + * 메타데이터로 활용되어 프레임워크와의 통합에 용이 + +### 4) 스프링의 서비스, 서비스impl과 마커 인터페이스 차이 + +| 항목 | 마커 인터페이스 | 스프링의 Service/ServiceImpl | +| --------- | --------------------------- | -------------------------------- | +| 목적 | 클래스에 **특정 속성** 부여 | **비즈니스 로직**의 정의와 구현 분리 | +| 메서드 존재 여부 | 메서드 없음 | 인터페이스에 메서드 시그니처 정의 | +| 사용 방식 | 인터페이스 구현으로 **타입 지정** | 인터페이스를 구현하여 **로직 구현** | +| 예시 | `Serializable`, `Cloneable` | `UserService`, `UserServiceImpl` | + +> 출처 및 참고 +> +> * [https://velog.io/@semi-cloud/Effective-Java-%EC%95%84%EC%9D%B4%ED%85%9C-41-%EC%A0%95%EC%9D%98%ED%95%98%EB%A0%A4%EB%8A%94-%EA%B2%83%EC%9D%B4-%ED%83%80%EC%9E%85%EC%9D%B4%EB%9D%BC%EB%A9%B4-%EB%A7%88%EC%BB%A4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC](https://velog.io/@semi-cloud/Effective-Java-%EC%95%84%EC%9D%B4%ED%85%9C-41-%EC%A0%95%EC%9D%98%ED%95%98%EB%A0%A4%EB%8A%94-%EA%B2%83%EC%9D%B4-%ED%83%80%EC%9E%85%EC%9D%B4%EB%9D%BC%EB%A9%B4-%EB%A7%88%EC%BB%A4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC)