본문 바로가기
개발 공부/Java

이펙티브 자바 아이템 26 - 로 타입은 사용하지 말라 - 완벽 공략

by 개발인생 2022. 12. 7.
반응형

아이템 26 - 로 타입은 사용하지 말라 - 완벽 공략

이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.

Generic DAO 만들기

public interface Entity {

    Long getId();
}
public class Account implements Entity {

    private Long id;

    private String username;

    public Account(Long id, String username) {
        this.id = id;
        this.username = username;
    }

    @Override
    public Long getId() {
        return this.id;
    }

    public String getUsername() {
        return username;
    }
}
public class Message implements Entity {

    private Long id;

    private String body;

    @Override
    public Long getId() {
        return id;
    }

    public String getBody() {
        return body;
    }
}

Entity 인터페이스가 있고 AccountMessage 는 이 인터페이스를 구현하고 있다.

이 두 클래스는 각각의 Repository 를 가지고 있다.

public class AccountRepository {

    private Set<Account> accounts;

    public AccountRepository() {
        this.accounts = new HashSet<>();
    }

    public Optional<Account> findById(Long id) {
        return accounts.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Account account) {
        this.accounts.add(account);
    }
}
public class MessageRepository {

    private Set<Message> messages;

    public MessageRepository() {
        this.messages = new HashSet<>();
    }

    public Optional<Message> findById(Long id) {
        return messages.stream().filter(a -> a.getId().equals(id)).findAny();
    }

    public void add(Message message) {
        this.messages.add(message);
    }
}

각각의 Repository 의 메서드들은 거의 동일하다.

이러한 경우가 굉장히 많다.

리플렉션과 제네릭을 활용하면 상당히 많은 양의 코드를 줄일 수 있고, 그만큼 유지보수에 용이해진다.


class AccountRepositoryTest {

    @Test
    void findById() {
        AccountRepository accountRepository = new AccountRepository();
        Account account = new Account(1L, "whiteship");
        accountRepository.add(account);

        Optional<Account> byId = accountRepository.findById(1L);
        assertTrue(byId.isPresent());
    }

}

제네릭 코드를 사용하기 전에 테스트 코드를 먼저 작성하자.

public class GenericRepository {
}

GenericRepository 를 생성한다.

public class GenericRepository {
   private Set<Account> accounts;

   public AccountRepository() {
      this.accounts = new HashSet<>();
   }

   public Optional<Account> findById(Long id) {
      return accounts.stream().filter(a -> a.getId().equals(id)).findAny();
   }

   public void add(Account account) {
      this.accounts.add(account);
   }
}

그 뒤 GenericRepositoryAccountRepository 코드를 그대로 복사한다.

public class GenericRepository<E extends Entity> {
   private Set<E> entities;

   public GenericRepository() {
      this.entities = new HashSet<>();
   }

   public Optional<E> findById(Long id) {
      return entities.stream().filter(a -> a.getId().equals(id)).findAny();
   }

   public void add(E account) {
      this.entities.add(account);
   }
}

제네릭 타입을 <E extends Entity> 로 선언해 Entity 클래스를 상속받은 타입으로 한정짓는다.

그 뒤 제네릭을 사용하는 코드로 수정한다.

   public Optional<E> findById(Long id) {
      return entities.stream().filter(a -> a.getId().equals(id)).findAny();
   }

위의 메서드에서 getId() 를 사용할 수 있었던 이유는 <E extends Entity> 를 사용해

Entity 클래스의 하위 타입을 받았기 때문이다.

public class AccountRepository extends GenericRepository<Account> {

}

위처럼 GenericRepository 를 상속받아 사용할 수 있다.

새로운 Entity 가 추가도 쉽게 할 수 있다.

반응형

댓글