generic 개인공부 2
generic + lambda + intersectionType
📜 제목으로 보기
- 개념
- 심화
- type paramter를 필요로 하는, 제네릭 메서드의 파라미터(List< >)에 대해 Type Paramter 적용 vs WildCards 적용
- PECS는 사실… 메서드내부 사용되는 T는 E / 메서드외부에서 사용되는 T는 S 이다.
- 와일드카드 사용시 캡처 문제
- Lambda
- IntersectionType 기본
- IntersectionType으로 marker interface의 default method을 합친 익명 객체 만들어보기(제한점 존재)
- Consumer파라미터 추가를 통한, 콜백형식으로 IntersectionType에 기능 하나씩 확장하기(제한점 존재)
- 람다식구현 인자용 함수형인페<T>를 상속한 인터페이스로, default method내부에서 변경가능한 람다식에 따른 기능 수행하는 메서드가 된다.
- 새로운 아이디어와 최종 예제
- 제네릭 기본 적용 예시 모음
개념
- 모던 자바(5이후) 가장 대표적인 기술 2가지
- 제네릭
-
람다
- 하나 더 추가한다면, 애노테이션
- 애노테이션은 프레임워크 개발자, 라이브러리 개발자들이나 만들 것. 우리가 만들 일은 없다.
- 코드 작성시 가장 많이 만지는 것
- 제네릭과 람다
- 잘 활용해야한다.
제네릭과 raw type
- 제네릭 -> 사실
타입 파라미터
를 사용하면 제네릭이다~! - 제네릭 사용이유
- .add() : 컴파일 시점에서, 컴파일에게 타입을 건네주어, 데이터 일관성 있게 모을 때, 정확하게 타입체킹한다.
- 타입정보를 안주면, List는 T자리에는 Object로 대체하여, 컴파일러가 타입체크를 못해준다.
- 컴파일러가 몰랐기 때문에, compile에러 대신 runtime시 에러가 난다.
- 즉, runtime시에만 발생하는 버그(모은 데이터가 일광성이 없는 버그)를 미리 방지한다.
- 해당 list에는 어떤 타입만 들어갈지 정해서, 일관성있게 코드를 작성해야하는데, 이런 부분을 도와준다.
- intellij 빨간줄 -> compile에러 by 제네릭
- 타입정보를 안주면, List는 T자리에는 Object로 대체하여, 컴파일러가 타입체크를 못해준다.
- .get():
1번에서 일관성 없게 모은 Object타입의 list
라면? 꺼내서 사용할 때, 매번 다운캐스팅해야한다. 실수 할 수도 있음.- 제네릭은 적절한 타입캐스팅(다운캐스팅)을 알아서 해준다.
-
똑같은 코드 Type만 다른 class를 또 생성할 필요 없다
- Type만 바꾼 class재사용이 가능해진다.
- .add() : 컴파일 시점에서, 컴파일에게 타입을 건네주어, 데이터 일관성 있게 모을 때, 정확하게 타입체킹한다.
- raw Type
- 좁은 것을 타입 파라미터 없는 raw Type
List
에 할당해주는 것은 가능하지만- 그 순간부터 가지고 있던 내용물의 Type을 잃어먹는다.
- 다시 좁은 쪽으로 할당시키면, 일단 해당 타입 파라미터에 맞게 할당은 되지만
- 꺼내는 순간부터 가지고 있던 내용물 Type <-> 타입파라미터 타입이 달라 에러가 난다.
-
raw Type을 쓰지마라.
-
더불어, 컴파일시
Problems
에 뜨는 워닝을 그냥 넘어가지마라.- 워닝을 체크하고 Build >
Rebuild Project
로 해결하면서 확인하자. - 상세하게 워닝을 보는 방법은
설정 > java compiler > 추가 파라미터
-> 에러창에 나온-Xlint
등을 붙혀서 다시 컴파일한다.
- 워닝을 체크하고 Build >
-
더불어, 컴파일시
- 좁은 것을 타입 파라미터 없는 raw Type
-
raw Type으로 중간에 넣어도 컴파일 에러가 안나는 이유
-
java 5 제네릭 이전의 코드는 모두
raw Type List
이었기 때문에-
개발자가 체크하고, 변환해서 사용해야한다.
-
개발자가
raw Type List
를 좁은 곳으로 할당할 때 생기는 문제인Unchecked assignment
에서는 @SuppressWarning(“unchecked”)로 옛날 코드임을 명시해준다final List<Integer> ints = Arrays.asList(1, 2, 3); List rawInts = ints; // 3) 반대의 상황은? // -> raw Type을 -> 다시 변<구>제에 넣으면? @SuppressWarnings("unchecked") final List<Integer> ints2 = rawInts;
-
-
public class Generics {
//2. staic main에 사용할 static 클래스를 만드는데, 클래스 옆에 <T> 뻐금괄호로 [타입파라미터]를 추가한다.
// - 타입 파라미터는 type variable로 불린다. 내부에서 변수처럼 T t;로 쓸 수 있기 때문
static class Hello<T> {
//6. Hello<T> class에 옆에 선언한 타입 파라미터에 매핑이 되어서
// -> 클래스 내부에서는 1) 필드Type 2) 메서드 파라미터의 Type 3) 메서드 응답Type으로 사용되기도 한다.
//new Hello<String>(); ->
//1) T t;
//2) Object method(T t) {return null;}
//3) <T> Obejct method(T t){return null};
}
//3. 일반적인 (메서드)파라미터는 파라미터 자리에 신언 -> 밖에서 해당Type의 input(값, 객체)를 넣어준다.
static void print(String value) {
System.out.println(value);
}
//1. main메서드 ps vm으로 만들기
public static void main(final String[] args) {
//4.
print("Generics");
//5. 타입 파라미터에도 메서드 파라미터와 마찬가지로 실제 뭔가(타입)를 넣어주는데 [타입 인자]라고 한다.
// -> 실제 <Type>정보(타입 인자)를 타입 파라미터 T자리에 넣어주면 -> 6.
//new Hello<String>(); //type argument
//7. 제네릭 사용이유
List<String> list = new ArrayList<>();
// 1) 넣을 때, 컴파일 에러가 나서, 일관성있게 타입체킹을 해준다
//list.add(4)
list.add("str");
// 2) 꺼내서 사용할 때, 매번 다운캐스팅해야한다. 실수 할 수도 있음.
//final String s = (String) list.get(0);
final String s = list.get(0);
//3) 타입만 바꿔서 class재사용이 가능해진다.
//8. raw Type?! (구체형 지정했는데 타입파라미터 없이 변수로 받는 경우)
// 1) 우항은 호.<구>.제를 해놨는데, -> 변<구>제로 변환없이 -> 좌측 변수는 제네릭 타입 없는 변수(raw Type)에 받는 경우
// -> 괜찮다?!
//new ArrayList<Integer>();
List rawList = new ArrayList<Integer>();
// 2) 호<>.구.지 -> 변.<구>.제 를 -> 다시 그대로 제네릭 타입 없는 변수(raw Type)에 할당하는 경우
// -> 괜찮다?!
final List<Integer> ints = Arrays.asList(1, 2, 3);
List rawInts = ints;
// 3) 반대의 상황은?
// -> raw Type을 -> 다시 변<구>제에 넣으면?
@SuppressWarnings("unchecked") final List<Integer> ints2 = rawInts;
// Unchecked assignment: 'java.util.List' to 'java.util.List<java.lang.Integer>'
// --> raw Type으로 받는 순간, 다시 어떤 Type인지 모르는 상태가 된다.
// --> T상태를 다시 type argument가 있는 곳에 넣어줬으니,
// --> Object(우) -> Integer(좌)로 캐스팅해서 넣는 것이다.
// ---> 위험하다. 만약 Object에 integer 들어있는 것을 List<String>에 캐스팅해서 넣으면...
@SuppressWarnings("unchecked") final List<String> strs = rawInts; // raw type에 들어간 순간, 안에 내용물의 타입은 잊어먹고, 좁은 곳으로 캐스팅되서 들어갈 수 있게 된다.
// -> 문제는, type argument에 맞춰서 다운캐스팅되서 들어가 컴파일은 되지만, 꺼내올 때부터 문제가 된다.
// --> 내용물은 int, type은
final String s1 = strs.get(0);
// class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
// at Generics.main(Generics.java:67)
}
}
제네릭 메서드
-
인스턴스 메서드
-
스태틱 메서드 : class level type parameter꺼는 못씀
- class level의 type paramter는 인스턴스 생성시
class<T>
의 T가 정해져 사용되기 때문
- class level의 type paramter는 인스턴스 생성시
-
생성자
public class GenericsMethod { //0. class 혹은 interface 옆에 타입파라미터가 붙어서 시작하는 경우도 있지만, static class Hello<T> { } //1. method에서도 타입 파라미터를 붙일 수 있다. // -> String을 파라미터 받아서 출력했었지만 void print(String value) { System.out.println(value); } // --> 이젠 어떤 타입이든 받아서 .toString()으로 출력하면 어떤 타입이든 다 출력된다. // --> 이 때, class, interface옆에 <T>를 붙인 것처럼, [응답형 앞에 <T>를 붙여줘야한다.] <T> void print(T t) { System.out.println(t.toString()); } //3. static 메서드(인스턴스 안만들고 쓰는 메서드)에도 적용가능하다. static <T> void staticPrint(T t) { System.out.println(t.toString()); } //5. 주의!! // class<T> type variable은 -> 해당 class의 인스턴스가 만들어질 때 인자를 받아오게 되는데 // -> static은 인스턴스 만들지를 않고 사용하니, T를 모르는 상태 그대로 // -> static 메서드는, 인스턴스생성때 T를 채우는 class level이 아닌, method level에서 Type parameter를 사용해야한다. // --> class level type paramter로는 사용 불가능했던 static method는 // method level에서는 type paramter를 static method에서 사용 가능하다. //6. class level과 다르게 메서드 level type parameter를 지정하자. static <S> void staticPrint2(S s) { System.out.println(s.toString()); } //7. <,>로 2개 level(class, method)의 Type paramter를 섞어 쓸 수 있다. // -> 1개는 class level, 1개는 method level일 것이다. // 1) method level에 정의한 타입파라미터 -> 메서드 파라미터에서 사용 // 2) class level에 정의한 타입파라미터 -> 메서드 응답형에서 사용 가능 <S, T> T staticPrint3(S s) { System.out.println(s.toString()); return null; } //8. method level 1) 인스턴스메서드 2) 스태틱메서드(new)에 추가로 // -> 3) 생성자에서도 method level type paramter를 쓸 수 있다.(class level과 별개로) public GenericsMethod() {} // 생성자에서도 class level과 별개로 아무Type이나 받을 수 있다. // 생성자는 응답이 없으므로 생성자 메서드앞에 <>를 작성해주면 된다. public <S> GenericsMethod(S s) { } public static void main(final String[] args) { //2. 어떤 타입이든 받아서 출력되는지 확인해보자. new GenericsMethod().print("Hello"); new GenericsMethod().print(1); //4. method level의 타입파라미터는 static method에도 적용가능하다 GenericsMethod.staticPrint("Hello"); GenericsMethod.staticPrint(1L); } }
-
Bounded Type paratmer : 내부 로직에 의해 제한이 필요한 Type paramter
- 다양한 Type을 다 받을 수 있다 -> generic한 method
- 내부 알고리즘상 꼭 필요한 제약조건(Comparable을 구현한 Type만 들어와서 비교가능해야함)이 걸고 싶다 -> bound type parameter
public class GenericBounded { // 0. default로는 타입제한이 없은 <T>가 오지만 <T extends List> 등 // -> Bounded Type parameter ? // 1. 인스턴스 or static(for main) 메서드에서도 쓸 수 있다. // -> 변.<구>.제와 비슷하게, 메서드용 <구체형>제한은, 응답형 앞에 표기하는 <T>에서 해준다. //static <T extends List> void print(T t) {} // 2. bound제한을 줄 때, 1개만 할 수 있따 생각하지만, & 1개로 여러개 형을 줄 수 있다. // -> multiple bound ( 공통으로서 다 만족해야한다. OR옵션은 없다.) //static <T extends List & B & C> void print(T t) {} // -> class가 올 경우는 1개만, interface는 여러개 구현하는 것처럼 여러개 가능 static <T extends List & Serializable & Comparable & Closeable> void print(T t) { } //3. 예시) array + 기준값을 받아서, 기준값보다 큰 것의 갯수 구하기 // -> 제네릭 사용하지 않고, Integer로 fix static long countGreaterThan(Integer[] arr, Integer elem) { return Arrays.stream(arr) .filter(s -> s > elem) .count(); } // 6. type만 다르니 generic method을 도입하자.(구체형 -> T로 변경) // static <T> long genericCountGreaterThan(T[] arr, T elem) { // 10. 내부 비교로직이 들어가는 method level type paramter는 Comparable<T>로 구현한 Type으로 제한을 걸어줘야한다 // -> 이제 genericCountGreaterThan() 인자는 comparable구현하여 비교가능한 애들만 넘어온다. // --> cf) Comparable은 impl시 해당 class형을 제네릭파라미터를 받았었다. -> 여기선 T static <T extends Comparable<T>> long genericCountGreaterThan(T[] arr, T elem) { return Arrays.stream(arr) // 7. 문자는 크기비교를 숫자Type만 가능한 부등호로 비교하고 있었다. //.filter(s -> s > elem) // 8. 숫자 이외에 더 일반적으로 & Type(객체)의 순서를 비교(크기 비교)하려면 // - Comparable <- 이 인터페이스를 작성해서 찾아 들어가서 확인도 같이해보자. //Comparable // - Comparable 구현후 -> 내부 compareTo 라는 메서드를 구현해서 비교할 수 있지만. // - s는 T타입 이라서 .compareTo가 있는지 도 모른다는게 문제다 //.filter(s -> s.compareTo(elem)) //9. 그렇다면 T타입은... 미리 좀 내부에서 Comparable을 구현해서 비교가 되는 놈으로 제한하자 // -> type parameter를 (비교가능한 객체로) 제한하려면? -> bounded type parameter를 쓰면 된다. //11. 이제 s가 bounded type parameter로서 comparable구현Type으로 보장되었다 // -> compareTo가 사용가능하다. 이 return값은 int 1, 0, 1이니.. t/f가 아님을 조심하자. .filter(s -> s.compareTo(elem) > 0) .count(); } public static void main(final String[] args) { final Integer[] integers = {1, 2, 3, 4, 5, 6, 7}; System.out.println("countGreaterThan(integers, 4) = " + countGreaterThan(integers, 4)); //4. 숫자말고, 다른Type인 문자도 크기 비교가 가능하다~ final String[] strings = {"1", "2", "3", "4", "5", "6", "7"}; //countGreaterThan(strings) -> 구체형이 fix되어있으니 컴파일 에러 //5. generic 도입을 고민한다. //12. generic + 비교를 위한 bound type paramter가 적용이 끝났으면 사용해보자 // -> string도 String을 찍어서 들어가보면, impl Comparabel<String>을 구현하고 있기 때문이다. System.out.println("genericCountGreaterThan(strings, \"3\") = " + genericCountGreaterThan(strings, "2")); } }
제네릭과 상속
-
타입 파라미터의 상속관계는, 제네릭 적용 class 상속관계에 아무 영향을 주지 않는다.
-
같은 추상체의 SubType(추상체가 가진 type paramter도 같으면)이면
-
구상체만의 type paramter는 달라고 할당된다.
static class MyList<E, P> implements List<E> {} // final List<String> s1 = new MyList<String, Integer>(); final List<String> s2 = new MyList<String, String>();
-
제네릭 타입 추론
-
java 7이하는 type paramter를 추론 못하는 경우도 있을 수 있다. 이 때는
메서드 호출()
앞에<Type>
를 붙여준다. -
Collections.emptyList(); 같은 경우, 들어가는 parameter가 없기 때문에 추론이 안될 수 있다. -> 메서드앞에
<Type>
을 명시해준다.//java 7이하 타입추론안될 때 명시해주기 final List<String> c = Collections.<String>emptyList();
제네릭과 와일드카드
-
?
: wildcards -> 모르고, 알필요도 없다. -
T
: 선언시점에서 T는 뭔지 모르지만, 타입 정해지면 그것으로 사용하겠따 -
<?>
-
<? extends Object>
와 동치이다. Object의 기능만 사용한다 -
List<?>
: 구상체들 기능은 필요없고, 추상체 List의 기능만 사용한다. -
**명확하게
public class GenericsWildcards { 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)); } public static void main(final String[] args) { final List<Integer> integers = Arrays.asList(1, 2, 3); // 1. Object-Integer와 달리, List<Object> 와 List<Integer>는 SubType의 관계가 아니므로 인자로 못 넣는다. // - my) 추상형 변수 = 구상형 객체 [할당] == 호출시( 구상형 인자 ) -> 메서드 선언부(추상체 파라미터) 호출이나 마찬가지 // -> 인자 대입은 구상형 -> 내부 파라미터는 추상형으로서, SubType만 올 수 있는데 // --> List<Integer> 인자 -> List<Object> 파라미터는 SubType관계가 아니므로 들어갈 수 없다. // --> 즉, integer는 Object의 서브타입이지만, 리스트integer는 리스트object의 서브타입이 아니므로 호출시 대입 불가능 // -> 명확하게 <형>을 정해놓으면, SubType만 할당/대입 가능하다!!! //printList(integers); //compile Error //2. ? 와일드카드는 명확하게 어떤형X, 어떤 형이든 상관없이 받아서, 추상형의 기능만 이용한다. printList2(integers); } }
-
-
와일드카드 제한점?!
public class GenericsWildcards { 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)); } //3. class 2개를 만들어보자 static class A { } static class B extends A { } public static void main(final String[] args) { final List<Integer> integers = Arrays.asList(1, 2, 3); // 1. Object-Integer와 달리, List<Object> 와 List<Integer>는 SubType의 관계가 아니므로 인자로 못 넣는다. // - my) 추상형 변수 = 구상형 객체 [할당] == 호출시( 구상형 인자 ) -> 메서드 선언부(추상체 파라미터) 호출이나 마찬가지 // -> 인자 대입은 구상형 -> 내부 파라미터는 추상형으로서, SubType만 올 수 있는데 // --> List<Integer> 인자 -> List<Object> 파라미터는 SubType관계가 아니므로 들어갈 수 없다. // --> 즉, integer는 Object의 서브타입이지만, 리스트integer는 리스트object의 서브타입이 아니므로 호출시 대입 불가능 // -> 명확하게 <형>을 정해놓으면, SubType만 할당/대입 가능하다!!! //printList(integers); //compile Error //2. ? 와일드카드는 명확하게 어떤형X, 어떤 형이든 상관없이 받아서, 추상형의 기능만 이용한다. printList2(integers); //4. final List<B> bList = new ArrayList<>(); // 4-1. A 이하의 SubType형 list를 받을 수 있게하고, List<B>를 넣었더니.. List<? extends A> la = bList; // 4-2. 호.<구>.지로 완전 내부가 바꼈나??? A를 add하면 compile error //la.add(new A()); //compile Error // 4-3. 그렇다고 치기엔, B도 add가 안된다. //la.add(new B()); // 4-4. 와일드카드의 제한점으로서... add는 null밖에 안된다. -> why? la.add(null); } }
심화
List< >
)에 대해 Type Paramter 적용 vs WildCards 적용
type paramter를 필요로 하는, 제네릭 메서드의 파라미터(Type Paramter 적용
-
와일드카드를 적용한 것과 다르게
-
한번 정의된
type parameter
는 추가 파라미터, body내부 인스턴스 변수, 응답 값 등메서드 내부 다양한 곳에서 T를 활용
할 수 있다.//1. type paramter를 메서드 파라미터(List< >)에 사용한 경우 static <T> void method1(List<T> list) { }
//1. type paramter를 메서드 파라미터(List< >)에 사용한 경우 static <T> T method1(List<T> list, T t) { // 1) 메서드파라미터 // 2) 내부 인스턴스 변수의 Type T value; // 3) 응답Type으로도 type paramter를 활용할 수 있다. return null; }
-
unbounded WildCards 적용
-
내부에서 활용할 수 도 없는 와일드 카드를 type paramter로 왜 사용하는 것일까?
static void method2(List<?> list) { }
-
컬렉션를 받고 싶다면, 컬렉션<?> 와일드카드를 이용해야한다.아무원소>
-
List<Object>
로 형이 명시된 경우,List<SubType>
의 자손이 아니므로 파라미터가 인자를 받을 수 없게 됨.List<?>
는List<Obejct의 SubType>
아무거나 다 받을 수 있다.
-
-
그러나
unbound wild card
인<?>
사용시에는 제약이 있다.-
특정 Type의 값을 add등 넣지 못한다.
-
특정 Type의 원소들과 관련된 기능은 사용못하고 -> 가장 추상체List, Object만의 기능만 사용한다.
static void method2(List<?> list) { //3.그러나 `unbound wild card`(extends 등 사용안한 것)인 `<?>`사용시에는 제약이 있다. //3-1. 해당 자료구조에 구체적인 Type의 값(객체)을 넣을 수 없다. // -> 해당 자료구조에는 null만 넣을 수 있다. //list.add("1"); //compile error //list.add(1); //compile error //list.add(null); // runtime UOE error //3-2. 가장 추상체 = List만의 기능 + Object만의 기능만 사용가능하며, 각 Type의 원소들과 관련된 기능은 사용 못한다. //3-2-1. List만의 기능 //list.clear();// runtime UOE error list.size(); final Iterator<?> iterator = list.iterator(); //3-2-2. Object만의 기능 list.toString(); list.equals(null); }
-
-
unbound wild card 사용 예시1
-
list를 받아 비어있으면 true반환하는
isEmpty()
-
list의 원소가 어느type이든, 어떤 종류의 list라도 받을 수 있게
<T>
와일드카드로 파라미터 지역변수를 지정해주자private static <T> boolean isEmpty(final List<T> list) { return list.size() == 0; }
-
메서드 파라미터속 type parameter T를 내부에서 사용하지 않았다면, T -> 언바운드 wild card로 바꿀 수 있다.
//메서드 파라미터속 type parameter T를 내부에서 사용하지 않았다면 // -> 타입 파라미터 대신 와일드카드를 적용시킬 수 있다. // -> (타입파라미터 안쓰면 -> 응답형 앞에 명시한 것도 삭제해주기) private static boolean isEmpty(final List<?> list) { return list.size() == 0; } //4. unbound wild card 사용 예시를 들기 위해 // -> 어느 list를 받아 비어있으면 true를 반환하는 isEmpty()를 만든다 가정한다. final List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); System.out.println(isEmpty(integerList));
-
-
지네릭 메소드를 쓰는 이유: 지네릭 type parameter로 정의된 T type의
원소
에 관심 있어,메서드 내부 T를 사용해야만
하는,add/remove/set 원소단위로 기능
이 들어가는 경우 -
지네릭 메소드 대신 와일드카드를 쓰는 이유: 자료구조 속 원소에는 관심X -> List단위로서의 기능만 + Object의 기능만 쓸 경우
-
unbound wild card 사용 예시2
-
frequnecy( list, 3) -> 특정 원소가 몇번 중복되어있는지
-
List의 원소 Type, 개별원소Type 모두
T
로 대체해준다.private static <T> long frequency(final List<T> list, final T elem) { return list.stream() .filter(s -> s.equals(elem)) .count(); }
-
빈도 검사에서도
내부에서 T가 안쓰였으니.. 와일드카드로 변환
이 가능할 것 같은데- type parameter를 정의하는 곳에만 들어갈 수 있다.
-
컬렉션<T>
의 파라미터 변수는컬렉션<Object>
로컬렉션<Subtype>
을 받을 수 없지만,단일 TypeParameter
는 추상-구상 SubType관계가 유지되므로Object
로어느 타입이든 받는 T
를 대신할 수 있다.- 즉 메서드 파라미터 속
T t
->? t
(X) ->Object t
(O)로 바꿔도 된다!
- 즉 메서드 파라미터 속
-
내부 로직에서 사용되는 메서드를 봐도
- equals -> Object에 정의된 기능 ->
?
로 변환 가능! - 와일드카드의 사용법 중에 1가지는 Object에 정의된 기능은 사용 허용!이다
private static long frequency(final List<?> list, final Object elem) { return list.stream() .filter(s -> s.equals(elem)) .count(); } //5. 빈도를 세는 것(list + 개별원소)도 내부에서는 개별원소 기능이 없다 -> filter + equals기능만 사용 -> 지네릭 메서드 대신 와일드카드로 final List<Integer> duplicateIntegerList = Arrays.asList(1, 2, 3, 4, 5, 3, 2); System.out.println("frequency(duplicateIntegerList, 3) = " + frequency(duplicateIntegerList, 3));
- equals -> Object에 정의된 기능 ->
-
bounded WildCards 적용
-
max 메서드 생성
-
원래는 integer로 시작해서 T로 변환해야하지만, 바로 제네릭 메서드로 작성해보자.
-
stream을 이용해서 max를 구한다면
- loop -> 제일 작은 초기값 세팅후 -> 업데이트 방식과 다르게
- reduce의 초기값 없이 바로 BinaryOperator 사용방식으로, 누적해서 2개의 값씩 비교해나간다
-
optional반환인데 값만 강제로 꺼내고 싶다면 .get()을 때리면 된다.
- 만약
Optional에서 .get()으로 값을 못꺼낸 상황
일 땐,NoSuchElementException
을 낸다.
//2. 원래는 Integer부터 만들어야 정상이지만, 제네릭을 계속 썼다보니 제네릭 메소드로 만들어보자. //private static <T> void max(final List<T> list) { //2-2. 제네릭 메서드의 type parameter에서 upper bound는 파라미터 자리가 아닌 [응답형 앞]에서 준다. private static <T extends Comparable<T>> T max(final List<T> list) { return list.stream() //2-1. max를 loop를 구할 땐, 가장 작은값을 후보 변수에 세팅 -> 계속 업데이트하면서 구하는데, stream으로는? // -> reduce의 2번째 방식(초기값 identity 따로 안주고 첫+둘 -> 합+셋 -> 순으로 연산 하기위해)으로 작성해보자. // -> 연산후 연산결과의 Type이 현재 원소들과 같은 Type으로 되돌려줘야한다. // -> 누적해가면서 <2변수를 비교후 큰값을 남기기>가 max값이다. // --> Integer였다면 a > b ? a : b 의 3항연산자를 써서 (비교후 t/f별 return) 했을 것이다. //.reduce((a,b) -> a > b ? a : b) // -> 하지만, 제네릭을 통해 아무Type T -> [부등호를 통한 숫자비교]이외의 비교를 위해서 // --> Comparable 구현으로 제한한 Type에 대해 compareTo로 비교한다. .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) //2-3.stream에 원소가 없을 경우 optional로 반환하는데, get()을 통해 값만 강제로 꺼낼 수 도 있다. // -> 값이 없는데 get을 시도했을 때 예외가 발생하는데, 이것은 자연스런 예외라고 생각할 수 있다. // NoSuchElementException: No value present // default값이나 or 특정예외를 발생시키는 코드를 작성안해도, 없는 값 get시도시 예외는 자연스러운 것으로 봐도 된다. .get(); }
- 만약
-
-
내부T 안쓴 것처럼 보이지만, stream -> 개별 원소 접근 -> T쓴 것 과 마찬가지로 wildcard로 변환 불가능.의 상태이다. 하지만,
wild cards가 개입될 여지
가 있다.-
wild card 도입 전
private static <T extends Comparable<T>> T max(final List<T> list) { return list.stream() .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) .get(); }
-
stream 아니였으면, 내부 T를 사용 -> wildcard로 변환은 불가능한 상태
T currentMax = list.get(0); for (final T t : list) { if (t.compareTo(currentMax)>0) { currentMax = t; } } return currentMax;
-
-
내부에 개별원소 T의 사용으로
<?>
대신<T>
의 제네릭 메서드로 정해졌어도- 내부에서 사용되는
파라미터 속 컬렉션의 type parameter
는upper bound with ?
으로 수정가능하고 외부
에서 사용되는응답형 앞의 Comparable<T> type paramter
는lower bound with ?
로 수정 가능하다
//3. 내부T 안쓴 것처럼 보이지만, stream -> 개별 원소 접근 -> T쓴 것 과 마찬가지로 wildcard로 변환 불가능.의 상태이다. //3-1. 비교메서드compareTo() 의 사용은 T type 원소뿐만 아니라, T의 SubType들도 인자로 들어와 비교될 수 있다. // 비교메서드의 인자뿐만 아니라 컬렉선<T>에서 각 원소가 [메서드 내부에서 사용=소모]되는 경우라면 [와일드카드를 사용한 upper bound type paramter]로 넘긴다는 가이드라인 있다. // -> 메서드 파라미터의 [개별 원소가 메서드 내부에 사용되는 컬렉션의 type parameter] List<T> -> List<? extends T> // private static <T extends Comparable<T>> T max(final List<T> list) { //3-2. 반대로, Comparable<T>에 걸린 T는 메서드내부에서 정해져 사용되지 않고, 메서드 밖에서 사용되는 Type paramter라서 // -> lower bound로 정의해준다. // private static <T extends Comparable<T>> T max(final List<? extends T> list) { private static <T extends Comparable<? super T>> T max(final List<? extends T> list) { return list.stream() .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) .get(); }
- 내부에서 사용되는
wild card로 정의 가능하다면 지네릭 메소드 대신 와일드카드로 메서드 작성하는 이유
- oracle문서: T type parameter의 지네릭 메소드는 내부 로직에 T type을 사용해서 처리하겠다라는 의미가 담겨있다고 한다.
- Type parameter는 내부 구현이 노출이 된다.
- 와일드 카드로 작성을 권장한다.
< ? extends Comparable>
은 허용한다. 하지만 메서드 내부에 T
를 이용하느 코드가 나온다면, 응답형 앞에 T를 <T extends Comparable>
로 바꿔주면 된다.
와일드카드를 사용한 상태에서 비교를 위한 compareTo 사용을 위해 bound wild card인 private static boolean isEmpty(final List<?> list) {
return list.size() == 0;
}
private static boolean isEmpty2(final List<? extends Comparable> list) {
return list.size() == 0;
}
private static <T extends Comparable> boolean isEmpty3(final List<T> list) {
return list.size() == 0;
}
PECS는 사실… 메서드내부 사용되는 T는 E / 메서드외부에서 사용되는 T는 S 이다.
-
어느타입의 List든
List<T>
를 받아 max(list)를 구현-
reduce로 누적하여 연산하되, 둘중에 큰 값만 남기도록 한다.
- 초기값 없이 바로 BinaryOperator작성하여, 1과2 -> 연산값과 3번째, … ,
- 매번 연산값은 첫번째 요소와 동일한 Type이 되어야한다.
- 둘 중에 큰 값만 남겨 return해야하므로 삼항연산자를 활용했다.
-
Integer로 시작했었지만, Number Type 이외의 값도 비교해야하므로
- 들어오는 T에 대해 응답형앞에서
<T extends Comparable<T>>
로 Comparable 인페를 구현한 Type으로 bound한다.
private static <T extends Comparable<T>> T max(final List<T> list) { return list.stream() //2-1. max를 loop를 구할 땐, 가장 작은값을 후보 변수에 세팅 -> 계속 업데이트하면서 구하는데, stream으로는? // -> reduce의 2번째 방식(초기값 identity 따로 안주고 첫+둘 -> 합+셋 -> 순으로 연산 하기위해)으로 작성해보자. // -> 연산후 연산결과의 Type이 현재 원소들과 같은 Type으로 되돌려줘야한다. // -> 누적해가면서 <2변수를 비교후 큰값을 남기기>가 max값이다. // --> Integer였다면 a > b ? a : b 의 3항연산자를 써서 (비교후 t/f별 return) 했을 것이다. //.reduce((a,b) -> a > b ? a : b) // -> 하지만, 제네릭을 통해 아무Type T -> [부등호를 통한 숫자비교]이외의 비교를 위해서 // --> Comparable 구현으로 제한한 Type에 대해 compareTo로 비교한다. .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) //2-3.stream에 원소가 없을 경우 optional로 반환하는데, get()을 통해 값만 강제로 꺼낼 수 도 있다. // -> 값이 없는데 get을 시도했을 때 예외가 발생하는데, 이것은 자연스런 예외라고 생각할 수 있다. // NoSuchElementException: No value present // default값이나 or 특정예외를 발생시키는 코드를 작성안해도, 없는 값 get시도시 예외는 자연스러운 것으로 봐도 된다. .get(); }
- 들어오는 T에 대해 응답형앞에서
-
-
(stream안썼으면) 내부에서 T가 사용되는 상황(stream으로는 개별원소에 접근하는 상황)이므로
<T> 대신 <?> = 추상체 자체기능 + Object의기능만 사용
은 불가능한 지네릭 메소드여야만 한다- 하지만
와일드카드가 끼어들만 여지
가 남겨져있다
private static <T extends Comparable<T>> T max(final List<T> list) { return list.stream() .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) .get(); }
- 하지만
-
위에서 보는 것처럼
-
내부에서 개별적으로 사용되는
메서드 파라미터 속 List<T>
에서의T
는a.비교메서드( b )
에서 a와 b 자리에는 T type대신SubType
도 들어가서 비교될 수 있기 때문에 T의 subtype을 의미하는List<? extends T>
를 대신사용할 수 있다. -
외부 Comparable 클래스에서 compareTo정의시 사용될
Comparable<T>
의 T는비교메서드를 구성
하는 Type이라고 볼 수 있다.- Integer끼리의 비교는 a - b 처럼 compareTo가 간단하게 구성될지 몰라도
- 어느타입이든 비교되려면,
Object객체 2개를 비교
할 수 있는 compareTo가 만들어져야한다. - 그래서 Comparable처럼
외부에서 사용될 T는 T에 대해 SuperType으로 구성
되어도 된다.
private static <T extends Comparable<? super T>> T max(final List<? extends T> list) { return list.stream() .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) .get(); }
-
-
바깥에서 쓰는 T의 대한 예시 -> Collections.max( list , comparator)
public static void main(final String[] args) { final List<Integer> list = Arrays.asList(1, 2, 3); //4. 바깥에서 쓰일 Comparable<T>의 T는 <T extends Comparable<? super T>>처럼 SuperType이 들어가도 된다?? 를 증명해보기 // 4-1. List 예시는, 자체 comparator가 내부에 내장 되어있어, Comparable를 구현안해도 알아서 집계함수내에서 집계된다. System.out.println("Collections.max(list) = " + Collections.max(list)); // 4-2. 이번엔 비교대상obj의 class가 직접 Comparable을 구현하는 것이 아니라, // 한 단계 위층에서, 직접 2개의 원소를 받아 비교가능하도록 만드는 비교자 Comparator인터페이스를 만드는 경우 // 집계메서드의 다음인자로 만들어서 던진다. //Collections.max(list, Comparator) //Comparator의 내부 구현을 보기 위해 잠시 적어두고 타고 들어갔다옴. // -> Comparator은 인터페이스로 증 @FunctionalInterface로서, 외부에서 인자를 던질 때, 람다식으로 구현가능하다. // --> int compare(T o1, T o2); 파라미터 2개 -> (a,b)로 시작 && 응답 int -> 1개 int로 나오도록 람다식 마무리 //Collections.max(list, (a, b) -> a - b ); // ---> 그러나, T 타입을 받아 비교하도록 되어있으므로 [람다식의 Type paramter 다운? 캐스팅]을 사용할 수 있다. // cf) 람다식은 타입을 캐스팅할 수 있는 것이 장점이라고 한다. // ----> Integer List의 원소들끼리 비교라면, Compator<Integer>로 람다식의 T타입을 캐스팅해서 쓰면 되지만 // 여기에서는 지네릭 -> 모든 타입 -> Object로 [람다식 타입 캐스팅]을 시도하여 obj vs obj 비교가 가능하게 한다. // -----> 이렇게 되면, object - object 의 부등호 비교가 말을 안듣게 된다. //Collections.max(list, (Comparator<Object>) (a, b) -> a - b); // a-b error! // ------> a와 b가 integer가 아니라 object가 되었다고 가정하고 // **object의 공통 .toString()**을 통해 compareTo로 비교한다고 가정하고 작성한다. Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString())); // a-b error! //4-3. 이렇게 2개의 obj로 비교하는 Comparator 인터페이스 구현체를 람다식타입캐스팅해서 인자로 던졌는데 잘 동적을 한다 System.out.println( "Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString())) = " + Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString()))); // 4-4. 여기서 하고 싶은말은, Comparable or Comparator에 붙은 <T>는 비교할 객체 2개의 type이며 // -> 그 T에는 SuperType이 와도 된다는 이야기를 하고싶은 것 // --> 하위 경계만 list의 T인 Integer로 잡혀있을 뿐 -> ? super T 로서, Integer의 SuperType이 와도 된다. //5. 이제 Comparable< ? super T>를 통해 -> compareTo를 사용할 객체는 T보다 더 SuperType 특히, Object를 줘도 가능하다는게 밝혀졌다. // -> Comparable내부 구조를 object. 비교메서드 ( object )로 바꿔놓은 상태다. // 그렇다면, max(final List<? extends T> list) 에서 list의 원소는 [비교될 객체 a, b자리에 구현체 SubType이 들어갈 수 있다]는 어떻게 확인할까? // -> 비교 구조는 SuperType 특히, Object1. 비교메서드 ( Object 2)로 잡힌 상태로서, // --> 각각의 Object1, 2 자리에 SubType(구현체)가 들어가는 것은 자연스러운 일로 이해할 수 있다. // -> SubType의 원소가 비교메서드 자리에 들어갈 수 있는지 판단하려면 // ---> max()함수의 T를 Integer의 추상체인 Number로 고정시켜 호출하면, // 파라미터속 T -> NumberType인데, 인자로 던진 Integer 타입이 들어가도 잘 동작하는 것을 확인할 수 있다. System.out.println( "Collections.<Number>max(list, (Comparator<Object>)(a,b) -> a.toString().compareTo(b.toString())) = " + Collections.<Number>max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString()))); //6. 어떨때는 upper bound? 어떨때는 under bound? -> java에서 설명하는 기준이 있다. // 1) 넘기는 파라미터 type이 메서드 내부에서 사용되는 경우 -> 상위경계(upper bound)를 쓰면 되고 // 2) 외부에서 정의되는 메서드에 사용될 경우 ex> Comparable내부에서 사용될 T -> 하위 경계(under bound)를 쓰면 된다. // -> 가장 잘보여주는 예가 Collections 내부의 copy()메서드다 // cf) alt+7(Structure)에서 ctrl+f에서 찾아보기 // public static <T> void copy(List<? super T> dest, List<? extends T> src) { // 소스 src는 안에서 copy메서드 내부에서 소모되는 것이므로 <? extends T> 구현체 와도 된다. // 데스티네이션 dest는 copy메서드 내부에서 [만들어진 뒤 밖으로 내보내 사용 될 것] -> 구조를 추상체로 잡아주도록 <? super T>로 와일드카드를 준다. // - 안에서 만들어지긴 하지만 ouput으로서 밖에서 사용되는 것임. /// 3) 귀찮으면, 잘모르겠으면 그냥 <T>로만 지정해서 쓰면 된다.. }
public class GenericsWildCard {
public static void main(final String[] args) {
final List<Integer> list = Arrays.asList(1, 2, 3);
//1. list가 들어오면 그 원소들 중 가장 큰 값을 반환하도록 한다. 만든다.
System.out.println("max(list) = " + max(list));
//4. 바깥에서 쓰일 Comparable<T>의 T는 <T extends Comparable<? super T>>처럼 SuperType이 들어가도 된다?? 를 증명해보기
// 4-1. List 예시는, 자체 comparator가 내부에 내장 되어있어, Comparable를 구현안해도 알아서 집계함수내에서 집계된다.
System.out.println("Collections.max(list) = " + Collections.max(list));
// 4-2. 이번엔 비교대상obj의 class가 직접 Comparable을 구현하는 것이 아니라,
// 한 단계 위층에서, 직접 2개의 원소를 받아 비교가능하도록 만드는 비교자 Comparator인터페이스를 만드는 경우
// 집계메서드의 다음인자로 만들어서 던진다.
//Collections.max(list, Comparator) //Comparator의 내부 구현을 보기 위해 잠시 적어두고 타고 들어갔다옴.
// -> Comparator은 인터페이스로 증 @FunctionalInterface로서, 외부에서 인자를 던질 때, 람다식으로 구현가능하다.
// --> int compare(T o1, T o2); 파라미터 2개 -> (a,b)로 시작 && 응답 int -> 1개 int로 나오도록 람다식 마무리
//Collections.max(list, (a, b) -> a - b );
// ---> 그러나, T 타입을 받아 비교하도록 되어있으므로 [람다식의 Type paramter 다운? 캐스팅]을 사용할 수 있다.
// cf) 람다식은 타입을 캐스팅할 수 있는 것이 장점이라고 한다.
// ----> Integer List의 원소들끼리 비교라면, Compator<Integer>로 람다식의 T타입을 캐스팅해서 쓰면 되지만
// 여기에서는 지네릭 -> 모든 타입 -> Object로 [람다식 타입 캐스팅]을 시도하여 obj vs obj 비교가 가능하게 한다.
// -----> 이렇게 되면, object - object 의 부등호 비교가 말을 안듣게 된다.
//Collections.max(list, (Comparator<Object>) (a, b) -> a - b); // a-b error!
// ------> a와 b가 integer가 아니라 object가 되었다고 가정하고
// **object의 공통 .toString()**을 통해 compareTo로 비교한다고 가정하고 작성한다.
Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString())); // a-b error!
//4-3. 이렇게 2개의 obj로 비교하는 Comparator 인터페이스 구현체를 람다식타입캐스팅해서 인자로 던졌는데 잘 동적을 한다
System.out.println(
"Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString())) = "
+ Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString())));
// 4-4. 여기서 하고 싶은말은, Comparable or Comparator에 붙은 <T>는 비교할 객체 2개의 type이며
// -> 그 T에는 SuperType이 와도 된다는 이야기를 하고싶은 것
// --> 하위 경계만 list의 T인 Integer로 잡혀있을 뿐 -> ? super T 로서, Integer의 SuperType이 와도 된다.
//5. 이제 Comparable< ? super T>를 통해 -> compareTo를 사용할 객체는 T보다 더 SuperType 특히, Object를 줘도 가능하다는게 밝혀졌다.
// -> Comparable내부 구조를 object. 비교메서드 ( object )로 바꿔놓은 상태다.
// 그렇다면, max(final List<? extends T> list) 에서 list의 원소는 [비교될 객체 a, b자리에 구현체 SubType이 들어갈 수 있다]는 어떻게 확인할까?
// -> 비교 구조는 SuperType 특히, Object1. 비교메서드 ( Object 2)로 잡힌 상태로서,
// --> 각각의 Object1, 2 자리에 SubType(구현체)가 들어가는 것은 자연스러운 일로 이해할 수 있다.
// -> SubType의 원소가 비교메서드 자리에 들어갈 수 있는지 판단하려면
// ---> max()함수의 T를 Integer의 추상체인 Number로 고정시켜 호출하면,
// 파라미터속 T -> NumberType인데, 인자로 던진 Integer 타입이 들어가도 잘 동작하는 것을 확인할 수 있다.
System.out.println(
"Collections.<Number>max(list, (Comparator<Object>)(a,b) -> a.toString().compareTo(b.toString())) = "
+ Collections.<Number>max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString())));
//6. 어떨때는 upper bound? 어떨때는 under bound? -> java에서 설명하는 기준이 있다.
// 1) 넘기는 파라미터 type이 메서드 내부에서 사용되는 경우 -> 상위경계(upper bound)를 쓰면 되고
// 2) 외부에서 정의되는 메서드에 사용될 경우 ex> Comparable내부에서 사용될 T -> 하위 경계(under bound)를 쓰면 된다.
// -> 가장 잘보여주는 예가 Collections 내부의 copy()메서드다
// cf) alt+7(Structure)에서 ctrl+f에서 찾아보기
// public static <T> void copy(List<? super T> dest, List<? extends T> src) {
// 소스 src는 안에서 copy메서드 내부에서 소모되는 것이므로 <? extends T> 구현체 와도 된다.
// 데스티네이션 dest는 copy메서드 내부에서 [만들어진 뒤 밖으로 내보내 사용 될 것] -> 구조를 추상체로 잡아주도록 <? super T>로 와일드카드를 준다.
// - 안에서 만들어지긴 하지만 ouput으로서 밖에서 사용되는 것임.
/// 3) 귀찮으면, 잘모르겠으면 그냥 <T>로만 지정해서 쓰면 된다..
}
//3. 내부T 안쓴 것처럼 보이지만, stream -> 개별 원소 접근 -> T쓴 것 과 마찬가지로 wildcard로 변환 불가능.의 상태이다.
//3-1. 비교메서드compareTo() 의 사용은 T type 원소뿐만 아니라, T의 SubType들도 인자로 들어와 비교될 수 있다.
// 비교메서드의 인자뿐만 아니라 컬렉선<T>에서 각 원소가 [메서드 내부에서 사용=소모]되는 경우라면 [와일드카드를 사용한 upper bound type paramter]로 넘긴다는 가이드라인 있다.
// -> 메서드 파라미터의 [개별 원소가 메서드 내부에 사용되는 컬렉션의 type parameter] List<T> -> List<? extends T>
// private static <T extends Comparable<T>> T max(final List<T> list) {
//3-2. 반대로, Comparable<T>에 걸린 T는 메서드내부에서 정해져 사용되지 않고, 메서드 밖에서 사용되는 Type paramter라서
// -> lower bound로 정의해준다.
// private static <T extends Comparable<T>> T max(final List<? extends T> list) {
private static <T extends Comparable<? super T>> T max(final List<? extends T> list) {
return list.stream()
.reduce((a, b) -> a.compareTo(b) > 0 ? a : b)
.get();
}
//2. 원래는 Integer부터 만들어야 정상이지만, 제네릭을 계속 썼다보니 제네릭 메소드로 만들어보자.
//private static <T> void max(final List<T> list) {
//2-2. 제네릭 메서드의 type parameter에서 upper bound는 파라미터 자리가 아닌 [응답형 앞]에서 준다.
private static <T extends Comparable<T>> T max2(final List<T> list) {
return list.stream()
//2-1. max를 loop를 구할 땐, 가장 작은값을 후보 변수에 세팅 -> 계속 업데이트하면서 구하는데, stream으로는?
// -> reduce의 2번째 방식(초기값 identity 따로 안주고 첫+둘 -> 합+셋 -> 순으로 연산 하기위해)으로 작성해보자.
// -> 연산후 연산결과의 Type이 현재 원소들과 같은 Type으로 되돌려줘야한다.
// -> 누적해가면서 <2변수를 비교후 큰값을 남기기>가 max값이다.
// --> Integer였다면 a > b ? a : b 의 3항연산자를 써서 (비교후 t/f별 return) 했을 것이다.
//.reduce((a,b) -> a > b ? a : b)
// -> 하지만, 제네릭을 통해 아무Type T -> [부등호를 통한 숫자비교]이외의 비교를 위해서
// --> Comparable 구현으로 제한한 Type에 대해 compareTo로 비교한다.
.reduce((a, b) -> a.compareTo(b) > 0 ? a : b)
//2-3.stream에 원소가 없을 경우 optional로 반환하는데, get()을 통해 값만 강제로 꺼낼 수 도 있다.
// -> 값이 없는데 get을 시도했을 때 예외가 발생하는데, 이것은 자연스런 예외라고 생각할 수 있다.
// NoSuchElementException: No value present
// default값이나 or 특정예외를 발생시키는 코드를 작성안해도, 없는 값 get시도시 예외는 자연스러운 것으로 봐도 된다.
.get();
}
}
와일드카드 사용시 캡처 문제
-
reverse
public class GenericsCapture { public static void main(final String[] args) { final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); //1. 이번엔 어느타입의 list든 revese시키는 함수를 만들어보자. // -> 제일 쉽게 구현하는 것은 list를 1개 temp에 방어적 복사(새로운 list로 복제)해놓고, // -> i번째에, temp에서는 n-i-1번재로 뒤에서부터 꺼내서, 원본 list값을 바꿔버리는(set) 전략이다. reverse(list); System.out.println("list = " + list); } private static <T> void reverse(final List<T> list) { //2. reverse전략: temp list 1개를 방어적 복사 -> index를 돌면서 뒤에서부터 temp에서 꺼낸 값을 -> 원본 list의 값에 set final List<T> temp = new ArrayList<>(list); for (int i = 0; i < list.size(); i++) { list.set(i, temp.get(list.size() - i - 1)); } } }
-
List
는 사용됬지만, 개별원소 T t의 type관련 기능이 사용되지 않았다. - T type정보가 내부에서 의미있게 사용되는가?
- 생성자에 list집어넣기 / size / get in List/ set in List -> 전부 List자체 기능
-
type paramter대신 와일드카드 사용가능하다
- 응답형 앞에 T 삭제
- type parameter
List<T>
를List<?>
대체하기
-
와일드 카드로 바꿔줬더니,
capture
에러가 뜬다.
- T type정보가 내부에서 의미있게 사용되는가?
-
와일드카드로 Type을 모르고 관심없다고 했는데
그 ?의 Type을 추론해야하는 상황
= 캡처의 상황이 찾아오기도 한다- 캡처=와일드카드의 타입추론
- listd에 .set을 할려고보니, temp에서 꺼내온 Type을 어떤 형으로 정해서 넣어줘야하지? 모르는 상황
다시 지네릭메서드로 돌아가도 되지만, 자바 설계상 헬퍼메서드를 만들어준다
-
reverse()메소드 내부에
private revserseHelper( list )
메소드를 만들고, 지네릭메소드로 돌린 코드를 helper메서드에 넣어준다.- 구조상 원본메서드는
List<?> list
의 와일드카드를 사용중이고 - 내부 helper메서드는 와일드카드 list를 던졌는데 -> 파라미터는
List<T> list
의 지네릭 메서드를 사용하게 된다.
- 구조상 원본메서드는
-
List<?> list
->List<T> list
메서드에 대입이 가능한가?- SubType 인자만 -> SuperType파라미터가 받을 수 있을 텐데?
- 일치하거나 List
가 ?의 SuperType(추상체)여야 인자로 던지기 가능함
- 일치하거나 List
-
사실 그런관계는 없다. 그러나 컴파일러가 자동으로 ? -> T 대입시 T로 캡쳐를 해준다고 한다.
- why 이런구조를 쓸까? 그냥 지네릭 메서드를 쓰면 편할텐데
- T라는 type도 내부구현으로서 감출 수 있으면 감추기 위해
- why 이런구조를 쓸까? 그냥 지네릭 메서드를 쓰면 편할텐데
public class GenericsCapture { public static void main(final String[] args) { final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); reverse(list); System.out.println("list = " + list); } private static void reverse(final List<?> list) { reverseHelper(list); } private static <T> void reverseHelper(final List<T> list) { final List<T> temp = new ArrayList<>(list); for (int i = 0; i < list.size(); i++) { list.set(i, temp.get(list.size() - i - 1)); } } }
- SubType 인자만 -> SuperType파라미터가 받을 수 있을 텐데?
-
와일드카드로 변환했는데, 구체적인 Type정보가 필요한 코드를 만나서 캡처문제가 발생했구나 정도로 이해하고 있으면 된다.
public class GenericsCapture {
public static void main(final String[] args) {
final List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
//1. 이번엔 어느타입의 list든 revese시키는 함수를 만들어보자.
// -> 제일 쉽게 구현하는 것은 list를 1개 temp에 방어적 복사(새로운 list로 복제)해놓고,
// -> i번째에, temp에서는 n-i-1번재로 뒤에서부터 꺼내서, 원본 list값을 바꿔버리는(set) 전략이다.
reverse(list);
System.out.println("list = " + list);
}
//3. List<T>는 사용됬지만, 개별원소 T t의 type관련 기능이 사용되지 않았다.
// -> T type정보가 내부에서 의미있게 사용되는가?
// 생성자에 list집어넣기 / size / get in List/ set in List -> 전부 List자체 기능
// -> type paramter대신 와일드카드 사용가능하다
private static void reverse(final List<?> list) {
//2. reverse전략: temp list 1개를 방어적 복사 -> index를 돌면서 뒤에서부터 temp에서 꺼낸 값을 -> 원본 list의 값에 set
// final List<?> temp = new ArrayList<>(list);
//
// for (int i = 0; i < list.size(); i++) {
// list.set(i, temp.get(list.size() - i - 1));
// }
//4. 지네릭 ->와일드카드로 변환시 캡처 문제가 발생할 경우, private helper메서드를 만들고
// 그 내부를 다시 지네릭메서드로 돌린다.
reverseHelper(list);
}
//5. List<?> list -> final List<T> list 메서드에 대입이 가능한가?
// -> SubType 인자만 -> SuperType파라미터가 받을 수 있을 텐데?
// (일치하거나 List<T> 가 ?의 SuperType(추상체)여야 인자로 던지기 가능함)
// --> 사실 그런관계는 없다. 그러나 컴파일러가 자동으로 ? -> T 대입시 T로 캡쳐를 해준다고 한다.
private static <T> void reverseHelper(final List<T> list) {
final List<T> temp = new ArrayList<>(list);
for (int i = 0; i < list.size(); i++) {
list.set(i, temp.get(list.size() - i - 1));
}
}
}
Lambda
-
람다식을 인자로 받는 메서드를 만들고
-
인자의 빨간줄은
파라미터를 뭘로 바꾸면 람다식 인자가 사라질까?
-
IDE가 아직 람다식을
java.util.Function
으로는파라미터 자동생성
을 안해준다.
-
-
해석해보기
-
Function
은람다식 구현을 하나의 구현체 인자
로 받는변수/파라미터 속 추상체이자 인터페이스
이다. -
내부를 보면, 메서드가 1개만 있는 함수형인터페이스다
-
람다식으로 구현 가능
== 인터페이스에 구현해야할 함수가 1개만 있어서 그자리에서 구현 == 함수형 인터페이스
-
-
**그렇다면
람다식
구현
자체가메서드 1개인 인터페이스
의구현체
가 되어, 인터페이스(추상체)를 파라미터로 받는 메서드의 인자로서 구현체로서 던져진다. ** -
함수형인터페이스는
지네릭 인터페이스
로서,인자/응답의 type paramter를 지정한 메서드 파라미터
로 지정할 수 있다.private static void hello(final Function<String, String> o) { }
-
람다식 구현 인자를 받는 함수형인페
에 제네릭 type parameter여부는parameter hint
로 확인하면서 람다식을 구현하면 된다.
-
람다식 구현 이전, 익명클래스로 구현
//4. 사실 람다식을 -> 익명클래스라는 [인터페이스를 객체처럼 생성후 -> 중괄호로 그자리에서 인터페이스속 1개 메서드를 Override구현]한 뒤, 변수 추출하여, 익클로 구현 변수를 함수형 인터페이스 인자 자리에 넣어줄 수 있다.
//new Function<String, String>(){}
// -> generate -> impl method
// new Function<String, String>(){
// @Override
// //4-1. 그자리에서 구현했지만, 들어오는 인자는 s
// public String apply(final String s) {
// //4-2. 그자리에서 구현했지만, 응답값은 s
// return s;
// }
// };
// 4-3. 인터페이스를 객체처럼 -> {}중괄호 그자리 구현 했다면 -> 그것을 사용하기 위해선 -> 익클은 변수 추출로 빼야한다.
final Function<String, String> function = new Function<>() {
@Override
public String apply(final String s) {
return s;
}
};
// 4-4. 이제 익클로 구현한 function를 람다식이 있던 자리 == 함수형인터페이스를 파라미터로 받는 메서드(인자) 자리에 넣어줄 수 있다.
// -> 그러면 내부에서 apply가 자동으로 호출된다.?!
//hello(function);
// 4-5. 하지만, 메소드1개인 함수형 인터페이스의 구상체 구현은 익클구현보다는, 람다식으로 해당 메서드를 구현하면 된다.
hello(s -> s);
-
익명 클래스 구현 -> 람다식 구현으로 옮겨오는 과정
//5. 메소드1개 인터페이스의 익명클래 구현에서 // 1) 익명클래스 관련 코드 삭제(new ~ @Override) // 2) 응답형은 알아서 추론<, R> // 3) 메서드명 필요없어 삭제 // 하면 아래와 같다 hello( (String s ) ->{ return s; } ); // 4) 지네릭을 쓰는 함수형인페에서 이미 인자의 type을 정해놨을 것이므로 // -> 인자 type도 추론 가능해서 삭제 // 5) 응답쪽 statement가 1줄인 경우, 중괄호 + return + 세미콜론 생략 hello( s -> s);
- cf) type추론은 compile time에 이루어진다.
IntersectionType 기본
-
람다식 구현
인자를 받는함수형인페 파라미터
의 메서드를 쓰는 데,마커 인터페이스를 붙여야할 경우
가 생긴다면-
해당 메서드의 파라미터를
Function t
->T t
를 받는 제네릭메서드로 바꾸고 - upper bound를
<T extends Function>
으로 준다. -
이제type parameter에
<T extends 함수형인페 & 마커인터페이스>
형식으로 intersectionType type parameter를 사용해서상한시 요구되는 타입을 여러 interface를 섞어 제한
하면 된다.
-
public class GenericsIntersectionType {
public static void main(final String[] args) {
//1. [람다식 구현]을 인자로 받는 [메서드]를 만들자.
// -> why? 람다식 구현을 받는 추상체 파라미터는 [메소드 1개의 함수형인페 구현을 받아주는 중 && 인자->응답T를 지네릭으로 정의]해놓았기 때문이다.
// cf) 람다식으로 구현해도, 익명클래스는 아니지만, 비슷한 클래스가 내부에 정의되서 익클처럼 작동한다...
hello(s -> s);
//3. 람다식은 앞에 (소괄호를 통한 타입 캐스팅)이 가능하다고 했다.
// -> type없이 캐스팅을 해줘도 된다. 불필요 하지만..
hello((Function) s -> s);
//4. 람다식의 캐스팅은 사실 intersectionType을 위해 사용된다.
// -> 메서드 1개는 이미 함수형인페것을 구현하고 있는 람다식이지만
// --> 메서드가 내부에 없는 marker interface를 &를 통해 추가 구현한다는 개념이다.
// 대표적인 marker interface를 [Serializable]로 생각하자.
// impl Comparable처럼, impl Serializable을 하면 -> [파일/네트워크에서 직렬화 가능한 class임을 마킹]하는 인터페이스다.
// impl로 구현즉시, instanceof로 확인해보면, Serializable Type이 되어버린다.
// -> Type만 가지고도 전송가능한지 확인할 수 있어서 좋다.
// 5. 람다식 구현 유지조건은, intersectionType을 사용하더라도, 총 구현 메서드가 1개기만 하면 된다.
// 1 & 0 & 1 -> 람다식구현에서 에러가 난다. 람다식은 메서드 1개만 구현
//hello((Function & Serializable & Closeable) s -> s);
// 1 & 0
// -> 내부적으로 class를 정의하는데, 3가지 인터페이스를 모두 impl한 class를 정의함.
hello((Function & Serializable) s -> s);
}
//2. 이번엔 함수형인페속 type paramter를 지금 정의하지 않았고 -> raw type으로 지네릭 인터페이스를 사용한다.
// -> 이 경우, <T, R> 자리에는 <Object, Object>가 들어가 있다.
private static void hello(final Function o) {
throw new UnsupportedOperationException("GenericsIntersectionType#hello not write.");
}
//6. [람다식구현을 받는 함수형인페 Type]의 메서드 파라미터(final Function o)에다가
// -> bounded type parameter으로 명시되는 제네릭 메서드(T o)로 변환할 수 있다.
// -> 원래는 지네릭메서드를 고려할 땐, 어느타입이든 받을 수 있게 T로 놓았지만
// -> 여기서는 [직렬화가능한 & 람다식 구현]을 인자로 넘기는 경우가 있으며, 그 때 제한을 걸어줄 때
// --> 여기선 최소 해당조건을 구현하거나 그 자식 구현체로 제한하는 Type을 type parameter로 넣어준다.
// ---> 이렇게 할 경우 class Myclass implements Function, Serializeable의 여러 인터페이 구현한 클래스의 객체를 생성할 필요가 없어진다.
//-> 요약) 마커인터페이스를 붙일 필요가 있는데, 람다식으로 간결하고 표현하고 싶다 -> 지네릭 메서드를 intersectionalType을 써서 정의해라
private static <T extends Function & Serializable> void hello2(final T o) {
throw new UnsupportedOperationException("GenericsIntersectionType#hello not write.");
}
}
- 그러나 요즘 직렬화가능한 것을 넘겨주는 것은 잘안한다고 한다.
- 새로운 class를 만들지 않고도,
IntersectionType을 통한 인터페이스 조합
을 통해새로운 class = 새로운 익명의 Type
을 만들어내는 효과를 만들어낼 수 있다.
IntersectionType으로 marker interface의 default method을 합친 익명 객체 만들어보기(제한점 존재)
- 기존 함수형인페의 기능(apply) + 마커인페의 default method들의 기능을 모두 합쳐서 가지고 있는 새로운 객체가 생성된다.
- 문제점: 람다식구현 인자 던질 때
캐스팅
해준 만큼 -> 제네릭 메서드의상한에도 또 명시
- 문제점: 람다식구현 인자 던질 때
public class GenericsIntersectionType2 {
//1. 새로운 default메서드만 가지는 인터페이스를 하나 생성한다.
interface Hello {
//2. 인터페이스안에는 default 메서드를 1개 만들어 -> 부연코드를 넣을 수 있다(아니면 다 공통 추상메서드만 있어야함)
// -> 제한이 있긴하다 (상속x/ public만/ 필드정의 후 사용x)
// -> 가능한 것 : 인터페이스 내 추상메서드 / static메서드 호출 가능
default void hello() {
System.out.println("Hello");
}
}
//2. 또 default메서드만 가지는 인터페이스를 하나 생성
interface Hi {
default void hi() {
System.out.println("Hi");
}
}
public static void main(final String[] args) {
//3. 람다식 구현을 인자로받는 [함수형인터페이스]를 파라미터로 받는 메서드를 정의한다.
//hello( s -> s);
//4. 람다식 구현은, 해당 추상체인 [함수형인페]로 캐스팅할 수 있다
// hello((Function) s -> s);
//5. 람다식 구현시 사용하는 캐스팅에 Intersection Type으로서 &로 여러 market 인터페이스를 구현할 수 있다.
// -> 람다식 캐스팅은 총 method개수가 1개여야하지만, default method는 0개로 친다!
hello((Function & Hello & Hi) s -> s);
}
//6. Function 및 그 구상체(람다식구현)만 받았었지만
// -> 3개 인터페이스 다 구현한 이후만 받도록 만들기 위해서
//private static void hello(final Function function) {
// 6-1. 제네릭 메서드로 변환한다. Function function -> T function
// 6-2. T에 상한을 Function으로 둔다
//private static <T extends Function> void hello(final T function) {
// 6-3. T에 상한 인터페이스에다가 & Intersection로 마커인터페이스들을 추가한다.
private static <T extends Function & Hello & Hi> void hello(final T function) {
//6-4. T function는 <Function & Hello & Hi> Type으로 제한된 상태이므로
// -> Function이 가지고 있는 apply를 적용할 수 있다.
function.apply("s");
// 7. 신기한 점은 market interface내에 있는 default method도 호출이 가능하다는 점이다.
// -> 함수형 인터페이스의 기능(apply) + 마커인터페이스의 default method들의 기능을 모두 합침
function.hello();
function.hi();
}
//8. 제네릭메서드의 상한과 insectionType + 인터페이스의 defaul method를 활용하면
// -> 기능 자체를 계속 추가해줄 수 있는데,
// [문제]는 [1) 람다식구현시 캐스팅 일일히 해놓고] -> 또 호출을 받는 [2) 제네릭 메서드의 upper bound에도 일일히 명시]해줘야하기 때문에
// -> 의미가 없다.
}
Consumer파라미터 추가를 통한, 콜백형식으로 IntersectionType에 기능 하나씩 확장하기(제한점 존재)
- 각각 따로 기능을 구현한 interface의 method기능들을 모았어도 -> 공통정보를 활용하는 방법이 없으니, 모아도 별로 의미가 없어 보이는 제한점…
public class GenericsIntersectionType3 {
interface Hello {
default void hello() {
System.out.println("Hello");
}
}
interface Hi {
default void hi() {
System.out.println("Hi");
}
}
public static void main(final String[] args) {
//1. run이라는 메서드를 만들어 (intersectionType)으로 캐스팅된 람다식구현을 인자로 받는다.
//run((Function & Hello & Hi) s -> s);
//5. 컨슈머T로 넘어갈 컨슈머용 람다식(인자O -> return값 없는 사용만 우항에 몰빵)
run((Function & Hello & Hi) s -> s, o -> {
o.hello();
o.hi();
});
//6. 누락된 개념:
// 6-1. 제네릭메서드의 상한으로 3개 인페 정의부분을 -> 첫번째 파라미터의 람다식 캐스팅에 의존한다 (컨슈머T는 첫번째 상한제한T를 그대로 이용하기 때문에 다 미리 타입이 추론되어있다)
// 6-2. 제네릭메서드 내부에서 호출된 로직을, 바깥의 ConsumerT 구현 람다식으로 뺐다.-> 2번째 인자의 람다식구현 우항(lambda object)
// - 요곤 6-1.을 이용하기 위함함
//7. s->s는 의미가 없다. Function의 람다식구현을 가장 짧게 나타냈을 뿐이다.
// -> 주요기능은 intersectionType으로 추가되는 marker 인터페이스의 <default method>기능이다.
//9. 새로운 마커인터페이스+그 default method기능을 추가시켜서 사용해보자.
// 9-1. 람다식 캐스팅에 추가한다.
run((Function & Hello & Hi & Printer) s -> s, o -> {
o.hello();
o.hi();
// 9-2. 새로운 default method 기능 추가 사용 in Consumer
o.print("sadsf");
});
//10. 각자 따로 노는 기능들을 그냥 단순 조합만 하면 무슨 의미가 있을까?
// 그냥 static메서드 모아놓은 것과 같은 효과일뿐이다?
// -> 1개의 Type안에 method가 여러개다? -> 공유하고 있는 필드정보가 있어야 의미가 있는 것이다.
// --> delegate를 활용하여 각각 따로 기능을 구현한 interface가 공통정보를 활용하는 방법이 있다.?!
}
//8. 이제 marker인터페이스 및 default method를 추가하여 새로운 기능을 추가한 Type으로 확장해보자.
interface Printer {
default void print(String str) {
System.out.println(str);
}
}
// cf) 일반 람다식은 파라미터를 함수형인페로 안잡아주는데, 캐스팅하니까 바로 잡아준다.
// 2. 일단은 함수형인페 이하로 upper bound를 준 제네릭 메서드로 변경한다.
//private static void run(final Function function) {
//3. 이번엔 upperbound에 intersection을 거는 것이 아니라
// -> [함수형인페 Consumer<T>]를 메소드 파라미터로 추가한다.
// --> 컨슈머T(void accept(T t);)는 accept기능을 가지는데, return은 없고 파라미터만 있다
// ---> 반대로 Supplier<T>는 get기능을 가지는데, 파라미터는 없고, return만 T type이다.
// [컨슈머T를 썼다 -> 뭔가 Lambda object(람다식 구현의 우항)를 갖다주면, << 그걸 파라미터로 받는 메소드 내부에서 응답없이 .accept( 인자 )로 넘어온 lambda object(우항)을 사용하기만 할 수 있다>> 는 뜻이다]
//private static <T extends Function> void run(final T t) {
private static <T extends Function> void run(final T t,
final Consumer<T> consumer) {
// 4. 그렇다면, run내부에서 호출되었던 t.hello();, t.hi();들을
// -> consumer 인자부분에서 lambda object(우항)에 담아서 던져주고
// consumer.accept(t)로 lambda object(컨슈머 람다식 우항)를 return없이 실행만 시킨다.
consumer.accept(t);
}
}
함수형인페<T>
를 상속한 인터페이스로, default method내부에서 변경가능한 람다식
에 따른 기능 수행하는 메서드가 된다.
람다식구현 인자용 public class GenericsIntersectionTypeWithDelegate {
//1. 지네릭으로서 type 정보 1개를 가진 interface 선언
interface DelegateTo<T> {
//2. supplier처럼 인자안받고, 응답만 T type의 객체로 하는, 추상메서드 1개를 선언한다.
T delegate();
}
//3. 이제 delegateTo 지네릭 인터페이스를 extends로 상속하는 인터페이스를 만든다.
// -> 이 때, 지네릭 인터페이스의 type parameter에 구체형을 줘야한다!
// -> 또한 확장기능을 추가하기 위해 default method를 추가한다. (총합 1개메서드의 람다식에 카운팅 안되는 메서드)
interface Hello extends DelegateTo<String> {
default void hello() {
//4. 이제 확장기능 뿐만 아니라 + default메서드 속에서 상속한 (함수형)인터페이스의 기능(delegate())를 호출할 수 잇다.
// -> 함수형인페 추상메서드는 인자없이 T응답인데 -> DelegateTo<String>으로 박아서 상속했으니 String을 응답할 것이다.
System.out.println("Hello " + delegate());
}
}
public static void main(final String[] args) {
//5. 아까랑 조금 다르다.
// -> 바깥에 호출하는 메서드 + 일단 람다식 캐스팅부터 작성한다.
// -> 상속한 마지막 인터페이스가 뿐만 아니라, 부모인 함수형인페도 같이 처음부터 적어줘야
// -> 함수형 인페로 인한 람다식 작성이 가능하다.
//run((DelegateTo<String> & Hello))
//6. 이번엔 [함수형인페 속 메서드가 인자X의 supplier -> 람다식 구현시 좌항이 빈 ()->로 출발해야한다]
//run((DelegateTo<String> & Hello) () -> )
//7. 인자없는 ()-> 지만, 응답은 T 타입 -> String 이므로 [우항=lambda object]는 String으로 반환해주자.
//run((DelegateTo<String> & Hello) () -> "ChoJaeSeong")
//8. 이제 기본적으로 제네릭메서드로서 상한 2개 && 내부에서 기능호출하는 메서드에서
// 내부호출을 2번재 인자에 [return없이 메서드 호출만 하는 로직 옮기기용 Consumer<T>의 람다식] 작성
// -> 이것을 통해, 상한을 일일히 제네릭 상한으로 안적어주고, Consumer<T>가 1번째 람다식에서 추론해서 갖다쓰게 된다.
run((DelegateTo<String> & Hello) () -> "ChoJaeSeong", o -> {
});
//12. 해석
// 1) 람다식 구현() -> "ChoJaeSeong"는, 함수형인터페이스DelegateTo<String> 의 추메 T delegate()의 실제 구현 코드가 되어버린다.
// 2) Consumer가 받아온 로직 중 default 메소드 hello 호출 -> 그 내부에 함수형인페 속 추메 delegate() 호출이 있다면?
// -> 여기 정해둔 람다식구현이 빨려 들어가 실행된다.
// 3) 그 이유는, 타입캐스팅 속 InterSectionType & 로 인해, 뒤쪽 인페의 default method안에 들어있는 부모인터페이스의 메서드 호출시, 앞에 함수형인페의 추상메서드를 그대로 가져와 호출한다.(자바가 인식해준다)
run((DelegateTo<String> & Hello) () -> "ChoJaeSeong", o -> {
// 13. 앞에서 intersectionType으로 추가해준 인터페이스는
// 그 앞의 deletage를 상속한 뒤, 그 속의 추메를 내부에서 사용할 수 있게 된다.
// -> default 고정 기능이 아닌, 함수형인페를 부모로 상속한 뒤, default method에서 사용 ->
// 호출시 람다식에 의해 언제든지 변경가능한 기능을 수행하게 됨됨
o.hello();
});
//14. 람다식 캐스팅 부분에 인터섹션Type을 추가한 뒤,
// consumer block에서 새로운 기능을 호출하면, 람다식구현이 그대로 추상메서드 호출 자리에 사용되면서 기능이 추가된다
run((DelegateTo<String> & Hello & UpperCase) () -> "ChoJaeSeong", o -> {
o.upperCase();
});
//15. 이제 람다식을 동적으로 바꿔도 바뀐 체로 기능이 합쳐진다.
run((DelegateTo<String> & Hello & UpperCase) () -> "abcde", o -> {
o.upperCase();
});
}
//13. 이제 Consumer block안에 새로운 기능을 추가하고 싶다! 하지만, 새로운 클래스를 만들거나 하고 싶진 않다면?
// -> 람다식 캐스팅에 인터섹션Type을 추가할 default method의 interface추가 -> [람다식으로 구현하는 delegate()기능 + @]로 기능 추가할 수 있다.
//13-1. 똑같이 함수형인페는 상속해서 -> 내부에서 함수형인페를 호출할 수 있어야한다.
interface UpperCase extends DelegateTo<String> {
default void upperCase() {
System.out.println(delegate().toUpperCase());
}
}
//9. 기본 제네릭 메서드로 정의하며, 람다식의 추상체 파라미터인 인터페이스(DelegateTo<String>) 대신 -> T t로 쓴다.
// -> 여기서 말하는 T는 람다식캐스팅 맨 앞의 인터페이스를 T로 본다.
// -> 이제 인터페이스를 구현한 것 이후로의 SubType만 받기 위해, T의 상한을 응답형 앞에서 받아준다.
// --> 여기서, T는 DelegateTo<>인데, DelegateTo 자체의 type paramter는 다른 것으로 표기해줘야한다.
// run은 2개의 type paramter <T, S> 2개를 가지게 된다.
// -> Consumer는 delegate에 해당하는 T를 받아와서 람다식에서 호출되도록 한다.
// 10. 아까는 Function이라는 함수형인페를 바로 사용했지만,
// -> 지금은, DelegateTo라는 자체제작 (메소드1개의 인터페이스->) 함수형인페 + type이 중요해서 Function에서 안쓰던 제네릭을 붙여서 type을 살린 상태로
// 좀더 복잡해졌다.
private static <T extends DelegateTo<S>, S> void run(final T t, final Consumer<T> consumer) {
//11. Consumer의 람다식으로 뺀 로직을, 실행context는 여기 메서드 내부에서 시켜주게 한다.
// -> my) 람다식구현 인자로 받은 로직 -> 함수형인페로 파라미터로 받아서 -> 로직.실행()의 context는 메서드 내부에서 실행되게 한다. 함수실행로직 작성만 밖에서
// -> my2) 구현체 객체 -> 인터페이스 파라미터 -> 구현체 객체.의 기능 실행()은 메서드 내부에서...
consumer.accept(t);
}
}
새로운 아이디어와 최종 예제
기존: (구현class 후 구현체 인자 대신) 람다식 구현 인자 -> 함수형인페 파라미터 (구현체.추클()호출 대신 정해진메서드()호출하여 내부context로 실행) -> 제네릭 메서드로 변환하되 함수형인페를 T상한으로 -> 여러Type 동시구현을 상한경계 대신 람다식 캐스팅 + Consumer로 밖으로 빼기
동적으로 추가
하고 싶다
현재: 일반 인터페이스(메서드 2개이상) 의 기능 다 가지면서 + 새로운 기능을 람다식 캐스팅 했던 것처럼 더이상 수정할 수 없는 라이브러리에 기능추가
-
Repository의 findAll()이 List가 아닌 iterator를 리턴하는데, list로 받으려고 별도의 메서드 만드는 것이 관련있을까? 아님…
-
예제 인터페이스는 이미 4개의 메서드를 가진 인터페이스가 base라서… 람다식 구현(함수형인페-메소드1개를 인자에서 구현)을 받을 수 가 없다.
- 람다식의 우항에서 해당 구현체객체를 넘겨주는 람다식을 작성하더라도.. 캐스팅은 안된다?!
public class GenericsIntersectionTypeWithDelegateExample {
//1. List는 구현해야할 추메가 너무 많아서, Pair<T>라는 인터페이스를 하나 정의해준다.
interface Pair<T> {
// 2. 값을 2개 가질 수 있어서 각각을 꺼낼 수 있는 메서드 2개를 가진다( 함수형인페X -> 람다식으로 인자에서 구현X)
// + setter도 2개 가진다.
T getFirst();
T getSecond();
void setFirst(T first);
void setSecond(T second);
}
//2. 이제 일반 인터페이스를 구현하는 class를 만든다.
// -> 인터페이스 impl할 때, 제네릭 인터페이스면 -> 직접 명시해서 구현하자.
static class Name implements Pair<String> {
//3. class라서 이제 필드를 만들어, 정보를 보관할 수 있다.
private String firstName;
private String lastName;
public Name(final String firstName, final String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String getFirst() {
return firstName;
}
@Override
public String getSecond() {
return lastName;
}
@Override
public void setFirst(final String first) {
this.firstName = first;
}
@Override
public void setSecond(final String second) {
this.lastName = second;
}
}
interface DelegateTo<T> {
T delegate();
}
private static <T extends DelegateTo<S>, S> void run(final T t, final Consumer<T> consumer) {
consumer.accept(t);
}
//7. Forwarding인터페이스를 만들어준다. (~를 넘긴다는 의미)
//7-1. 람다식으로 쓰기 위해 함수형인페 Delegate 인터페이스를 상속한다.
// - 동적으로 쓰고 싶은 인터페이스 Pair<T>를 함수형인페<T>의 type paramter에 넣어준다.
// - Delegate뿐만 아니라, Pair 인터페이스도 상속한다.
interface ForwardingPair<T> extends DelegateTo<Pair<T>>, Pair<T> {
//7-2. 인터페이스에 있는 여러개의 추상메서드를 -> 상속후 default method로서 오버라이딩 하는 방법이 있다.
// (1) 오버라이딩 해서 다 가져온 다음 -> (2) @Override 지우고, default메서드로 바꾸주고
// (3) getter의 경우, DelegateTo 상속으로 호출가능한 delegate()를 호출한다.
// -> delegate는 들어오는 T를 응답해주는 supplier 역할인데, 그 T자리에 Pair<T>가 들어가 있으니
// -> pair내부의 여러 추상메서드를 호출하도록 작성한다.
// @Override
// T getFirst();
default T getFirst() {
//8. delegate()하면, elegateTo에 적힌 Type인 pair<T> 타입이 오고 -> 거기서 정의된 메서드를 호출한다.
// -> supplier형태의 T응답 DelegateTo<T> 인페를 상속한 인페는
// -> DelegateTo<T>의 T 자리에 넣은, extends DelegateTo<Pair<T>> 상의 [Pair<T>]를 반환해주게 된다.
// --> delegate()로 반환된 인터페이스 Pair<T>는.. 그 내부 추상메서드를 호출할 수 있다.
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);
}
}
//9. ForwardingPair를 만들면, 인터페이스는 Delegate만 남는다.
// -> 그 뒤의 Pair는 이미 안에서 구현된 상태기 때문이다.
public static void main(final String[] args) {
//4. 생각하면서 람다식구현을 인자로 받는 메서드 -> 그것을 제네릭으로 받는 메서드를 run을 호출해보자.
//run(()->"", o->{});
//5. 우리가 원하는 것: 일반인페 기능구현 + 동적으로 기능추가 하고 싶다.
// 5-1. 메서드 2개이상의 일반 인터페이스 의 기능을 다 가진다 -> 람다식구현 대입 불가 -> 구현체객체를 만들어, [넣어줘야할 구현체 객체를 람다식 우항에서 제공]해주기
// - 참고) 추상체 인터페이스 파라미터에 넣을, 구현체 객체를 생성할 때도, 변수(좌항)은 추상체로 받아주자.!
//final Name name = new Name("Toby", "Lee");
final Pair<String> name = new Name("Toby", "Lee");
// 5-2. 람다식으로 함수형인페 1개 전체 구현이 아니라, 일반 인페가 받는 구현체 객체를 람다식으러 넣어주면 된다.
//run(() () -> name, );
//6. 하지만, 람다식의 우항으로 넘기는 name은 Pair라는 인터페이스를 넘길 수 없는 상황?이다.
// -> 람다에서 사용하게 만들고 싶다~! 새로운 아이디어가 필요하다.
//10. 이제 람다식 캐스팅을 ForwardingPair라고 놓는다.
// -> FP는 DelegateTo를 상속한 상태이므로
// -> DelegateTo를 상한경계로 받는 run의 첫번재 인자 T에 넣어줄 수 있고 ( 람다식의 우항에 supplier -> T t )
// -> 메서드 내부호출될 로직을, 함수형인페로 받아 -> 외부에서 [첫번째 인자에 의해 자동 추론된 Type] 람다식으로 제공 받을 수 있어
// -> 2번째 인자 람다식을 동적으로 넣어줄 수 있다.
run((ForwardingPair<String>) () -> name, o -> {
System.out.println("o.getFirst() = " + o.getFirst());
System.out.println("o.getSecond() = " + o.getSecond());
});
//11. 컨슈머 쪽의 o의 Type은 사실 ForwardingPair이다.
// -> ForwardingPair이다는 delegateTo에 해당하는 것이고
// -> delegateTo를 구현하는 메서드는 () -> name 밖이다.
// -> 그 우항에서 반환되어 입력되는 name속 메서드는
// -> ForwardingPair가 delete상속후 다 정의를 해놓았으니
// -> o에서 호출해서 쓰기만 하면 된다?!
//람다식 타입캐스팅 쓸 수 있는 인터페이스 -> default method는 0개로 침. 함수형 인터페이스..?!
//https://www.youtube.com/watch?v=PQ58n0hk7DI&list=PLv-xDnFD-nnmof-yoZQN8Fs2kVljIuFyC&index=11
//1:43:00 c참고하기
}
}
제네릭 기본 적용 예시 모음
-
print
//before void print(String value) { System.out.println(value); } //after <T> void print(T t) { System.out.println(t.toString()); }
-
countGreaterThan(arr, elem)
//before static long countGreaterThan(Integer[] arr, Integer elem) { return Arrays.stream(arr) .filter(s -> s > elem) .count(); } public static void main(final String[] args) { final Integer[] integers = {1, 2, 3, 4, 5, 6, 7}; System.out.println("countGreaterThan(integers, 4) = " + countGreaterThan(integers, 4)); }
//after static <T extends Comparable<T>> long genericCountGreaterThan(T[] arr, T elem) { return Arrays.stream(arr) .filter(s -> s.compareTo(elem) > 0) .count(); } public static void main(final String[] args) { final Integer[] integers = {1, 2, 3, 4, 5, 6, 7}; System.out.println("genericCountGreaterThan(integers, 4) = " + genericCountGreaterThan(integers, 4)); final String[] strings = {"1", "2", "3", "4", "5", "6", "7"}; System.out.println("genericCountGreaterThan(strings, \"3\") = " + genericCountGreaterThan(strings, "2")); }
-
isEmpty(list) - 지네릭 메소드 버전
private static <T> boolean isEmpty(final List<T> list) { return list.size() == 0; }
- isEmpty - 내부 로직에 type paramter 미사용으로 unbound wild card로 변경한 버전
private static boolean isEmpty(final List<?> list) { return list.size() == 0; }
-
frequency(list, elem) - 지네릭 메소드 버전
private static <T> long frequency(final List<T> list, final T elem) { return list.stream() .filter(s -> s.equals(elem)) .count(); }
- frequency - 와일드 카드 버전
- **equals비교가 제대로 되려면 List
의 원소 vs 원소1개 T를 비교하는 지네릭 메소드가 낫지 않을까?** - equlas의 비교는 사실, 똑같은 Type일 이유가 없다.
- equals비교 대상으로 허용하지 않는다면, 에러가 날 것이다.
- list의 원소Type와 비교할 원소는 다른 Type을 가져가는 경우도 있다. ex> SubType
- 동일한Type으로만 비교시, 지네릭 메서드를 쓰면 되겠지만… SubType(구상체)와의 비교도 하기도하고, 자바 api에도 이렇게 설계되어있다.
- **equals비교가 제대로 되려면 List
private static long frequency(final List<?> list, final Object elem) { return list.stream() .filter(s -> s.equals(elem)) .count(); }
- frequency - 와일드 카드 버전
-
max - 제네릭 메소드 버전
private static <T extends Comparable<T>> T max2(final List<T> list) { return list.stream() .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) .get(); }
- wild card 개입 by 메서드 내부 사용시 upper / 메서드 외부용 super
private static <T extends Comparable<? super T>> T max(final List<? extends T> list) { return list.stream() .reduce((a, b) -> a.compareTo(b) > 0 ? a : b) .get(); }
- max - collections(인자인 List내장 Comparator 사용 버전)
Collections.max(list)
- max - collections +
Comparable<? super T>
예시를 위해, 비교메서드 구성을 Object로 해버리는 직접 Comparator(객체 비교방법) 제시 with 람다 캐스팅
Collections.max(list, (Comparator<Object>) (a, b) -> a.toString().compareTo(b.toString()));
-
reverse: 지네릭 메서드로는 1개로 표현가능
private static void reverse(final List<?> list) { final List<?> temp = new ArrayList<>(list); for (int i = 0; i < list.size(); i++) { list.set(i, temp.get(list.size() - i - 1)); } }
private static void reverse(final List<?> list) { reverseHelper(list); } private static <T> void reverseHelper(final List<T> list) { final List<T> temp = new ArrayList<>(list); for (int i = 0; i < list.size(); i++) { list.set(i, temp.get(list.size() - i - 1)); } }