Whiteship's Note


EJ2E Item 23. 새로 작성하는 코드에서는 raw type 쓰지 말자

Java : 2010.03.26 17:44


참조: Effective Java 2nd Edition. Item 23:Don't use raw types in new code

먼저 용어 정리

class List<E> {
...
}

List<String> stList = ~~~~;

이런 코드가 있을 때...
- List<E>: Generic type
- List<String>: Parameterized type
- E: Formal type parameter
- String: Actual type parameter
- List: Raw type (제목에 쓴게 이거임)
- List<?>: Unbounded wildcard type
- 나머진 패스.

Generic 장점
- 컴파일 시에 타입 안전성을 보장한다.
- 부가적인 장점으로 컬렉션에 들어있는 것을 꺼낼 때 캐스팅할 필요가 없다.

그럼 Generic을 꼭 쓰게 하지 왜 Raw Type으로 쓸 수 있게 해뒀냐?
- 호환성 때문에. 이미 1.5전 버전 기준으로 만들어 놓은 코드가 많으니까.

Generic 특성
- List<String>은 List의 하위 타입이지만 List<Object>의 하위 타입은 아니다.
- 다음 item에서 더 자세히 설명 나옴.


Raw Type을 사용했을 때 발생할 수 있는 문제

    public static void main(String[] args) {
        List<String> strings = new ArrayList<String>();
        unsafeAdd(strings, new Integer(42));
        String s = strings.get(0); // Compiler-generated cast
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

굵은 글씨에서 타입을 명시하지 않았기 때문에
list.add(o); 여기서 일단 컴파일 경고가 발생하지만 일단 무시하고 실행할 수는 있다 막상 실행하면 ClassCastException 발생한다. 하지만 만약 저기서 List<String>이라고 타입을 명시했다면 애초에 컴파일도 못했을 것이다.

어떤 종류가 들어있는지 모르는 컬렉션을 처리하는 메서드에서는 unbounded wildcard type을 사용하자.
   
    //위험
    static int numElementsInCommon(Set s1, Set s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

    //안전
    static int numElementsInCommon(Set<?> s1, Set<?> s2) {
        int result = 0;
        for (Object o1 : s1)
            if (s2.contains(o1))
                result++;
        return result;
    }

위에껀 왜 위험하고 아래껀 왜 안전할까? 안전한 녀석에는 무언가 추가할 수가 없다. 위험한 녀석에는 아무 객체나 넣을 수 있지만 unbounded wildcard type을 사용한 경우에는 컬렉션에 아무것도 추가할 수 없다. 컴파일 에러다.

왜? Actual type parameter가 뭔지 모르는데 뭘 집어 넣을 수 있을까.. 생각해보면 당연하다.

Type parameter를 사용할 수 없는 경우
- List<String>.class
- instanceof


top

TAG Generic, Java
Java : 2010.03.26 17:44 Trackback. : Comment.

[Generic] 자바 Generic 타입 알아내기

모하니?/Coding : 2009.09.04 14:35


참조: http://blog.xebia.com/2009/03/09/jpa-implementation-patterns-data-access-objects/

GenericDao를 만들다 보면, entity 클래스 타입이 필요하게 되는데, 이 타입을 구하기 위해 GenericDAO를 상속받을 때 마다 생성자에서 넘겨주거나, abstract 메서드로 entity 클래스를 세팅하도록 강제하기도 하기도 하고, 저 같은 경우는 DAO 이름 컨벤션에 따라 도메인 이름을 추측하여 Class.forName()으로 자동으로 인식할 수 있게 한적도 있습니다.

일단, 전자의 방법들은 GenericDAO를 상속받는 클래스를 만들 때 귀찮다는 단점이 있습니다. 맨 마지막 방법은 컨벤션을 따르지 않았을 경우 난감해진다는 문제가 있습니다.

그러나... 이 방법들 보다 더 좋은 방법이 있었습니다.

자바 Generic 타입 정보는 흔히들 런타임에 활용할 수 없다고 알고 계실겁니다. 그건 객체들 얘기이고, 모든 객체가 지니고 있는 클래스(.class)에는 해당 정보가 남아있습니다. 따라서 런타임 때도 리플렉션을 이용해서 Generic 정보를 활용할 수 있습니다.

http://whiteship.me/1614

    @PersistenceContext
    protected EntityManager entityManager;
 
    public JpaDao() {
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[1];
    }

참조 링크에 있는 JPA 패턴을보다가 발견한 이 코드를 응용해서 사용하시면 되겠습니다.

이로써, 보다 깔끔한 GenericDao를 만들 수 있겠네요. 하나 덤으로 위 링크에 첨언을 하자면, 위 링크에서는 주키 타입도 Generic 타입으로 지정해서 일일히 지정받고 있는데, 저는 그것보다 Serializable을 사용하면 타입 인자를 하나 더 줄일 수 있는데다, 키 타입에 대해 별다른 제약도 없기 때문에 더 깔끔하지 않나 생각하빈다.

ps: 오랜만에 HibernateGenericDao를 TDD로 만들고 있는데 재밌네요. 별거 아니지만;; 만들면 공개해볼까 합니다.
top


Effective Java Reloaded

Java : 2008.10.26 09:49


참조: Parleys.com에서 Effective Java Reloaded

Generic

- PECS, Producer extends, Consumer super.
- 타입제한으로 인해 다형성같은 유연함이 떨어질 땐, PECS를 적용하면 유연하게 만들 수 있다.

Enum

- int 값을 얻고 싶을 때 ordinal()+1 을 반환하는건 유지보수 힘들다.(순서 바뀔 수도 있고, 중간 요소가 빠져있을 수도 있고, 같은 수를 반환할 수가 없다.)
- int 속성을 enum 생성자에 전달하고 그 값을 반환하도록 코딩한다.(오.. 멋지다.)
public enum Ensemble {
  SOLO(1), DUET(2), TRIO(3), ...
 
  private final int numberOfMusicians;

  Ensemble(int size){
    numberOfMusicians = size;
  }
 
  public int numberOfMusicians() {
    return numberOfMusicians;
  }

}

- int 상수 사용 문제. not typesafe, no namespace, brittle, 출력시 값이 명시적이지가 않아, 순회하기도 힘들어, 64비트 넘으면 ㄷㄷㄷ
- EnumSet을 enum이 가지고 있는 메소드에 넘긴다.
public void applyStyles(Set<Style> styles) {...}

text.applyStyles(EnumSEt.of(Style.BOLD, Style.ITALIC));

- ordinal()은 EnumSet, EnumMap 같은 곳에서 내부적으로 사용할 목적이니까, 프로그래밍 할 땐 쓰지 말도록.

- 싱글톤 퀴즈, enum으로 싱글톤 만들기
 Serializable Singleton

public enum Elvis {
  INSTANCE;
  ...
}

- enum 제약 사항
확장 못 한다. 메소드는 인터페이스를 사용해서 확장 가능
Emulated Extensible Enum

Lazy initialization

- 언제 사용하는가
성능, initialization circularity 해결하려고

- static 필드는 Holder Class 사용하기
private static class FiledHolder {
  static final FieldType field = computeFirldValue();
}

static FieldType getField(){
  return FieldHolder.field;
}

- 인스턴스 필드는 더블 체킹

private volatile FieldType field;

FieldType getField(){
  FieldType result = field;
  if(result == null){  // 1st check (no lock)
    synchronized(this){
      result = field;
      if(result == null)  // 2nd check (w/ lock)
        field = result = computeFieldValue();
    }
  }
  return result;
}





top

Java : 2008.10.26 09:49 Trackback. : Comment.

Generic 팩토리 메소드

Java : 2008.09.25 08:53


참조: http://www.ibm.com/developerworks/kr/library/j-jtp04298.html

흠.. 제네렉 타입을 사용한 객체를 생성할 때 유용할 것 같습니다.

Box<String> box = new BoxImpl<String>();

보통 제네릭 타입을 사용한 객체를 생성할 때 저렇게 String이라는 타입을 두 번이나 입력해야 하는데..

public class BoxImpl<T> implements Box<T> {

    public static<V> Box<V> make() {
        return new BoxImpl<V>();
    }

    ...
}

이렇게 make라는 팩토리 메소드를 만들어 두면..

Box<String> myBox = BoxImpl.make();

이렇게 타입을 한 번만 지정해도 되는군요. 오호~ 괜찮군요. 근데 굳이 V 라고 안하고 그냥 T 계속 써도 될텐데, 굳이 구분할 필요가 있었나..흠. 어차피 같은 타입 지칭하는건데..

V대신 T를 써도 무방하긴 하지만, 이 예제는 왠지 컴파일러가 제네릭 타입을 선택하는 과정을 보여주기 위해, V를 둔것 같습니다.
top


Generic 메타데이터 활용하기

Java : 2008.04.27 10:21


참조 : http://blog.springsource.com/main/2006/09/29/exploiting-generics-metadata/

윗글 재미있습니다. 자바에서 욕을 많이 먹고 있는 기능 중에 하나가 Generic인데 사실 그렇게 욕먹을 필요까진 없다고 봅니다. 타입 캐스팅을 줄여주는 것 만으로도 충분히 좋은 기능이고 컴파일 시점에 타입 체크를 할 수 있다는 것도 좋다고 생각합니다. 단지 복잡해 보일 수도 있고 가독성을 떨어트릴 수도 있다는 것 때문에 사장되어야 할 정도로 엉망진창인 기능은 아니라고 생각합니다.

어쨋든 자바의 Generic은 자바 1.4 이하 버전들과의 호환 때문에 erasure 방식을 사용하고 따라서 런타임 시에 어떤 타입을 가지고 있는지 알겨를이 없습니다.

그런데 바로 이 Erasure 방식 때문에 오해가 생기기도 하나봅니다. 즉, 자바의 Generic은 타입 정보를 지워버린다는 오해입니다. 클래스 파일에 붙어있는 타입 정보는 지워지지 않습니다. 코딩을 해놨는데 그걸 굳이 지워버릴 필요는 없겠죠. 단지 런타임 시에 해당 클래스 타입의 객체에서 지워져버릴 뿐이죠. 따라서 클래스 파일에 붙어있는 타입 정보를 얼마든지 활용할 수도 있습니다.

private Class extractTypeParameter(Class<? extends EntitlementCalculator> calculatorType) {
    Type[] genericInterfaces = calculatorType.getGenericInterfaces();

    // find the generic interface declaration for EntitlementCalculator<E>
    ParameterizedType genericInterface = null;
    for (Type t : genericInterfaces) {
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType)t;
            if (EntitlementCalculator.class.equals(pt.getRawType())) {
                genericInterface = pt;
                break;
            }
        }
    }

    if(genericInterface == null) {
        throw new IllegalArgumentException("Type '" + calculatorType
               + "' does not implement EntitlementCalculator<E>.");
    }

    return (Class)genericInterface.getActualTypeArguments()[0];
}

위 코드는 메소드 파라미터로 넘어온 클래스가 가지고 있는 타입 정보를 반환해주는 메소드 입니다. 객체가 아니라 클래스를 받고 있기 때문에 가능한 일이죠.
top

Java : 2008.04.27 10:21 Trackback. : Comment.

Generic과 다형성 2탄

Java : 2007.01.05 15:13


앞서 살펴본 Generic과 다형성에서 보았던 답답함을 해결하는 간단한 방법은 와일드 카드를 사용하는 것입니다. feedAnimals메소드를 다음과 같이 수정합니다.

private void feedAnimals(List<? extends Animal> animals) {

             for(Animal animal : animals){

                    animal.eat();

             }

       }


위 코드는 아래와 같이 써도 똑같은 의미 입니다. 다른 의미 입니다. 위에 것은 상위 제한이 Animal인 어떠한 타입이든(Dog든 Cat이든 Animal을 상속 받았다면..) 올 수 있는데 아래 것은 상위 타입이 Animal인 어떤 타입 하나(Dog면 Dog, Cat이면 Cat)만 올 수 있기 때문에 콜렉션에 타입 제약을 가할 수 있기 때문에 추가가 됩니다. T 타입으로 추가를 해야겠죠.

private <T extends Animal> void feedAnimals(List<T> animals) {

             for(Animal animal : animals){

                    animal.eat();

             }

       }


그럼 이런 상황에서 Dog 리스트에 Cat 객체를 넣으려는 건 어떻게 방지가 될까가 궁금합니다. 컴파일러는 위와 같이 ? 나 T 같은 와일드 카드[각주:1]가 사용되면 컴파일러는 콜렉션에 추가하는 작업을 허용하지 않습니다.

따라서 다음고 같은 코드를 컴파일 에러를 발생시킵니다.

private <T extends Animal> void feedAnimals(List<T> animals) {

             for(Animal animal : animals){

                    animal.eat();

             }

             animals.add(new Dog());   //에러

             animals.add(new Cat());   //에러

             animals.add(new Animal());  //에러

       }


하지만 다음과 같이 추가하는 작업을 할 수 있습니다.
    private <T extends Animal> void feedAnimals(List<T> animals, T a) {
        for(Animal animal : animals){
            animal.eat();
        }
//        animals.add(new Dog());
//        animals.add(new Cat());
//        animals.add(new Animal());
        animals.add(a);
    }
이게 되는 이유는 T라는 타입 하나로 Collection안에 들어가는 요소들의 타입이 제한 할 수 있기 때문입니다.

또 하나 여기서 사용된 extends의 의미는 class head부분에서 사용되는 extends보다 포괄적인 의미를 나타냅니다. extends와 implements를 포함합니다.


참조 : Head First Java

좀더 자세한 내용은 Generics를 참조 하세요.



  1. Java 에서 identifier가 될 수 있는 어떤 문자도 상관이 없습니다. [본문으로]

'Java' 카테고리의 다른 글

제8회 한국 자바 개발자 컨퍼런스  (2) 2007.01.25
GC관련 아티클  (0) 2007.01.19
Generics 번외 - 겉모습만 보곤 알 수 없슴.  (2) 2007.01.17
Generics  (2) 2007.01.17
Eclipse 단축키 모음  (6) 2007.01.11
Generic과 다형성 2탄  (4) 2007.01.05
Generic과 다형성  (0) 2007.01.05
자바 검은 띠에 도전해 보시길~  (2) 2006.12.31
Hiding Method  (0) 2006.12.31
Overriding - covariant return type  (6) 2006.12.31
LinkedList vs ArrayList  (6) 2006.12.22
top


Generic과 다형성

Java : 2007.01.05 14:27


사용자 삽입 이미지
위와 같은 계층 구조를 가진 클래스를 사용하는 콜렉션을 다음과 같이 코딩을 합니다.

public class AnimalCollection {

       @Test

       public void feedingAnimalList(){

             List<Animal> animals = new ArrayList<Animal>();

             animals.add(new Animal());

             animals.add(new Dog());

             animals.add(new Cat());

             feedAnimals(animals);

       }

 

       private void feedAnimals(List<Animal> animals) {

             for(Animal animal : animals){

                    animal.eat();

             }

       }

}


결과를 확인하면 다음과 같이 원하는 대로 돌아간 것을 확인할 수 있습니다.
동물이 먹습니다.
강아지가 먹습니다.
고양이가 먹습니다.

다형성을 이용하기 위해서 Dog List를 만들고 이 리스트도 밥을 먹이기 위해서 feedAnimals메소드를 사용해 봅시다.

       @Test

       public void feedingDogList(){

             List<Dog> dogs = new ArrayList<Dog>();

             dogs.add(new Dog());

             dogs.add(new Dog());

             dogs.add(new Dog());

             feedAnimals(dogs);

       }

       private void feedAnimals(List<Animal> animals) {

             for(Animal animal : animals){

                    animal.eat();

             }

       }

위와 같이 코딩을 하면 컴파일에러가 발생하는 것을 알 수 있습니다.

즉 List<Animal> animals 매개변수를 가진 feedAnimals메소드에 List<Dog> 타입이 들어갈 수 없다는 것입니다. 들어가지 못하는 이유는 위험하기 때문입니다. 만약 feedAnimals에서 dogs를 받아 들인다고 했을 때 feedAnimals에서 dogs에 Cat 타입의 객체를 넣을 수도 있을 겁니다.

       private void feedAnimals(List<Animal> animals) {

             animals.add(new Cat());

       }

위와 같은 일이 아예 벌어지지 않도록 컴파일 에러를 내준다고 합니다.

배열과의 차이점 보기


참조 : Head First Java

'Java' 카테고리의 다른 글

GC관련 아티클  (0) 2007.01.19
Generics 번외 - 겉모습만 보곤 알 수 없슴.  (2) 2007.01.17
Generics  (2) 2007.01.17
Eclipse 단축키 모음  (6) 2007.01.11
Generic과 다형성 2탄  (4) 2007.01.05
Generic과 다형성  (0) 2007.01.05
자바 검은 띠에 도전해 보시길~  (2) 2006.12.31
Hiding Method  (0) 2006.12.31
Overriding - covariant return type  (6) 2006.12.31
LinkedList vs ArrayList  (6) 2006.12.22
Agile Java 소스코드(10장까지..)  (8) 2006.12.21
top

Java : 2007.01.05 14:27 Trackback. : Comment.

탬플릿 클레스 만들기

Java : 2006.11.05 21:26


5.0에 새로 추가된 generic을 c++에서는 탬플릿이라고 하더군요. c++에도 처음 에는 없었지만 나중에 추가 된 기능이라고 합니다. 개인적으로 Generic 보다는 탬플릿이라는 이름이 기능과 참 잘 어울리네요.

Daum DNA에 올라온 소스 코드를 보겠습니다.

more..


이런식으로 List를 구현해 두면 정말 편하게 사용할 수 있겠습니다. 물론 5.0 java.util에 있는 collection에서 제공해주는 API들도 이런 모양을 하고 있지요. 다소 복잡해 보이지만요.. 오늘 토비님 블러그올라온 글을 보니 실제도로 복잡한 것 같습니다.

'Java' 카테고리의 다른 글

Null은 객체인가 아닌가?  (13) 2006.11.12
Reference Object 활용  (2) 2006.11.09
XML을 EXEL 파일로 바꾸기  (0) 2006.11.08
Enumeration & Iterator  (2) 2006.11.07
Auto (un)boxing은 -128~127  (4) 2006.11.07
탬플릿 클레스 만들기  (3) 2006.11.05
local inner class의 예  (0) 2006.11.05
객체지향의 구멍 static  (0) 2006.11.04
i++와 i=i+1 속도 비교  (0) 2006.11.02
Comparator를 사용하여 비교하기.  (0) 2006.11.02
Arrays.sort() & Collections.sort()  (2) 2006.11.02
top







티스토리 툴바