일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
26 | 27 | 28 | 29 | 30 | 31 |
- 백준#BOJ#1939#중량제한
- 백준#BOJ#2615#오목
- 백준#BOJ#8012#한동이는영업사원
- 백준#boj#16932#모양만들기
- 백준#BOJ#14501#퇴사#브루트포스
- 백준#BOJ#12865#평범한배낭
- 백준#boj#12755
- Today
- Total
순간을 성실히, 화려함보단 꾸준함을
[리뷰] 14주차 : 제네릭 본문
중복되는 코드를 줄여보자!!!!
과거 스프링 JAP(아직 나도 이 개념이 뭔지는 모르지만 요즘 JPA 모르면 바보라니깐 천천히라도 공부하려는 의지를 갖자!!!)가 나오기 이전에는 데이터베이스에 접근하려는 방식은 DAO(Data Access Object) 를 사용하였다.
그러나 이런 방식은 중복된 코드를 양상시키는 주된 이유였으며, 단순 노동으로 치부되기도 하였다.
//Apple
public class Apple {
private Integer id;
public Integer getId(){
return this.id;
}
}
public class Banana {
private Integer id;
public Integer getId(){
return this.id;
}
}
Apple 과 Banana 클래스가 존재하고 있다.
public class AppleDao {
private Map<Integer,Apple> datasource = new HashMap<>();
public void save(Apple apple){
datasource.put(apple.getId(), apple);
}
public void delete(Apple apple){
datasource.remove(apple.getId());
}
public void aVoid(Integer integer){
datasource.remove(integer);
}
public List<Apple> findAll(){
return new ArrayList<>(datasource.values());
}
public Apple findById(Integer id){
return datasource.get(id);
}
}
public class BananaDao {
private Map<Integer,Banana> datasource = new HashMap<>();
public void save(Banana banana){
datasource.put(banana.getId(), banana);
}
public void delete(Banana banana){
datasource.remove(banana.getId());
}
public void aVoid(Integer integer){
datasource.remove(integer);
}
public List<Banana> findAll(){
return new ArrayList<>(datasource.values());
}
public Banana findById(Integer id){
return datasource.get(id);
}
}
각각의 Dao 가 정의되어져 있다. 위의 소스만 봐도 Dao 에 너무나도 같은 역할을 하는 메소드들이 중복되어있는 것을 볼 수 있다. 이를 제네릭을 사용해서 한번 중복된 코드들을 해결해 보자.
그 전에 먼저 변경된 소스와 비교해 주기 위해 main
메소드를 선언해 주어서 실행시켜 보자.
public class Store {
public static void main(String[] args) {
AppleDao appleDao = new AppleDao();
appleDao.save(Apple.of(1));
appleDao.save(Apple.of(2));
List<Apple> list = appleDao.findAll();
list.forEach(System.out::println);
}
}
//결과값
generic.Apple@2e0fa5d3
generic.Apple@5010be6
먼저 GenericDao
라는 클래스를 하나 선언해주고 Key
와 Entity
제네릭 키워드로 변경을 시켜주자
public class GenericDao<K,E> {
private Map<K,E> datasource = new HashMap<>();
public void save(E entity){
datasource.put(entity.getId(), entity); //Compile Error
}
public void delete(E entity){
datasource.remove(entity.getId()); //Compile Error
}
public void aVoid(K id){
datasource.remove(id);
}
public List<E> findAll(){return new ArrayList<>(datasource.values()); }
public E findById(K id){return datasource.get(id); }
}
그러면 너무나도 당연스럽게 getId()
에 컴파일 에러가 발생하는 것을 확인할 수 있을 것이다.
그 이유는 현재 getId() 메소드를 찾지 못하고 있기 때문이다. 이유는 entity
에는 getId()
가 정의되어 있지 않은 상태이기 때문이다.
getId()
라는 메소드가 존재한다고 알려주기 위해 Entity
라는 클래스를 추가해주고 id
맴버변수와 getId()
메소드를 추가해준 후 Apple
과 Banana
모두에게 상속을 받게 해주자.(이때 Apple
클래스와 Banana
클래스에선 id
와 getId()
메소드를 삭제해도 된다.) 또한, GenericDao
에 E extends Entity
으로 E
에게 상속받게 해주자
public class Entity {
protected Integer id;
public Integer getId(){
return id;
}
}
public class GenericDao<K,E extends Entity> {
private Map<K,E> datasource = new HashMap<>();
public void save(E entity){
datasource.put(entity.getId(), entity); //Compile Error
}
public void delete(E entity){
datasource.remove(entity.getId());
}
public void aVoid(K id){
datasource.remove(id);
}
public List<E> findAll(){return new ArrayList<>(datasource.values()); }
public E findById(K id){return datasource.get(id); }
}
그러면 여전히 컴파일 에러가 발생하지만, 아까와는 다르게 getId()
를 찾을 수 있는 것을 확인할 수 있다.
일단 getId()
를 찾았으니 지금까진 잘하고 있는 것이다.
그러나 컴파일에러는 아직도 발생하고 있는데 그 이유는 바로 타입이 일치하지 않기 때문이다.
위의 소스에서는 entity
의 타입은 E
라고 정의를 해놓았지만, 클래스 Entity
에서는 현재 리턴타입을 Integer
로 정의한 것을 확인할 수 있다. 이것 때문에 컴파일 에러가 발생하는 것이다.
이를 해결해주기 위해 타입을 일치시켜주자Entity
클래스에 K
타입을 정의해 주고 나머지 클래스들에서도 추가해주자.
public class Entity<K> {
protected K id;
public K getId(){
return id;
}
}
public class Apple extends Entity<Integer>{
public static Apple of(Integer id){
Apple apple = new Apple();
apple.id=id;
return apple;
}
}
public class Banana extends Entity<Integer>{
public static Banana of(Integer id){
Banana banana = new Banana();
banana.id=id;
return banana;
}
}
public class GenericDao<K,E extends Entity<K>> {
private Map<K,E> datasource = new HashMap<>();
public void save(E entity){
datasource.put(entity.getId(), entity);
}
public void delete(E entity){
datasource.remove(entity.getId());
}
public void aVoid(K id){
datasource.remove(id);
}
public List<E> findAll(){return new ArrayList<>(datasource.values()); }
public E findById(K id){return datasource.get(id); }
}
그럼 여기까지 제네릭한 클래스를 만든 것이다. 마지막 단계만 남았다. AppleDao
와 BananaDao
의 코드들을 싹다 밀어주자!!
public class AppleDao extends GenericDao<Integer,Apple>{
}
public class BananaDao extends GenericDao<Integer,Banana>{
}
그럼 Stroe
클래스에서 실행한 결과가 이전값과 같은지 확인해 주자
generic.Apple@2e0fa5d3
generic.Apple@5010be6
동일한 것을 확인할 수 있다.
여기서 더 나아가 곰곰이 생각을 해보면 AppleDao
와 BananaDao
가 굳이 존재할 필요성이 있을까??라는 의문점이 생길 수 있다.
사실은 필요없다.
public class Store {
public static void main(String[] args) {
//AppleDao appleDao = new AppleDao();
GenericDao<Integer,Apple> appleDao = new GenericDao<>();
appleDao.save(Apple.of(1));
appleDao.save(Apple.of(2));
List<Apple> list = appleDao.findAll();
list.forEach(System.out::println);
}
}
이렇게 GenericDao
클래스를 사용하여 접근할 수 있다.
정말 제네릭 타입은 런타임시 알아낼 수 있는 방법이 아에 없는 것 인가???
지난 포스팅에서 컴파일러가 컴파일시 제네릭 타입을 Erasure 한다고 했다. 그러면 당연하게 런타임시에 제네릭의 타입을 알아낼 수 있는 방법은 없어야 한다.
그러나 정말 없는 것일까??
만약 정말로 타입 정보가 필요한 경우가 생긴다면 super
토큰을 사용하여 타입 정보를 알아낼 수 있는 방법이 존재한다.
public class Store {
public static void main(String[] args) {
AppleDao appleDao = new AppleDao();
//GenericDao<Integer,Apple> appleDao = new GenericDao<>();
appleDao.save(Apple.of(1));
appleDao.save(Apple.of(2));
List<Apple> list = appleDao.findAll();
list.forEach(System.out::println);
System.out.println(appleDao.getEntityClass());
}
}
public class AppleDao extends GenericDao<Integer,Apple>{
public AppleDao(){
super(Apple.class);
}
}
public class GenericDao<K,E extends Entity<K>> {
private Class<E> entityClass;
public GenericDao(Class<E> entityClass){
this.entityClass = entityClass;
}
public Class<E> getEntityClass(){
return entityClass;
}
private Map<K,E> datasource = new HashMap<>();
public void save(E entity){
datasource.put(entity.getId(), entity);
}
public void delete(E entity){
datasource.remove(entity.getId());
}
public void aVoid(K id){
datasource.remove(id);
}
public List<E> findAll(){return new ArrayList<>(datasource.values()); }
public E findById(K id){return datasource.get(id); }
}
이렇게 생성자에 메타데이터의 정보를 사용하여 타입의 정보를 알아낼 수 있다.
그러나 이 방법은 매우매우 귀찮다...그래서 우린 리플렉션으로 타입을 추론할 수 있는 방법이 있다.
public class GenericDao<K,E extends Entity<K>> {
private Class<E> entityClass;
public GenericDao(){
this.entityClass = (Class<E>)((ParameterizedType)this.getClass().getGenericSuperclass())
.getActualTypeArguments()[1];
}
public Class<E> getEntityClass(){
return entityClass;
}
private Map<K,E> datasource = new HashMap<>();
public void save(E entity){
datasource.put(entity.getId(), entity);
}
public void delete(E entity){
datasource.remove(entity.getId());
}
public void aVoid(K id){
datasource.remove(id);
}
public List<E> findAll(){return new ArrayList<>(datasource.values()); }
public E findById(K id){return datasource.get(id); }
}
public class AppleDao extends GenericDao<Integer,Apple>{
}
이렇게 생성자의 소스를 변경해 주고 AppleDao
의 소스를 전부 지워줘 보자
그리고 실행을 하면
generic.Apple@25f38edc
generic.Apple@1a86f2f1
class generic.Apple
Apple
이 잘 나오는 것을 확인 할 수 있다.
'백기선님과 함께 하는 자바 스터디' 카테고리의 다른 글
15주차 과제: 람다식 (0) | 2021.08.07 |
---|---|
[번외] final 과 static 키워드 (0) | 2021.08.01 |
14주차 과제: 제네릭 (0) | 2021.07.31 |
[리뷰] 13주차 : I/O (0) | 2021.07.25 |
13주차 과제: I/O (0) | 2021.07.21 |