토비의 봄 TV 4회 (2) Generics에서 와일드카드 활용법, 람다와 인터섹션 타입을 이용한 동적인 기능확장법IT/Spring Framework2018. 2. 3. 23:28
Table of Contents
(시청일 : 20171022)
■ List<T>와 List<?>의 차이점
타입 파라미터(T)는 element에 초점을 맞춰 기능 구현이 가능하지만
와일드카드(?)는 List에만 초점을 맞춰 기능 구현 가능
■ 와일드카드(?)에 비해, 타입 파라미터(T)이 가진 단점
(오랄클에서 공식적으로 밝힌 내용)
- 내부 구현 노출
- 구현 의도가 불명확
따라서, 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();
});
}
}
'IT > Spring Framework' 카테고리의 다른 글
토비의 봄 TV 6회 스프링 리액티브 프로그래밍 (2) - Reactive Streams - Operators (0) | 2018.02.03 |
---|---|
토비의 봄 TV 5회 스프링 리액티브 프로그래밍 (1) - Reactive Streams (0) | 2018.02.03 |
토비의 봄 TV 4회 (1) 자바 Generics (0) | 2018.02.03 |
토비의 봄 TV 2.5회 - 수퍼 타입 토큰(2), 스프링 ResolvableType (0) | 2018.02.03 |
Annotation 정리 (0) | 2018.02.01 |
@DEAN :: Dean Story
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!