연관관계 매핑

객체 연관관계와 테이블 연관관계의 차이점

  • 객체는 참조(주소)값을 기준으로 연관관계를 맺는다.(단방향)
    ex) X->Y , Y->X
  • 테이블은 외래키를 기준으로 연관관계를 맺는다. (JOIN operation , 양방향)
    ex) X JOIN Y 는 Y JOIN X 도 가능하다.

단방향 연관관계

  • 두 entity 중 어느 한쪽만 다른 한쪽을 참조하는 경우을 단방향이라고 한다.
Read more

Item41. 정의하려는 것이 타입이라면 marker interface를 사용하라

  • marker interface : 아무 method도 없으며, 단지 자신을 구현하는 class가 특정 속성을 가짐을 표시해주는 인터페이스

ex) Serializable interface : 직렬화 가능함을 표시

marker interface 의 장점

marker interface는 2가지 측면에서 marker annotation 에 비해 유리하다.

  1. 타입으로 사용 가능하다
  2. 적용대상을 더 정밀하게 지정이 가능하다.

특정 인터페이스를 구현한 class에만 적용하고 싶은 marker interface 가 있다고 하면 해당 인터페이스를 확장하면 된다.

Read more

Item40. @Override annotation을 일관되게 사용하라

@Override

Java가 기본으로 제공하는 annotation중 @Override는 상위 타입 method를 재정의하였을때 달릴 수 있다.

@Override annotation을 사용함으로 여러 가지 버그들을 컴파일 시점에 예방해 줄 수 있다.

예를 들어 다음과 같은 영어 알파벳 2개로 구성된 문자열을 표현하는 클래스가 있다고 할때, main method에서 26개의 소문자를 set에 넣어준뒤 출력하면 당연히 26개의 size가 출력되야하지만 실제로는 260 개의 size가 나온다.

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
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 o) {
return first == o.first && second == o.second;
}
public int hashCode() {
return Objects.hash(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()); // 260
}
}

그 이유는 바로 equals method를 실수로 parameter 를 Bigram type으로 받아 overrloading하였기 때문이다. @Override 를 붙여주면 바로 compile error를 보여준다.

따라서 상위 class의 method를 재정의하는 모든 method는 @Override annotation을 달 것을 권고한다.

Read more

Item39. 명명패턴보다는 annotation을 사용하라

도구나 프레임워크가 특별히 다뤄야 할 프로그램 요소에는 구분되는 명명 패턴을 적용해왔다 예를 들면 JUnit 은 버전 3까지 테스트 method 이름을 test로 시작하게끔 하였다.

명명패턴의 단점

명명 패턴의 단점은 다음과 같다.

  1. 오타시 runtime 예외가 발생
  2. 올바른 프로그램 요소에 사용됨을 보장하지 못함

예를 들면 클래스 이름에 TestSafetyMechanism이라고 명명한다고 하여도 해당 class에 있는 method들은 실행되지 않는다.
3. 매개변수 전달 방법이 없음

특정 예외가 터져야 성공하는 test가 있을때 매개변수로 예외 class type을 전달해줄수 없다.

Annotaion

Junit 4부터는 annotation을 명명패턴 대신에 도입하였다.

annotation에 관한 기본내용은 다음과 같다.

  • Meta-annotation : @Retention, @Target과 같은 annotation 선언에 다는 annotation
  • @Retention
    • SOURCE : 소스코드까지만 annotation이 남아있고, compiler에 의해 .class file에는 제거된다.
    • CLASS : class file 까지 남아있고, run time 시에는 사라진다. (reflection 불가) , default값
    • RUNTIME : run time까지도 남아있는다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      package java.lang.annotation;
      public enum RetentionPolicy {
      /**
      * Annotations are to be discarded by the compiler.
      */
      SOURCE,
      /**
      * Annotations are to be recorded in the class file by the compiler
      * but need not be retained by the VM at run time. This is the default
      * behavior.
      */
      CLASS,

      /**
      * Annotations are to be recorded in the class file by the compiler and
      * retained by the VM at run time, so they may be read reflectively.
      *
      * /
      RUNTIME
      }
Read more

Item38. 확장할 수 있는 Enum type이 필요하면 interface를 사용하라

enum type은 상속이 불가능하다.

확장한 타입의 원소는 기반 타입으로 받을 수 있지만, 반대로 기반 타입은 확장한 타입으로 받을 수 없으며, 기반 타입과 확장한 타입들의 원소를 모두 순회할 방법도 마땅치 않다.

enum type의 확장

그럼에도 불구하고 enum type의 확장시 유용한 경우가 종종 있는데, 그 예중에 하나가 연산 코드이다.

enum type은 상속이 불가능한 대신 interface 구현은 가능하다. 따라서 interface를 통해 간접적으로 확장이 가능하다.

client 가 접근할 interface를 두고, 그 interface의 구현체별로 enum type을 정의하는 방법이다.

client가 접근할 operation interface를 두고 operation interface의 구현체로 enum class를 생성하였다.

Read more

Item37. ordinal indexing 대신 EnumMap을 사용하라

다음과 같이 식물 class가 있고 이 class를 LifeCycle enum type을 key로 set에 분류해서 담고 싶다고 가정하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Plant {

enum LifeCycle{ANNUAL,PERENNIAL , BIENNIAL}

final String name;
final LifeCycle lifeCycle;

public Plant(String name, LifeCycle lifeCycle) {
this.name = name;
this.lifeCycle = lifeCycle;
}

@Override
public String toString() {
return name;
}
}

한가지 방법은 Set 배열에 ordinal method()로 가져온 LifeCycle Enum type의 index 값으로 배열에 indexing 해 저장하는 것이다.

1
2
3
4
5
6
7
8
9
10
11
Set<Plant>[] plantsByLifeCycle = (Set<Plant>[]) new Set[Plant.LifeCycle.values().length];
for(int i = 0 ; i <plantsByLifeCycle.length; i++){
plantsByLifeCycle[i] = new HashSet<>();
}
for (Plant p : garden) {
plantsByLifeCycle[p.lifeCycle.ordinal()].add(p);
}

for(int i = 0; i< plantsByLifeCycle.length ; i++){
System.out.printf("%s: %s %n", Plant.LifeCycle.values()[i],plantsByLifeCycle[i]);
}

위 코드는 다음과 같은 문제점을 가지고 있다.

  1. Generic 배열 : 타입안전하지 않음
  2. 배열은 각 Index가 무슨 Enum type인지 모름
  3. 상수의 위치가 변경될 경우 바로 고장남 (Item 35. ordinal() method는 사용하지 말라고 권고 )

EnumMap

EnumMap을 쓰면 위와 같이 ordinal method로 indexing 하는 단점을 제거해주고, 추가로 출력 문자열도 자체로 제공해준다.
EnumMap은 runtime에서 generic type 정보 제공을 위해 생성자에서 key 로 사용할 class 객체를 받는다.

1
2
3
4
5
6
7
8
Map<Plant.LifeCycle, Set<Plant>> plantsByLifeCycle = new EnumMap<>(Plant.LifeCycle.class);
for(Plant.LifeCycle lc : Plant.LifeCycle.values()){
plantsByLifeCycle.put(lc,new HashSet<>());
}
for (Plant p : garden) {
plantsByLifeCycle.get(p.lifeCycle).add(p);
}
System.out.println("plantsByLifeCycle = " + plantsByLifeCycle);
Read more

Item36. 비트 필드 대신 EnumSet을 사용하라

열거한 값들이 주로 집합으로 사용될 경우, 예전에는 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴을 사용해왔다.

다음과 같이 비트별 OR를 사용해 여러 상수를 하나의 집합으로 모을 수 있으며, 이렇게 만들어진 집합을 bit field라 한다.

1
2
3
4
5
6
7
8
9
10
public class Text {

public static final int STYLE_BOLD = 1 << 0;
public static final int STYLE_ITALIC = 1 << 1;
public static final int STYLE_UNDERLINE = 1 << 2;
public static final int STYLE_STRIKETHROUGH = 1 << 3;
// 매개변수 styles는 0개 이상의 STYLE_상수를 비트별 OR한 값
public void applyStyles(int styles){...}

}

bit field의 문제점은 비트별 연산을 통해 집합 연산은 효율적으로 수행할 수 있으나, 정수 열거 상수의 단점들에 추가로 비트 필드값이 그대로 출력되면 해석하기 더 어렵다는 단점이 있다.

Read more

Item35. Enum type ordinal method 대신 member field를 사용하라

Enum type의 ordinal method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns the ordinal of this enumeration constant (its position
* in its enum declaration, where the initial constant is assigned
* an ordinal of zero).
*
* Most programmers will have no use for this method. It is
* designed for use by sophisticated enum-based data structures, such
* as {@link java.util.EnumSet} and {@link java.util.EnumMap}.
*
* @return the ordinal of this enumeration constant
*/
public final int ordinal() {
return ordinal;
}

대부분 열거타입 상수는 하나의 정수값과 대응되는데, ordinal method는 특정 상수가 그 열거타입에서 몇번쨰 위치인지를 반환하는 method이다. 예를 들면 아래와 같이 합주단 종류 enum type내에서 몇번쨰 위치하고 있는지 알아낼때 사용한다.

1
2
3
4
5
6
7
8
9
public enum Ensemble {

SOLO,DUET,TRIO,QUARTET,QUINTET,
SEXTET,SEPTET,OCTET,NONET,DECET;

public int positionOfMusicians(){
return ordinal()+1;
}
}
Read more

Item34. int 상수 대신 열거 타입을 사용하라

Enum Type 정의

Enum type은 일정 개수의 상수 값을 정의한 다음 그 외의 값들은 허용하지 않는 타입이다. Enum Type은 다음과 같은 특징을 가지고 있다.

1
2
3
4
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}

(https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html)

  1. enum type 자체는 java.lang.Enum 추상 class를 상속받고 있는 class이다. (Java에서는 중복 상속이 불가하므로 다른 class는 상속받을 수 없다.)
1
2
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
  1. 상수당 자신의 instance를 하나씩 만들어 public static final 필드로 공개한다. (enum class내 상수는 public static final 필드와 같음 )

  2. 외부에서 접근할 수 있는 생성자를 제공하지 않는다. 때문에 singleton은 원소가 하나뿐인 열거타입이라고도 볼 수 있다.

Read more

Junit5 Parameterized Test

Parameterized Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SetTest {
private Set<Integer> numbers;
@BeforeEach
void setUp(){
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);
}
@Test
void contains() {
assertThat(numbers.contains(1)).isTrue();
assertThat(numbers.contains(2)).isTrue();
assertThat(numbers.contains(3)).isTrue();
}
}

위와 같이 Set의 API에 대한 학습테스트를 수행한다고 하였을떄 각 원소를 포함했는지 중복코드가 발생한다.

Junit 5부터는 이를 ParameterizedTest 로 테스트에 여러개의 매개변수를 넣어주게 해줌으로써 테스트 코드 리팩토링을 원할하게 해준다.

필요한 library dependency는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
// maven
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.8.1</version>
<scope>test</scope>
</dependency>

//gradle
testCompile("org.junit.jupiter:junit-jupiter-params:5.8.1")
Read more