(시청일 : 20171022)




■ List<T>와 List<?>의 차이점
  타입 파라미터(T)는 element에 초점을 맞춰 기능 구현이 가능하지만
와일드카드(?)는 List에만 초점을 맞춰 기능 구현 가능


■ 와일드카드(?)에 비해, 타입 파라미터(T)이 가진 단점
  (오랄클에서 공식적으로 밝힌 내용)
  1. 내부 구현 노출
  2. 구현 의도가 불명확
따라서, element에 초점을 맞추지 않아도 되는 경우에는 타입 파라미터보다 와일드카드를 쓰는 것이 좋다.



■ Generic에서 와일드카드 활용법
public class Generics {
    
    static <T> boolean isEmpty(List<?> list){ // 타입 파라미터
        return list.size() == 0;
    }
    static boolean isEmpty2(List<?> list){ // 와일드카드
        return list.size() == 0;
    }
    static <T> long frequency(List<T> list, T elem) { // 타입 파라미터
        return list.stream().filter(s -> s.equals(elem)).count();
    }
    static long frequency2(List<?> list, Object elem) {  // 와일드카드
        return list.stream().filter(s -> s.equals(elem)).count();
    }
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,3,2);
        System.out.println(frequency2(list, 3));  // 결과는 2
    }
}


■ upper bounded(extends)와 lower bounded(super)의 사용
  넘기는 파라미터 타입이 메소드안에서 사용되어지는 input으로서는 upper bounded 사용
외부의 메소드에서 사용되어지는 output으로서는 lower bounded 사용
public class Generics {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,3,2);
        System.out.println(max(list)); // 결과는 5
        List<Integer> list2 = Arrays.asList();
        System.out.println(max(list2)); // NoSuchElementException 발생!!
        List<Integer> list3 = Arrays.asList(1);
        System.out.println(max(list3)); // 결과는 1
        System.out.println(Collections.max(list, (a,b) -> a - b)); // 결과는 5
        System.out.println(Collections.max(list, (a,b) -> b - a)); // 결과는 1
        System.out.println(Collections.max(list, (Comparator<Object>)(a,b) -> a.toString().compareTo(b.toString())));
    }
    // upper bounded 타입 파라미터
    private static <T extends Comparable<T>> T max(List<T> list) {
        return list.stream().reduce((a,b)-> a.compareTo(b) > 0 ? a : b).get();
    }
    // 와일드카드 (super는 lower bounded, extends는 upper bounded)
    private static <T extends Comparable<? super T>> T max2(List<? extends T> list) {
        return list.stream().reduce((a,b)-> a.compareTo(b) > 0 ? a : b).get();
    }
}


■ Helper 메소드와  Row type의 사용
public  class Genirics {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,3,2);
        reverse(list);
        System.out.println(list);
    }
    static <T> void reverse(List<T> list) {
        List<T> temp = new ArrayList<>(list);
        for(int i=0; i<list.size(); i++){
            list.set(i, temp.get(list.size()-i-1));
        }
    }
    // 와일드카드 사용 시의 capture 오류를 피할 수 있는 Helper 메소드 이용
    static <?> void reverse2(List<?> list) {
        reverseHelper(list);
    }
    private static <T> void reverseHelper(List<T> list) {
        List<T> temp = new ArrayList<>(list);
        for(int i=0; i<list.size(); i++){
            list.set(i, temp.get(list.size()-i-1));
        }
    }
    //Row Type  (1.5버전의 자바코드. 타입파라미터 안쓴 것)
    static void reverse3(List<?> list) {
        List temp = new ArrayList<>(list);
        List list2 = list;
        for(int i=0; i<list.size(); i++){
            list2.set(i, list2.get(list2.size()-i-1)); //
        }
    }
}


■ 자바8에서 추가된 upper bound에 intersection 타입 사용
 - intersection type : 새로운 클래스를 정의하지 않은 채, 여러개의 인터페이스를 조합한 익명 타입 구현 가능
import java.io.Serializable;
import java.util.function.Function;
public class IntersectionType  {
    interface Hello {
        default void hello() {
        }
    }
    public static void main(String[] args) {
        hello ((Function & Serializable & Cloneable) s -> s);
    }
    // intersection type
    private static <T extends Function & Serializable & Cloneable> void hello(T o) {
    }
}



■ Lamda식의 타입에 intersection type을 적용했을 경우, induction 과정을 거쳐서 메소드가 1개라면, 최종적으로는 인터페이스 1개짜리 intersection type이 된다.
import java.awt.print.PrinterGraphics;
import java.io.Serializable;
import java.util.function.Consumer;
import java.util.function.Function;

public class IntersectionType {
    interface Hello extends Function{
        default void hello() {
            System.out.println("Hello");
        }
    }
    interface Hi extends Function {
        default void hi() {
            System.out.println("Hi");
        }
    }
    interface Printer {
        default void print(String str) {
            System.out.println(str);
        }
    }
    public static void main(String[] args) {
        run((Function & Hello & Hi & Printer) s->s, o -> {
            o.hello();
            o.hi();
            o.print("Lamda");
        });
    }
    private static <T extends Function> void run(T t, Consumer<T> consumer) {
        consumer.accept(t);
    }
}

public class IntersectionType {
    interface DelegateTo<T> {
        T delegate();
    }
    interface Hello extends DelegateTo<String> {
        default void hello() {
            System.out.println("Hello " + delegate());
        }
    };
    interface UpperCase extends DelegateTo<String> {
        default void upperCase() {
            System.out.println(delegate().toUpperCase());
        }
    };
    public static void main(String[] args) {
        run((DelegateTo<String> & Hello & UpperCase)()->"Daniel Jung", o-> {
            o.hello();
            o.upperCase();
        });
    }
    private static <T extends DelegateTo<S>, S> void run(T t, Consumer<T> consumer) {
        consumer.accept(t);
    }
}


■ class에 동적으로 기능을 추가하기 위한 intersection type의 사용

import java.awt.print.PrinterGraphics;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.function.Consumer;
import java.util.function.Function;
public class IntersectionType {
    interface Pair<T> {
        T getFirst();
        T getSecond();
        void setFirst(T first);
        void setSecond(T second);
    }
    static class Name implements Pair<String> {
        String firstName;
        String lastName;
        public Name(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
        @Override
        public String getFirst() {
            return null;
        }
        @Override
        public String getSecond() {
            return null;
        }
        @Override
        public void setFirst(String first) {
            this.firstName = first;
        }
        @Override
        public void setSecond(String second) {
            this.lastName = second;
        }
    }
    interface ForwardingPair<T> extends DelegateTo<Pair<T>>, Pair<T> {
        default T getFirst() { return delegate().getFirst(); }
        default T getSecond() { return delegate().getSecond(); }
        default void setFirst(T first) { delegate().setFirst(first);}
        default void setSecond(T second) { delegate().setSecond(second);}
    }
    interface DelegateTo<T> {
        T delegate();
    }
    private static <T extends DelegateTo<S>, S> void run(T t, Consumer<T> consumer) {
        consumer.accept(t);
    }
    interface Convertable<T> extends DelegateTo<Pair<T>> {
        default  void convert(Function<T,T> mapper){
            Pair<T> pair = delegate();
            pair.setFirst(mapper.apply(pair.getFirst()));
            pair.setSecond(mapper.apply(pair.getSecond()));
        }
    }
    interface Printable<T> extends DelegateTo<Pair<T>> {
        default void print(){
            System.out.println(delegate().getFirst() + " " + delegate().getSecond());
        }
    }
    static <T> void print(Pair<T> pair) {
        System.out.println(pair.getFirst() + " " + pair.getSecond());
    }
    public static void main(String[] args) {
        Pair<String> name = new Name("Toby", "Lee");
        run((ForwardingPair<String> & Convertable<String> & Printable<String>)()->name, o->{
            o.print();
            o.convert(s->s.toUpperCase());
            o.print();
            o.convert(s->s.substring(0,2));
            o.print();
        });
    }
}


(시청일 : 20171014)





import java.io.Closeable;
import java.io.Serializable;
import java.util.*;
/*
public class Generics<T> { // class level type parameter
    static class Hello<T> {
    }
    <S, T> T print(S t){ // method level type parameter
        System.out.println(t.toString());
    }
    public <S> Generics(S s){
    }
    static void print(String value) {
        System.out.println(value);
    }
    public static void main(String[] args) {
    }
}
*/
/*
public class Generics {
    //bounded type parameter 관련 예시
    static <T extends List & Serializable & Comparable & Closeable> void print(T t){} // intersection type
    static <T extends Comparable<T>> long countGreaterThan(T[] arr, T elem) {
        return Arrays.stream(arr).filter(s -> s.compareTo(elem) > 0).count();
    }
    public static void main(String[] args) {
        Integer[] arr = new Integer[] {1,2,3,4,5,6,7};
        String[] arr2 = new String[] {"a","b","c","d","e"};
        System.out.println(countGreaterThan(arr2, "a"));
    }
}
*/
/*
public class Generics {
    public static void main(String[] args) {
        Integer i = 10;
        Number n = i;
        List<Integer> intList = new ArrayList<>();
        List<Number> numberList = intList;   // compile error!! type parameter는 상속 관계 없어서..
        ArrayList<Integer> arrList = new ArrayList<>();
        List<Integer> numberList2 = arrList;  // no problem. List는 ArrayList의 수퍼타입
    }
}
*/
/*
public class Generics {
    // type 추론 예시
    static class MyList<E, P> implements List<E>{
        @Override
        public int size() {
            return 0;
        }
        @Override
        public boolean isEmpty() {
            return false;
        }
        @Override
        public boolean contains(Object o) {
            return false;
        }
        @Override
        public Iterator<E> iterator() {
            return null;
        }
        @Override
        public Object[] toArray() {
            return new Object[0];
        }
        @Override
        public <T> T[] toArray(T[] a) {
            return null;
        }
        @Override
        public boolean add(E e) {
            return false;
        }
        @Override
        public boolean remove(Object o) {
            return false;
        }
        @Override
        public boolean containsAll(Collection<?> c) {
            return false;
        }
        @Override
        public boolean addAll(Collection<? extends E> c) {
            return false;
        }
        @Override
        public boolean addAll(int index, Collection<? extends E> c) {
            return false;
        }
        @Override
        public boolean removeAll(Collection<?> c) {
            return false;
        }
        @Override
        public boolean retainAll(Collection<?> c) {
            return false;
        }
        @Override
        public void clear() {
        }
        @Override
        public E get(int index) {
            return null;
        }
        @Override
        public E set(int index, E element) {
            return null;
        }
        @Override
        public void add(int index, E element) {
        }
        @Override
        public E remove(int index) {
            return null;
        }
        @Override
        public int indexOf(Object o) {
            return 0;
        }
        @Override
        public int lastIndexOf(Object o) {
            return 0;
        }
        @Override
        public ListIterator<E> listIterator() {
            return null;
        }
        @Override
        public ListIterator<E> listIterator(int index) {
            return null;
        }
        @Override
        public List<E> subList(int fromIndex, int toIndex) {
            return null;
        }
    };
    static <T> void method(T t, List<T> list){
    }
    public static void main(String[] args) {
        List<String> s1 = new MyList<String, Integer>(); // no problem
        List<String> s2 = new MyList<String, String>();  // no problem
        Generics.method(1, Arrays.asList(1,2,3)); // no problem
        Generics.<Integer>method(1, Arrays.asList(1,2,3)); // no problem
        List<String> str = new ArrayList<>(); // no problem
        List<String> c = Collections.<String>emptyList(); // no problem
    }
}
*/
public class Generics {
    // ?(와일드 카드)와 T(제네릭 타입)의 차이점 관련 예시
    static void printList(List<Object> list){
        list.forEach(s -> System.out.println(s));
    }
    static void printList2(List<?> list){
        list.forEach(s -> System.out.println(s));
    }
    static void print(List<? extends Object> list) {}
    static class A {}
    static class B extends A {}
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3);
        printList(list); // compile error!! List<Integer>는 List<object>의 서브타입이 아니라서..
        printList2(list); // no problem
        List<Integer> listInt = Arrays.asList(1,2,3);
        print(listInt); // no problem
        List<B> listB = new ArrayList<B>();
        List<A> listA = listB; // compile error!!
        List<? extends A> la = listB; // no problem
        List<? super B> l2 = listB; // no problem
        List<? super A> l3 = listB; // compile error!!
        la.add(new B()); // compile error!!
        la.add(null); // null만 가능
    }
}


(시청일 : 20171001)


https://www.youtube.com/watch?v=y_uGSqpE4So : 2회에서 설명했던 수퍼 타입 토큰의 예제를 좀 더 효율적으로 개선하는 방법과 스프링이 제공하는 ResolvableType 사용법을 간단히 설명합니다.


■ Super Type Token (2회로 부터 개선된 버전)
 
public class SuperTypeToken {
    static Class TypeSafeMap {
        Map<Type, Object> map = new HashMap<>();
    <T> void put(TypeReference<T> tr, T value) {
        map.put(tr.type, value);
    }
    <T> T get(TypeReference<T> tr) {
        if (tr.type instanceof Class<?>)
            return ((Class<T>)tr.type).cast(map.get(tr.type));
        else
            return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr.type)); // TR<List<String>>
    }
}
    static class TypeReference<T> {
        Type type;
        public TypeReference() {
            Type stype = getClass().getGenericSuperclass();
            if (stype instanceof ParameterizedType) {
                this.type = ((ParameterizedType) stype).getActualTypeArguments()[0];
            } else throw new RuntimeException();
        }
    }
    public static void main(String[] args) throws Exception {
        TypeReference m = new TypeSafeMap();
        m.put(new TypeReference<Integer>() {}, 1);
        m.put(new TypeReference<String>() {}, "String");
        m.put(new TypeReference<List<Integer>>() {}, Arrays.asList(1,2,3)); // List<Integer>
        m.put(new TypeReference<List<String>>() {}, Arrays.asList("a","b","c")); // List<String>
        m.put(new TypeReference<List<List<String>>>() {}, Arrays.asList(Arrays.asList("a","b"),Arrays.asList("c","d"),Arrays.asList("e"))); // List<String>

        System.out.println(m.get(new TypeReference<Integer>() {})); // 1
        System.out.println(m.get(new TypeReference<String>() {})); // String
        System.out.println(m.get(new TypeReference<List<Integer>>() {})); // [1, 2, 3]
        System.out.println(m.get(new TypeReference<List<String>>() {})); // [a, b, c]
        System.out.println(m.get(new TypeReference<List<List<String>>>() {})); // [[a, b], [c, d], [e]]

        Map<String, String> map = new HashMap<>();
        map.put("key1", "value1");
        map.put("key2", "value2");
        m.put(new TypeReference<Map<String, Stirng>>(), map);  // {key1=value1, key2=value2}
    }
}



■ 스프링 4.0버전 이상에서 지원하는 ResolvableType 예제 코드

public class SuperTypeToken {
    static class TypeReference<T> {
        Type type;
        public TypeReference() {
            Type stype = getClass().getGenericSuperclass();
            if (stype instanceof ParameterizedType) {
                this.type = ((ParameterizedType) stype).getActualTypeArguments()[0];
            } else throw new RuntimeException();
        }
    }
    public static void main(String[] args) throws Exception {
        //ResolvableType rt = ResolvableType.forClass(TypeReference.class);

        ResolvableType rt = ResolvableType.forInstance(new TypeReference<List<String>>() {});
        
        System.out.println(rt.getSuperType().getGenerics(0).getType()); // java.Util.List<java.lang.String>
        System.out.println(rt.getSuperType().getGenerics(0).getNested(2).getType()); // class.java.lang.String
        System.out.println(rt.getSuperType().hasUnresolvableGenerics()); // false
        System.out.println(ResolvableType.forInstance(new ArrayList<String>()).hasUnresolvableGenerics()); // true
    }
}


 출처 : http://noritersand.tistory.com/156

 


+ Recent posts