Programming/JAVA

자바의 정석 공부내용 요약본2. 공부 기간: 2021-01-15~2021-01-28

최동훈1 2021. 12. 14. 02:57

2021-01-15

람다식을 받을 수 있는 래퍼런스 타입은 무엇인가?

 

람다식은 익명 객체이다. , 익명 클래스가 선언과 동시에 생성한, 객체이다. 그러나 이 생성된 객체를 받아줄 마땅한 타입의 참조 변수가 존재 하지 않는다.->이유: Object(상위 조상의 래퍼런스)로 받는다면 Object 리모콘에는 내가 람다식으로 만들어낸 메서드가 존재 하지 않기 때문에, 컴파일 에러난다.(업케스팅때의 오류와 비슷,)

 

따라서 함수형 인터페이스라는 것을 통해, 람다식으로 만들어진 익명객체를 전달 받을 수 있는, 참조변수 타입을 자바에서 새로 만들었다.

 

다른 관점으로 설명 하자면, 애초에 익명 클래스를 생성 할 때, new 키워드 뒤에 붙이는 타입은 조상클래스의 이름 또는, 구현하고자 하는 인터페이스의 이름을 사용해서 정의한다. 그런데, 람다식은 메서드의 이름을 생략한다. , 인터페이스에 존재하는, 구현 해야할 추상 메서드가 2개 이상이면, 람다식으로 구현한 메서드가 어떤 것을 구현했는지, 추정이 불가능 하다. 그래서 오로지 단 1개의 추상 메서드만을 가진 함수형 인터페이스로 익명 객체의 일종인 람다식을 받을 수 있는, 래퍼런스가 제한된 것이다.

 

2. 람다식이 만들어낸 익명 객체의 주소를 받을 수 있는 래퍼런스는 함수형 인터페이스 뿐인데, 그 함수형 인터페이스가 되는 조건은, 추상메서드가 1개 뿐이고, @FunctionalInterface 애너테이션이 붙여져야 한다. , 대표적으로, 정렬할 때 쓰이는, Comparator 인터페이스가 그렇다.

 

2021-01-18

자바는 왜 함수형 인터페이스 들을 java.util.function 패키지에 몇 개 만들어 놓았나?

 

1.근본적인 이유: 유지보수를 쉽게 하기 위해.

함수의 반환값과 매개변수의 개수에 따른 분류는 4가지 경우의 수만 존재(매개변수 2개 이하일 때) 그러므로, 미리 표준화된 함수형 인터페이스를 만들어 놓아서, 굳이 @FunctinalInterface 라고 치면서 인터페이스를 만들지 않아도, 미리 만들어진 것을 가져다가 쓰는 것.-> 유지보수할때도, 일일이 고치지 않고 용이함.

 

2. 반환값과 매개변수의 개수에 따른 분류

1)Supplier<T> : 공급자. 말 그대로 공급만 하는 기능.->매개변수 개수 0 반환값 개수 1

2)Consumer<T> :소비자. 소비만 하는 기능. 먹기만 한다고 생각. 매개변수<T> 만 존재.

3)Function<T, T> :함수. 함수처럼 투입과 산출이 있음. 매개변수<T>, 반환값<T> 다 존재.

4)Predicate<T> :예측하다. 값을 예측하는 것->매개변수, 반환값 다 존재, 그런데 반환값이 boolean 타입임.

 

2021-01-19

메소드 참조.

메소드 참조는 람다식이 하나의 메소드를 호출하는 경우에 매개변수를 생략 해주고, ‘클래스이름::메소드 이름의 형식으로 간략하게 나타내도록 한 것이다.

주의 할 점은, 람다식이 하나의 메소드를 호출한 다는 것은, 함수형 인터페이스의 메소드가 1개라는 말이 아니다. 람다식은 함수형 인터페이스에 정의된 단 한 개의 메소드를 이름 없이 간략하게 표현 한 것이기 때문에, 그 간략하게 표현한 한 개의 메소드에 구현 된 메소드가 1개이면 메소드 참조를 할 수 있다는 뜻이다.

 

, (i) -> { int a=i/10;

                    return a*i;} 이런 람다식은 메소드 참조가 불가능 하다는 이야기이다.

 

2. 메소드 참조의 종류는 static 메소드를 참조하는 경우, 인스턴스 메서드를 참조하는 경우 등이 있다. 또한, 생성자도 메서드참조로 간략하게 나타낼 수 있다.

Function<Integer,MyClass > f= MyClass::new;   <-이건 매개변수 있는 생성자일 때.

 

3.람다식으로 메서드 참조 연습

1) @FunctionalInteface를 붙인, 함수형 인터페이스를 내가 만든다. 여기서 지네릭 타입을 쓸건지(매개변수로 주든가, 반환값이 있던가, 아니면 run()메소드처럼 void 하던가) 정하고 만듬.

@FunctionalInterface

interface finter<T>{

void play(T i);

}

여기선 내가 반환값은 없고 매개변수만 존재하는 메서드를 만듬.

그다음, 내가 만든 finter 인터페이스를 구현한 람다식을 만든다.

finter<String> f= (i)->System.out.println(i);

f.play("동훈아 넌 최고야.");//이줄 실행시 화면에 동훈아 넌 최고야출력됨

그런데, 이 람다식은, 메서드가 1개뿐이라서, 메서드 참조를 사용 할 수 있는 조건이 충족된다.

그래서 메서드 참조로 변경해서 실행하면,

finter<String> f= System.out::println;

f.play("동훈아 넌 최고야.");

 

같은 값이 나온다.

 

2021-01-20

 

람다식으로 Comparator 인터페이스를 구현하여 정렬의 기준을 잡을 때, Comparator의 추상 메서드인compare(Object o1,Object o2) 메소드를 구현 해야 하는데, Object 타입인 o1,o2를 정렬하고자 하는 클래스 타입으로 변환 하고 뺄샘을 반환 해야 되지 않나? 그렇다면, 람다식을 이용해서, 간단히 하여봤자, if(o1 instatnceof Student && o2 instanceof Student) return 같은 코드가 추가되어야 하니, 간단히 할 수 없지 않는가?

+comparing의 원리.

 

1.우선 정렬을 하려면, 스트림에 배열이나 리스트를 담아야 한다. 그 다음 데이터를 담은 스트림의 래퍼런스로 sort() 메소드를 호출하여서, 정렬을 하는 것이다. 그런데 이때, 컴파일러는 스트림의 래퍼런스를 선언할 때 지정해 준 지네릭 타입을 보고, 어떤 타입의 매개변수가 올 건지 알고 있다. 그러므로 바로 (s1,s2)->s1.compare.s2 이렇게 sort() 메소드 안의 comparator로 넣어도 되는 것이다. 위 식은, comparator을 구현 한 익명 객체를 간단히 한 것이다(람다식의 구성).

 

Stream<Student> s=Stream.of(new Student("최동훈",1,200),

new Student("최미미",2,200),

new Student("박권",3,100),

new Student("이석진",3,210),

new Student("조석래",2,100) );

s.sorted((s1,s2)->s1.ban-s2.ban).forEach(i->System.out.println(i));

 

이렇게 써도 문제없이 출력 된다. 왜냐하면 스트림 래퍼런스 s<Student>라는 지네릭 타입으로 인해서 컴파일러가 s1, s2Student 형이라는 걸 알고 받아들이기 때문이다.

 

2.Comparator 인터페이스 에는 우리가 그 전부터 정렬하기 위해서, 구현해왔던 추상 메서드인 compare(Object o1,Object o2) 말고 static 메서드가 더 있다. 그중, 가장 기본적으로 정렬에 사용되는 메서드는 comparing()이다. 이 메서드는 반환타입이 Comparator 이므로, 스트림 sort() 메소드 안에 넣어도, sort() 메소드 안 Comparator 인터페이스를 구현한 것과 같은 효과를 가진다. 작동 원리는 잘 모르지만, 비교 대상이 되는 요소를 반환 하게 구현 하면 된다. comparing(s->s.getban())

 

2021-01-21

스트림의 생성부터 중간연산까지 전반적인 흐름과정.

 

1.스트림은 스트림의 생성-중간연산-중간연산-...-최종연산 순으로 진행 된다.

스트림을 생성하는 방법은 스트림으로 만들려는 데이터의 종류에 따라 방법이 세분화되어 있다.

대표적으로 스트림으로 만들려는 데이터 소스가 컬랙션, 배열, 특정 범위의 정수, 임의의 수 등이 있다.

컬랙션의 최고 조상인 Collectionstream()이 정의되어 있어서 이 Collection 클래스를 구현한 List SetCollection.stream() 메소드로 각 컬랙션의 요소들로 스트림을 만들 수 있다.

배열은 2가지 방법이 있는데, Stream.of(T[]) 메소드에 매개변수로 배열을 주는 방법과, Arrays.stream(T[]) 으로 스트림을 만드는 법이 있다.

그런데 주의 할 점은, 기본형 스트림을 만들때는 IntStream.of(int[]) 메소드를 써야 한다는 것이다.

특정 범위의 정수를 스트림으로 만들려면, IntStream.range(int begin, int end) 메소드를 사용하면 된다. 그럼 begin부터 end까지(end 미포함)의 정수 데이터가 스트림속 데이터로 들어간다.

2.중간연산은 횟수에 제약을 받지 않고, 무한번 실행 가능하다.

종류는 skip(), limit() -건너뛰기

filter(),distinct() -선별

sorted() -정렬

map(),flatMap() -변환

등이 있는데 각 분류별로 비슷한 특징 이있다.

여기서 map()flatMap()의 차이를 아는 것이 중요한데, map(Function<T,R>) map의 매개변수로 들어간 람다식의 반환 타입<R>으로 스트림을 변환한다. 여기서 함수의 입력 타입은 변환 전 스트림의 지네릭 타입인 <T>이다.

 

2021-01-22

Optional 이란 래퍼클래스의 효용성.

 

1.NullPointerException을 미연에 방지.

때문에, 스트림의 최종연산의 타입으로 Optional 클래스가 올 때가 있음. null 처리를 orElse(str)를 하면, Optional의 요소가 null일 경우 str 문자열을 반환.

 

2.Stream의 메소드 들도 활용 가능.

 

3.생성, 주요 메소드 기능, 기본형Optional

생성-> 스트림과 비슷하게 of 이용.

get-> Optional객체의 값을 얻어옴.

스트림의 중간연산을 그대로 사용 가능, filter, flatmap, distinct .

 

2021-01-27

스트림의 최종연산중 가장 복잡하면서도 유용하게 활용되는 collect()

 

collect()reduce()와 같은 스트림의 요소를 수집하는 기능을 한다. 그런데 collect()는 그룹별로 수집한다.

collect() 최종연산의 매개변수로는, 수집 방법을 구현 해 놓은 Collector 인터페이스가 온다. 그런데 우리는 Collector 인터페이스를 직접 다 구현하기 보다는, Collectors 클래스로 미리작성된 다양한 종류의 Collector을 반환하는 메소드를 이용해서 collect() 최종연산을 사용한다.