들어가며

우리는 Spring Framework를 사용하며 이미 많은 어노테이션을 사용해왔는데요
많은 자바 개발자분들이 @Controller, @Service, @Repository, @Component 등의 사용에 익숙하실거라 생각합니다.
오늘은 이러한 어노테이션 이외에 메타 어노테이션에 대해 알아보려고 합니다!!


@annotation

자바 어노테이션은 jdk5 부터 추가된 기능입니다. 특별한 기능은 아니지만 Source 코드에 추가적인 정보 (메타 데이터)를 제공하여 줍니다
따라서, @어노테이션을 사용하면 비즈니스 로직에 직접적인 영향을 미치지 않지만, 어노테이션 추가만으로 더 깔끔한 코딩이 가능해집니다


Built-in 어노테이션

빌트인 어노테이션이란 자바에서 기본적으로 제공해주는 어노테이션으로
컴파일러 경고 및 에러를 생성하여 코드를 형식에 맞게 제한하여 줍니다.
해당 어노테이션들은 java.lang 패키지에 속하며, 아래에 보이는 6개의 어노테이션이 존재합니다. 다들 많이 사용해 보셨을 거라 생각하고 설명은 생략하도록 하겠습니다

@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface, @Native


메타어노테이션

Target

@Target annotation은 해당 어노테이션의 적용범위를 Java Compilrer가 설정하는 것을 적용해주는 역할을 합니다. ElementType enum으로 범위를 설정해주어야 합니다. 설정 범위는 아래와 같습니다

ElementType.TYPE : 클래스, 인터페이스, enum
ElementType.FIELD : 필드
ElementType.METHOD : 메서드
ElementType.PARAMETER : 파라미터
ElementType.CONSTRUCTOR : 생성자
ElementType.LOCAL_VARIABLE : 지역 변수 
ElementType.ANNOTATION_TYPE : 어노테이션 
ElementType.PACKAGE : 패키지
ElementType.TYPE : 클래스, 인터페이스, enum
ElementType.TYPE_PARAMETER : 타입 파라미터
ElementType.TYPE_USE : 타입
ElementType.MODULE : 모듈
  • @Target()의 value로 ElementType을 지정하지 않으면 오류가 납니다

@Retension

@Retension 어노테이션은 해당 어노테이션이 적용되고 유지되는 범위를 설정해줍니다.
RetentionPolicy enum으로 범위를 설정해주어야 합니다. 설정 범위는 아래와 같습니다

RetentionPolicy.RUNTIME : 런타임
RetentionPolicy.CLASS : 컴파일
RetentionPolicy.SOURCE : 컴파일 전 소스레벨

Jvm 환경에서 실제로 사용하기 위해서는 RUNTIME을 적용해주어야 합니다.


@Documented

JavaDoc 생성시 Annotation에 대한 정보도 함께 생성하여 줍니다


참조

java.lang.annotation (Java Platform SE 8 )
Java Meta Annotation 메타 어노테이션 :: daily

블로그 이미지

yhmane

댓글을 달아 주세요

40 @Override 애너테이션을 일관되게 사용하라

@Override는 메서드 선언에만 달 수 있으며, 이 애너테이션이 달렸다는 것은 상위 타입의 메서드를 재정의했음을 뜻합니다.
애너테이션을 일관되게 사용하면 여러 가지 악명 높은 버그들을 예방해줍니다

public class Bigram {
    private final char first;
    private final char second;

    public Bigram(char first, char second) {
        this.first = first;
        this.second = second;
    }

    public boolean equals(Bigram bigram) {
        return bigram.first == this.first &&
                bigram.second == this.second;
    }

    public int hashCode() {
        return 31 * first + second;
    }

    public static void main(String[] args) {
        Set<Bigram> s = new HashSet<>();
        for (int i = 0; i < 10; i++) {
            for (char ch = 'a'; ch <= 'z'; ch++) {
                s.add(new Bigram(ch, ch));
            }
        }

        System.out.println(s.size());
    }
}
  • Main () 메서드를 살펴보면 바이그램 26개를 10번 반복해 집합에 추가한 다음 그 집합의 크기를 출력합니다
  • Set은 중복을 허용하지 않으니 26이 출력될거 같지만 실제로는 260이 출력됩니다
    • -> 여기서 equals를 재정의한게 아니라 다중정의를 하였습니다
  • HashSet은 내부적으로 equals 메서드를 기반으로 객체의 논리적 동치적(equals) 검사를 실시합니다
  • 하지만 equals메서드의 파라미터 타입이 Bigram입니다
    • 즉, equals 메서드를 재정의 한게 아니라 Overloading 하였습니다
    • equals를 재정의 하려면 파라미터 타입이 Object이어야 합니다
@Override
public boolean equals(Object bigram) {
    if(!(o instanceof Bigram)) {
        return false;
    }
    Bigram b = (Bigram) bigram;
    return b.first == this.first &&
        b.second == this.second;
}

정리

  • 상위 클래스의 메서드를 재정의 하는 모든 메서드에 @Override 애너테이션을 다는게 좋습니다
  • 인터페이스를 상속한 구체 클래스인데 아직 구현하지 않은 추상 메서드가 남아있다면 컴파일러가 알려줍니다
  • Java 8 부터 Default 메서드의 사용이 가능해 지면서, 인터페이스의 메서드를 재정의 할 때도 사용할 수 있습니다
  • 추상클래스나 인터페이스에서는 상위 클래스나 상위 인터페이스를 재정의하는 모든 메서드에 @Override를 다는것이 좋습니다
블로그 이미지

yhmane

댓글을 달아 주세요

38. 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

타입 안전 열거 패턴은 확장이 가능하나, 열거 타입은 확장을 할 수 없습니다.
하지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있습니다

타입 안전 열거 패턴(typesafe enum pattern)

enum이 나오기전 jdk 1.5 밑에 버전에서는 아래와 같이 사용하였습니다

public final class Shape {

    private String polygon;

    private Shape() {
    }

    private Shape(String polygon) {
        this.polygon = polygon;
    }

    public static final Shape TRIANGLE = new Shape("triangle");
    public static final Shape RECTANGLE = new Shape("rectangle");
    public static final Shape PENTAGON = new Shape("pentagon");
}

열거타입 (enum)

Enum class cannot inherit from classes

public enum Shape {
    TRIANGLE, RECTANGLE, PENTAGON
}
  • 열거 타입은 타입 안전 열거 패턴보다 우수합니다
  • 단, 타입 안전 열거 패턴은 확장이 가능하나 열거 타입은 확장할 수 없습니다
  • 열거 타입을 확장하려면 열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용하면 됩니다

인터페이스를 이용한 확장 가능 열거 타입

public interface Operation {
    double apply(double x, double y);
}
public enum BasicOperation implements Operation {

    PLUS("+") {
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS("-") {
        @Override
        public double apply(double x, double y) {
            return x - y;
        }
    },
    TIMES("*") {
        @Override
        public double apply(double x, double y) {
            return x * y;
        }
    },
    DIVIDE("/") {
        @Override
        public double apply(double x, double y) {
            return x / y;
        }
    };

    private final String symbol;

    BasicOperation(String symbol) {
        this.symbol = symbol;
    }
}

열거 타입인 BasicOperation은 확장할 수 없습니다.
다만, 인터페이스인 Operation을 이용해 확장할 수 있고
이 인터페이스를 연산의 타입으로 이용할 수 있습니다

다른 열거 타입을 추가

public enum ExtendedOperation implements Operation {

    EXP("^") {
        @Override
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        @Override
        public double apply(double x, double y) {
            return x % y;
        }
    };

    private final String symbol;

    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }
}

Operation 인터페이스를 구현하면 다른 열거 타입에서도 확장할 수 있습니다


enum 타입 확인

  1. Enum타입을 넘겨 순회하기 (class literal 넘기기)
    public static void main(String[] args) {
     double x = 10;
     double y = 2;
     test(ExtendedOperation.class, x, y);
    }
    

public static <T extends Enum & Operation> void test(Class opEnumType, double x, double y) {

for (Operation op : opEnumType.getEnumConstants()) {
    System.out.printf(“%f %s %f = %f%n”, x, op, y, op.apply(x, y));
}

}


ExtendedOperation의 class 리터럴

<T extends Enum<T> & Operation> 
 타입이 Enum타입이면서, Operation을 구현하는 클래스

10 ^ 2 = 100
10 % 2 =0

2. 열거 타입의 리스트 넘기기
```java
public static void main(String[] args) {
    double x = 10;
    double y = 2;
    test(Arrays.asList(ExtendedOperation.values()), x, y);
}

public static void test(Collection<? extends Operation> opSet, double x, double y) {
    for (Operation op : opSet) {
        System.out.printf(“%f %s %f = %f%n”, x, op, y, op.apply(x, y));
    }
}

열거 타입의 리스트를 넘겨 <? extends Operation>인 한정적 와일드 카드 타입으로 지정

10 ^ 2 = 100
10 % 2 =0

정리

  • 열거 타입끼리 구현을 상속 할수는 없습니다
  • 확장할 수 있는 열거 타입이 필요한 경우 인터페이스 정의를 구현합니다
블로그 이미지

yhmane

댓글을 달아 주세요

어느덧 21년을 마무리하게 되었고,

작년을 되돌아보며 회고하는 시간을 가지려 합니다 

 

1. 이직 🤗

1월 퇴사를 하고 2월달 이직을 하게 되었습니다

스타트업의 대한 환상이 와르르 깨졌던 시기였습니다 😭

 

육체적, 정신적으로 많이 힘들었기에 이직을 고민하게 되었고,

때마침 지인분께서 제안을 주셨고, 이직을 하게 되었습니다

 

자세한 내용은 말씀드릴 수 없지만,

건강을 해치는 선택은 피하시길 바랍니다🙏


2. 개인의 성장 🦾

이직을 하게 되면서 많은 경험을 할 수 있었습니다 🥰

이전 경험했던 회사들과는 트래픽 양이 달랐기에

팀에서도 기술적으로 많은 시도를 하고 있었습니다

 

엘라스틱서치 클러스터를 운영하는게 좋은 경험이 되었고,

스프링배치, 코틀린, 테스트컨테이너, 카프카, 레디스, 엘라스틱서치 등

다방면으로 아키텍쳐를 설계하고 코드에 접할수 있었던 점이 좋았습니다

 

뿐만 아니라 팀원분들이 성장에 목말라 있었고, 

코드리뷰에 대해서도 적극적으로

임해주셨기에 감사하게 생각하고 있습니다!!! 🥰


3. 스터디 ✍️

스터디는 사람마다 다른 주제일 거 같습니다

혼자 공부하는걸 선호하시는분도 있고,

여러 사람들이 만나 스터디를 하는걸 좋아하는 사람도 있는데

저는 후자입니다!!

 

사실, 혼자 책상에 앉아 공부하는 걸 좋아하지 않습니다

공부할 때도 가능하면 카페에서 하는걸 선호하네요 ㅎㅎ

 

운좋게도 팀에서 스터디를 진행하고 있어서

오브젝트, 엘라스틱서치를 공부할 수 있었습니다!

 

최근엔 우아한스터디락에서 이펙티브자바를 다같이 공부하고 있는데요 

회사 일정으로 너무 바빠서 잘 참여하지 못하고 있는게 아쉬울 뿐입니다 ㅠㅠ

12월에 4개월동안 준비한 tf 배포건이 있어서 너무 바빴지만…

1월부터는 열심히 달려 볼려고 합니다!!


4. 21년, 읽은책 📚

  • 오브젝트
  • 모던 자바 인 액션
  • 클린코드
  • 엘라스틱서치 실무 가이드

 

개인적으로 오브젝트라는 책이 정말 훌륭한거 같네요!!


5. 22년도, 앞으로 읽을책📚

  • 이펙티브자바 3판 (읽고 있음)
  • HTTP 완벽 가이드
  • DDD start!
  • 실전! 스프링 5를 활용한 리액티브 프로그래밍
  • 아파치 카프카 애플리케이션 프로그래밍 with 자바

 

(스프링 개발자 책 추천해주세요!!)


6. 아쉬웠던 부분 & 목표 🤭

영어공부와 운동을 꾸준히 하겠다고 다짐 했었는데

지키지 못했습니다 ... 너무 게으른 제 자신을 반성합니다 ... ㅎㅎ

 

외국인과 데일리로 가볍게 통화할 수 있는 프로그램이 있다는데 한번 알아보려 합니다!!

운동은 예전부터 꾸준히 하려고 다짐했었는데 ...

올해는 꼭 몸은 만들어 보려고 합니다!! 다행히 헬스장은 등록했네요!! 

 

 

 

 

 

 

 

 

 

블로그 이미지

yhmane

댓글을 달아 주세요

28. 배열보다는 리스트를 사용하라

배열과 제네릭 타입에는 중요한 차이가 두가지 있습니다

1. 배열은 공변, 제네릭은 불공변입니다

// 배열, ArrayStoreException, RunTime 오류
Object[] objectArray = new Long[1];
objectArray[0] = “타입이 달라 넣을 수 없다”; 

// 리스트, 컴파일 되지 않는다
// List<Object> objectList = new ArrayList<Long>(); 

배열은 sub가 super의 하위타입이라면 sub[]는 배열 super[]의 하위타윕이 됩니다. 따라서, 위에 Long에 String을 입력하여도 컴파일 오류가 나지 않았습니다.

반면, 리스트 List은 List의 하위타입도 상위 타입도 아닙니다. 따라서, 위의 코드에서 컴파일 오류가 났습니다

2. 배열은 실체화가 되고, 리스트는 그렇지 않습니다

배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인합니다.
반면 제네릭은 타입정보가 런타임에는 소거됩니다. 이 두 차이로 인해 배열과 제네릭은 어울러질 수 없습니다

List<String>[] stringLists = new List<String>[1];

정리

타입 세이프하고, 런타임이 아닌 컴파일 단계에서 에러를 잡아줍니다. 다만, 성능으로는 배열이 유리하지만 웬만하면 리스트를 사용하도록 합니다

참조

effective java 3/e

블로그 이미지

yhmane

댓글을 달아 주세요

27. 비검사 경고를 제거하라

제네릭을 사용하기 시작하면 수많은 컴파일러 경고들을 마주치게 됩니다
이러한 경고들을 가능한 많이 제거하는 것이 습니다
경고들을 모두 제거한다면, 그 코드는 타입 안정성이 보장되기 때문입니다

대부분의 비검사 경고는 쉽게 제거할 수 있습니다

Set<Car> cars = new HashSet();
Venery.java:4: warning: [unchecked] unchecked conversion
                Set<Car> cars = new HashSet();
                                ^
  required: Set<Car>
  found:    HashSet

컴파일러가 알려준 대로 수정하면 경고가 사라집니다

자바7 이후 제공하는 다이아몬드 연산자<>로 해결이 가능합니다

Set<Car> cars = new HashSet<>();

다만, 제거하기 훨씬 어려운 경고들도 있습니다.

곧바로 해결되지 않는 경고가 나타나더라도 할 수 있는한 모든 비검사 경고를 제거하도록 합니다

경고를 제거할 수 없지만 타입이 안전하다고 확신할 수 있으면,  @SuppressWarnings(“unchecked”)를 달아 경고를 숨기도록 합니다

@SuppressWarnings

  • 지역변수 ~ 클래스 전체까지 모두 선언 할 수 있습니다
    • 가능한 가장 좁은 범위에 적용하도록 합니다
    • 또한, 클래스 전체에 적용하지 않도록 합니다
  • 한줄이 넘는 메서드나 생성자에 달린 @SuppressWarnings은 지역변수 선언 쪽으로 옮기도록 합니다
    • 이를 위해 지역변수를 새로 선언하는 수고를 해야할 수 있지만, 그만한 값어치가 있습니다
  • 또한 @ SuppressWarnings 사용시 안전한 이유를 항상 주석으로 남겨두도록 합니다 
  • public <T> T[] toArray(T[] a) { if (a.length < size) { // 생성한 배열과 매개변수로 받은 배열의 타입이 모두 T[]로 같으므로 올바른 형변환입니다 @SuppressWarnings("unchecked") T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass()); return result; } System.arraycopy(elements, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }

참조

effective java 3/e

블로그 이미지

yhmane

댓글을 달아 주세요

25. 톱레벨 클래스는 한 파일에 하나만 담아라

우리가 사용하는 파일 내에 하나만 존재하는 클래스를 톱레벨 클래스라고 합니다.
(중첩클래스는 top 레벨 클래스가 아닙니다)

하지만, 소스파일 하나에 여러개의 톱레벨 클래스가 있다면 심각한 위험을 감수해야 합니다

문제

Utensil.java

class UtenSil {
    static final String NAME = "pan";
}

class Dessert {
    static final String NAME = "cake";
}

Dessert.java

class UtenSil {
    static final String NAME = "pot";
}

class Dessert {
    static final String NAME = "pie";
}

여기서 아래의 main 함수를 실행해보자

class Main {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }
}

일반적으론 중복 클래스가 존재한다고 컴파일 에러가 나지만,
javac Main.java Utensil.java의 명령으로 컴파일 한다면 ‘pancake’가
Javac Main.java Dessert.java의 명령으로 컴파일 한다면 ‘potpie’가 출력될 것입니다

해결방안

정적멤버 클래스 또는 톱레벨 클래스를 서로 다른 파일로 분리

정적멤버클래스

class Main {
    public static void main(String[] args) {
        System.out.println(Utensil.NAME + Dessert.NAME);
    }

    static class Utensil {
        static final String NAME = "pan";
    }

    static class Dessert {
        static final String NAME = "cake";
    }
}

톱레벨 클래스로 분리

class Utensil {
    static final String NAME = "pan";
}
class Dessert {
    static final String NAME = "cake";
}

참조

effective java 3/e

블로그 이미지

yhmane

댓글을 달아 주세요

24. 멤버 클래스는 되도록 static으로 만들라

중첩클래스란

중첩 클래스(nested class) 다른 클래스 안에 정의된 클래스를 말합니다.
중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱 레벨 클래스로 만들어야 합니다

  • 정적 멤버 클래스
  • (비정적) 멤버 클래스
  • 익명 클래스
  • 지역클래스

이 중에서 정적 멤버 클래스를 제외한 나머지를 내부 클래스 (inner class)라고 합니다

정적 멤버 클래스

정적 멤버 클래스는 바깥 클래스의 private 멤버에도 접근할 수 있다는 점을 제외하고
일반 클래스와 쓰임새는 동일합니다

public class OuterWithStaticClass {

    private String name;

    static class StaticClass {
        void hello() {
            OuterWithStaticClass outerWithStaticClass = new OuterWithStaticClass();
            outerWithStaticClass.name = "홍길동";
            System.out.println(outerWithStaticClass.name);
        }
    }
}
public class Item24App {

    public static void main(String[] args) {

        OuterWithStaticClass.StaticClass staticClass = new StaticClass();
        staticClass.hello();
    }
}

비정적 멤버 클래스

구문상으로는 정적 클래스와 static이 붙어있고 없고의 차이입니다.
하지만 의미상으로 비정적 멤버 클래스는 바깥 클래스의 인스턴스와 암묵적으로 연결됩니다. 따라서 정규화된 this를 사용해 바깥 인스턴스의 메서드를 호출하거나 참조를 가져올 수 있습니다

public class OuterWithNoneStaticClass {

    private final String name;

    public OuterWithNoneStaticClass(String name) {
        this.name = name;
    }

    public String getName() {
        NonStaticClass nonStaticClass = new NonStaticClass("noneStatic-class : ");
        return nonStaticClass.getNameWithOuter();
    }

    private class NonStaticClass {
        private final String noneStaticName;

        public NonStaticClass(String noneStaticName) {
            this.noneStaticName = noneStaticName;
        }

        public String getNameWithOuter() {
            return noneStaticName + OuterWithNoneStaticClass.this.name;
        }
    }
}
public class Item24App {

    public static void main(String[] args) {
        OuterWithNoneStaticClass noneStaticClass = new OuterWithNoneStaticClass("고길동");
        System.out.println(noneStaticClass.getName());
    }
}

드물게 직접 바깥 인스턴스의 클래스.new Member Class(args)를 호출해 수동으로
만들기도 합니다. 하지만 이 관계 정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성시간도 더 걸립니다.

따라서, 중첩 클래스의 인스턴스가 바깥 클래스의 인스턴스와 독립적으로 존재해야 한다면 정적 멤버 클래스로 만드는게 좋습니다.

익명 클래스

  • 이름이 없고 바깥 클래스의 멤버도 아닙니다
  • 쓰이는 시점에 선언과 동시에 인스턴스가 만들어집니다
  • 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스 참조가 가능하다
  • 상수 정적변수 (static final) 외에는 정적 변수를 가질 수 없다.
public class Anonymouss {

    private String name;

    public void hello() {
        HelloBot helloBot = new HelloBot() {
            @Override
            public void hello() {
                System.out.println("안녕하세요. 헬로우 봇입니다");
            }
        };
        helloBot.hello();
    }
}

interface HelloBot {
    void hello();
}
public class Item24App {

    public static void main(String[] args) {
        Anonymouss anonymouss = new Anonymouss();
        anonymouss.hello();
    }
}

익명 클래스의 제약사항

  • 선언한 지점에서만 인스턴스를 만들수 있습니다
  • 여러 인터페이스를 구현할 수 없고, 구현과 동시에 다른클래스 상속도 불가능합니다
  • 익명 클래스 사용 클라이언트는 사용하는 익명 클래스가 상위타입에서 상속한 멤버외에는 호출이 불가능합니다
  • 람다(자바7) 등장 이전에는 작은 함수 객체나 처리 객체 구현에 사용되었지만 java8 부터는 람다를 제공합니다

지역 클래스

네 가지 중첩 클래스 중 가장 드물게 사용됩니다.
지역 클래스는 지역 변수를 선언할 수 있는 곳이면 어디서든 선언할 수 있고, 유효 범위도 지역 변수와 같습니다.

public class LocalClass {

    public void hello() {
        class LocalExample {
            private String name;

            public LocalExample(String name) {
                this.name = name;
            }

            public String getName() {
                return name;
            }
        }

        LocalExample localExample = new LocalExample("윤호호");
        System.out.println(localExample.getName());
    }
}
public class Item24App {

    public static void main(String[] args) {
        LocalClass localClass = new LocalClass();
        localClass.hello();
    }
}

지역 클래스는 다른 중첩 클래스들의 공통점을 하나씩 가지고 있다.

  • 멤버 클래스처럼 이름을 가질수 있고 반복해서 사용할 수 있습니다
  • 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있습니다
  • 정적 멤버는 가질 수 없으며, 가독성을 위해 짧게 작성되어야 합니다
블로그 이미지

yhmane

댓글을 달아 주세요

23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라

태그 달린 클래스

태그 클래스란, 두가지 이상의 의미를 표현할 때 그 중 현재 표현하는 의미를 태그값으로 알려주는 클래스 입니다.

책에 나온 예제로 살펴보도록 하겠습니다.

class Figure {

    enum Shape {
        RECTANGLE, CIRCLE
    }

    // 태그 필드
    final Shape shape;

    // RECTANGLE 용 필드
    double length;
    double width;

    // CIRCLE 용 필드
    double radius;

    // RECTANGLE
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    // CIRCLE
    Figure(double radius) {
        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);
        }
    }
}

태그 클래스의 단점

  1. Enum switch, field 등 여러 구현이 혼합 되어 있습니다
  2. 이로 인해 가독성이 떨어집니다
  3. 사용하지 않는 필드로 인해 메모리도 추가적으로 사용합니다
  4. 또 다른 타입을 추가하게 되면 switch 문의 수정이 일어납니다
  5. 인스턴스 타입만으로 객체의 의미를 알 수 없습니다

태그가 달린 클래스는 코드가 길어지고 확장성에 취약합니다.
또한, 태그가 추가 될 때마다 사용하지 않는 필드들이 추가 될 수도 있습니다.


계층 클래스

계층 구조의 클래스를 만드는 방법

  1. 계층구조의 최상위(root)가 될 추상클래스를 정의합니다
  2. 태그값에 따라 달라지는 동작(메서드)들을 최상위 클래스의 추상 메서드로 선언합니다
  3. 태그값에 상관없이 동작이 일정한 메서드는 최상위 클래스에 일반 메서드로 정의합니다.
  4. 모든 하위 클래스에 공통으로 사용하는 상태값(필드)들은 루크 클래스에 정의합니다.

계층구조로 변환

abstract class Figure {
    abstract double area();
}

class Rectangle extends Figure {

    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width  = width;
    }

    @Override
    double area() {
        return length * width;
    }
}


class Circle extends Figure {

    final double radius;

    Circle(double radius) {
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * (radius * radius);
    }
}

클래스 계층구조의 장점

  1. 독립된 의미를 가지는 상태값(필드)들이 제거 되어 각 구현 클래스는 간결해집니다
  2. 살아남은 field는 모두 final이므로 정의할 수 있습니다
  3. 실수로 빼먹은 case 문으로 인해 runTime 에러를 방지할 수 있습니다
  4. 최상위 클래스를 수정하지 않고 타입을 확장 할 수 있습니다
  5. 타입사이의 자연스러운 계층 관계를 반영할 수 있습니다
블로그 이미지

yhmane

댓글을 달아 주세요

22. 인터페이스는 타입을 재정의하는 용도로만 사용하라

인터페이스는 타입을 정의하는 용도로만 사용해야 합니다.
상수 공개용 수단으로 사용해서는 안됩니다

안티패턴 - 상수 인터페이스

메서드 없이, 상수를 뜻하는 static final 필드로만 가득찬 인터페이스

public interface PhysicalConstants {

    // 아보가드로 수
    static final double AVOGADROS_NUMBER = 6.022_140_857e23;
    // 볼츠만 상수
    static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
    // 전자 질량
    static final double ELECTRON_MASS = 9.109_383_56e-31;
}
  • 클래스 내부에서 사용하는 상수는 외부 인터페이스가 아닌 내부 구현입니다
    • 내부 구현을 클래스의 API로 노출하는 행위가 됩니다
    • 또한, 사용자에게 혼란과 오해의 소지를 줄 뿐입니다

대안

클래스의 상수로 선언하여 사용합니다

public class PhysicalConstants {

    // 인스턴스화 방지
    private PhysicalConstants(){}

    static final double AVOGADROS_NUMBER = 6.022_140_857e23;
    static final double BOLTZMANN_CONSTANT = 1.380_648_52e-23;
    static final double ELECTRON_MASS = 9.109_383_56e-31;
}

또는 enum의 값으로 사용합니다

public enum PhysicalConstants {

    AVOGADROS_NUMBER(6.002_140_857e23),
    BOLTZMANN_CONSTANT(1.380_648_52e-23),
    ELECTRON_MASS(9.109_383_56e-31);

    private double value;

    PhysicalConstants(double value) {
        this.value = value;
    }

    public double getValue(){
        return value;
    }
}

참조

effective java 3/e - 조슈아 블로크

블로그 이미지

yhmane

댓글을 달아 주세요