(시청일 : 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();
        });
    }
}


+ Recent posts