모던 자바 인 액션 (세션 3-1)

kimseungki·2022년 6월 17일
0

독서

목록 보기
3/8
post-thumbnail

개요

이번 세션은 람다와 메소드 참조에 대한 내용이 대부분이다.

람다를 쓰는 이유

세션2에서 본 동적 파라미터화를 더 유연하고 재사용이 가능한 코드를 만들기 위해 사용
이전 동적 파라미터화의 경우 선언과 인스턴스화를 각각 해야되는 단점이 존재했었음.
익명함수로 해결하기에는 너무 코드가 길어지고, 이는 가독성을 저해함

람다 특징

  1. 익명 : 메소드에 이름이 존재하지 않음
  2. 함수 : 메서드와 달리 특정 클래스에 종속되지 않아 함수로 부름. 단, 파라미터 리스트, 리턴, 예외처리 등이 가능
  3. 전달 : 람다 표현식을 메서드 파라미터 인수나, 리턴을 통한 데이터를 변수에 삽입할 수 있다.
  4. 간결성 : 익명 클래스와 달리 new 등을 선언할 필요가 없어 코드가 간결한 편

람다 표현식

(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
  1. 파라미터 리스트 : (Apple a1, Apple a2)
  2. 화살표 : 파라미터 리스트와 바디를 분리
  3. 람다 바디 : a1.getWeight().compareTo(a2.getWeight()

람다 스타일

  1. 표현식 스타일 : 별도의 리턴문이 존재하지 않음
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
  1. 블록 스타일 : 어떤 데이터를 리턴을 할지에 대한 명시가 필요
(Apple a1, Apple a2) -> {
return a1.getWeight().compareTo(a2.getWeight());
}

함수형 인터페이스

인터페이스이고 정확히 하나의 추상 메소드를 지정하는 인터페이스를 의미한다.

@FunctionalInterface
public interface Funcational {
    int add(int a, int b);
}

함수형 인터페이스의 경우 여러개의 추상 메소드 지정은 하면 안되고, 디폴트 메소드를 넣는 것은 가능하다.
람다는 함수형 인터페이스를 인수로 받는 메서드에서만 사용이 가능하다.

람다와 함수형 인터페이스 접목

Functional data = (a, b) -> a+b;

함수형 인터페이스의 추상 메소드 구현을 람다가 하기 때문에, 람다의 표현은
함수형 인터페이스의 인스턴스로 취급이 되고, 함수형 인터페이스를 구현한 클래스의 인스턴스를 생성했다고
말할 수 있다.

함수 디스크립터

위의 예시에서 add 메소드를 예시로 들면,
추상 메서드 시그니처 : add
함수 디스크립터 : (int, int) -> int

자바에서 제공하는 함수형 인터페이스

  1. Predicate : test 메소드를 제공하며 제네릭 타입을
    파라미터로 받아 리턴을 boolean으로 처리할 때 사용하면 유용
    (디폴트 메서드로 and, or 등도 있어 함께 연달아서 사용할 수 있음)
  2. Consumer : 제네릭 객체를 받아 리턴을 void로 처리 추상메소드는 accept
    보통 출력 데이터를 람다로 처리할 때 많이 쓰면 좋을것 같다.
  3. Function : 제네릭 타입을 받아서 또 다른 제네릭 타입의 객체로 반환해주는 기능
    추상 메소드는 apply

자바에서 제공하는 기본형 특화 함수형 인터페이스

IntPredicate, DoublePredicate 등등
해당 메소드를 사용하면 추후 제네릭 타입에 기본형을 선언을 할 필요가 없어서 매우 편하다.

람다 예외처리

람다를 쓸 때 예외처리 역시 고려해야 한다. 또한 예외처리를 할 경우 함수형인터페이스와
람다 모두 예외처리를 선언해야 한다.

@FunctionalInterface
public interface Funcational throws NullPointerException {
    int add(int a, int b);
}
Functional data = (a, b) -> {
	try{
	return a+b;
    } catch(NullPointerException e){
    	throw new NullPointerException(e.getMessage());
    }
};

람다의 형식 검사 개요

콘텍스트 : 람다의 형식을 추론하기 위해 사용, 이를 통해 람다의 메서드 파라미터나 할당되는 변수 등을 확인
대상 형식 : 람다의 표현식의 형식을 나타내는 말, 위의 예시에선 Functional(클래스명)이 대상형식

람다의 형식 검사 프로세스

  1. 람다에 사용 된 콘텍스트 확인
  2. 확인결과 Functional(클래스명)이 콘텍스트임을 확인
  3. 함수형 인터페이스에서 추상메소드가 무엇인지 확인
  4. add(추상메소드)임을 확인함과 동시에 int 형 2개가 파라미터이고 int로 반환해야되는 것을 확인
  5. 함수 디스크립터가 (int,int) -> int 이므로 람다의 시그니처와 동일, 이후 형식 검사 종료
  • 형식 추론 중 4번에서 파라미터를 확인할 수 있기 때문에..
Funcational data = (a, b) -> a+b;

이런 식으로 써도 상관없다.

람다에서 지역변수를 못쓰는 이유

람다에서 지역변수를 출력은 할 수 있지만, 값을 변경하는 것은 할 수 없다.
이유는 2가지이다.
1. 스레드가 있고, 각각의 스레드는 고유의 스택을 가지고 있기 때문에
스택에 저장한 데이터를 다른 스레드가 가지고 올 수 없기 때문에 문제발생
2. 설사 같은 스레드라고 하더라도 스택에 있는 것은 람다가 끝나면 pop으로 사라지기 때문에
이후 다른 람다나 해당 지역 변수를 호출할 경우 해당 변수를 초기화 했던 정보를 가져올 수 없기 때문이다.

람다가 클로저의 정의에 부합되는가?

결론만 말하자면 아니다.
공통점 : 지역변수에 접근이 가능하다.
차이점 : 위에 말한 것처럼 값을 바꿀 수는 없다.

의문점

람다에 Integer같이 참조형 변수를 넣고 해당 변수를 초기화하는 것은 스택에 변수이름이 저장이 되어있어 막히는건지..?
힙은 서로 공유가 되기 때문에 상관은 없지만.. 이에 대한 의문점은 아직까지 남아있다.

후기

사실 동적 파라미터만 봐도 신세계였는데 람다를 보고 더 신기함을 많이 느꼈다.
메소드 참조도 여기에 다 작성할라 했는데 지금 정보도 너무 많아서 따로 출간할 생각이다.

profile
seung 기술블로그

0개의 댓글