[백기선님께서 주최하시는 Java 기초스터디]
github.com/whiteship/live-study
whiteship/live-study
온라인 스터디. Contribute to whiteship/live-study development by creating an account on GitHub.
github.com
드디어 마지막 주차.....
이번주 과제의 주제는 람다식이고 자세한 내용은 아래와 같다
▶ 람다식 사용법
람다식(Lambda Expression) 이란 나중에 실행될 목적으로 다른 곳에 전달될 수 있는 이름이 없는 함수이며 Java8 부터 도입됐다
람다식을 사용하는 이유는 다음과 같다
- 함수형 코드를 작성할 수 있게 해준다
자바는 대표적인 객체지향 언어이지만 람다식을 사용함으로 인해서 함수형 프로그래밍의 작성도 가능하게 한다 - 가독성이 있고 간결한 코드를 작성할 수 있다
- API와 라이브러리들을 쉽게 사용할 수 있게 해준다
- 병렬처리에 대한 지원을 가능하게 해준다
- 함수를 하나의 객체로 간주하기 때문에 객체처럼 다룰 수 있다
- 메소드를 한 번만 사용할 경우에 클래스에 메소드를 작성할 필요 없이 람다식을 통해서 간단하게 한 번만 사용할 수 있다
■ 람다식의 문법
람다식의 문법은 다음과 같다
() -> {body}; // 매개변수가 없는 경우
(arg1, arg2) -> {body}; // 매개변수의 타입을 유추할 수 있는 경우 타입에 대한 명시를 생략할 수 있다
(type arg1, type arg2) -> {body};
매개변수가 하나일 경우 + 람다식의 몸체가 단 한 줄로 구성되어 있는 경우에는 간단하게 다음과 같이 쓸 수 있다
arg -> statement;
■ 람다식을 이용해서 배열 내에 요소 출력하기
먼저 기존의 for 문을 사용해서 배열 내의 원소를 출력하는 방법은 아래와 같이 할 수 있다
nums 의 forEach 메소드에 람다식을 전달하면 더 간결하게 코드를 작성할 수 있다
람다식으로 전달된 num 매개변수는 타입추론이 가능하므로 타입을 명시하지 않아도 되고, 람다식의 몸체는 단일문장으로 구성되어 있으므로 중괄호를 생략할 수 있다는 이점이 있다
▶ 함수형 인터페이스
함수 인터페이스란 하나의 추상 메소드만 선언된 인터페이스를 의미한다
대표적인 예로 Runnable 인터페이스가 있다
위에 인터페이스를 보면 알 수 있듯이 단 하나의 추상 메소드인 run() 만 명시 되어있다
람다식이 있기 전에는 Runnable 클래스의 구현체를 Thread 의 생성자로 넘겨주었어야 했다 (앞서 과제에서 해본 적이 있다)
아래는 무명 클래스(Anonymous Class) 를 이용해 Thread 의 생성자로 넘겨주었다
하지만 람다식을 Thread 클래스의 생성자로 넘겨주어도 코드는 간단하게 만들면서 같은 결과를 낼 수 있다
이 람다식이 어떻게 Runnable 인터페이스를 구현한 클래스로써 Thread 생성자에 전달될 수 있는걸까??
이 이유는 오늘 과제의 마지막 주제인 메소드, 생성자 레퍼런스와 관련이 있다
※ 람다식 vs 무명 클래스
1. this 키워드의 사용
람다식의 this 는 람다식이 작성된 클래스를 가리킨다. 예를 들어 람다식이 Main 클래스 내부에 작성이 되어 있다면 this 는 메인 클래스를 가리킨다. 반면에 무명 클래스에서의 this 는 무명 클래스 자신을 가리킨다
2. 컴파일 방식
람다식을 컴파일 하여서 클래스의 private 메소드로 변환한다
▶ Variable Capture
Java 의 람다식은 특정한 상황에서 함수 몸체 바깥에 선언된 변수들에 접근할 수 있다. 이를 변수 캡쳐라고 부른다.
람다식 몸체에서 다음과 같은 종류들에 대해 접근할 수 있다.
- 지역변수
- 인스턴스 변수
- 정적 변수
■ 지역변수 캡쳐(Local Variable Capture)
지역변수 캡쳐는 람다식 몸체 외부에 선언된 지역변수의 값에 접근할 수 있다
람다식 내부에서 지역변수를 참조할 수 있는 이유는 변수에 대한 참조가 effectively final 이기 때문이다
그렇기 때문에 람다식에서 지역변수를 참조하기 위해서는 참조하는 지역변수가 final 이거나 effectively final 이어야 한다
(effectively final? 해당 변수의 값이 할당되고 난 뒤에 변경되지 않음을 의미한다)
만약에 참조하고 있는 변수가 나중에 변경이 되면 컴파일러가 람다식 내부에서 경고를 표시한다.
■ 인스턴스 변수 캡쳐(Instance Variable Capture)
람다식은 람다를 생성하는 객체의 인스턴스 변수를 캡쳐할 수 있다
객체 내에 숫자를 하나씩 증가 시키는 예제를 보자
먼저 함수형 인터페이스를 정의한다
그리고 Counter 클래스를 작성한다
이 클래스에 정의된 printNumAndIncrease 메소드의 몸체는 함수형 인터페이스에 정의된 추상 메소드를 구현하고 그 메소드를 실행한다
그렇게 되면 이 객체에 있는 num 이라는 인스턴스 변수가 하나씩 증가하게 되고 그 결과를 출력한다
지역변수 캡쳐랑 다른 점은 인스턴스 변수 캡쳐는 참조하고 있는 변수의 값을 변경할 수 있다는 점이다
위의 예제를 보면 람다식 내부에서 num 의 값을 변경하고 있음에도 컴파일러가 오류를 띄우지 않고 있다
■ 정적 변수 캡쳐(Static Variable Capture)
람다식은 정적 변수를 캡쳐할 수 있는데, 우리가 알고 있는 것과 같이 정적 변수는 원래 Java 애플리케이션 내부 어디에서든지 접근할 수 있다. 람다식의 몸체 내부도 예외는 아니다.
그리고 인스턴스 변수 캡쳐와 마찬가지로 참조하고 있는 정적 변수가 변경되더라도 그 변수를 계속 캡쳐할 수 있다
▶ 메소드, 생성자 레퍼런스
■ 람다로써의 메소드 참조
만약에 람다식이 매개변수를 받아서 다른 메소드를 호출하는 작업이 전부라면 람다 구현은 메소드를 호출하는 표현을 짧게 할 수 있는 방식을 제공한다
에를 들어 다음과 같은 함수형 인터페이스가 있다고 하고
위의 함수형 인터페이스를 구현하는 람다식은 다음과 같이 작성할 수 있다
여기서 람다 몸체는 하나의 문장만 있기 때문에 중괄호는 생략할 수 있다(위에 람다 사용법에서 봤듯이)
또 람다 메소드에 전달되는 매개변수는 하나 밖에 없으므로 매개변수 서명을 감싸고 있는 괄호도 제거할 수 있다
람다 몸체는 받은 매개변수를 System.out.println 메소드로 전달하고 있으므로 람다 선언을 메소드 참조로 대체할 수 있다
여기서 :: 은 Java 컴파일러에게 메소드 참조임을 알려주는 것이다. 위의 코드에서는 println 메소드를 참조한다는 것을 의미한다
참조된 메소드를 소유하는 클래스 또는 객체가 더블 콜론(::) 앞에 나온다
이제 본격적으로 메소드 참조에 대해 알아보자
다음의 메소드들의 타입을 참조할 수 있다
- 정적 메소드
- 매개변수 메소드
- 인스턴스 메소드
- 생성자(생성자도 메소드이므로)
■ 정적 메소드 참조
정적 메소드는 참조하기 가장 쉬운 메소드이다
먼저 함수 인터페이스 하나를 정의한다
그리고 하나의 클래스를 정의하는데 함수형 인터페이스의 구현을 담당할 정적 메소드를 갖는다
이 정적 메소드는 ss1 문자열 내에서 얻어지는 ss2 문자열의 위치를 찾는다
이제 이 정적 메소드를 참조하는 람다식은 다음과 같다
여기서 Finder.find() 메소드의 매개변수와 FinderClass 에 정의된 doFind 정적 메소드의 매개변수는 타입과 갯수가 같다
그러므로 Finder.find() 를 구현하는 것이 가능해진거고 FinderClass 의 doFind 메소드를 참조하는게 가능한 것이다
■ 매개변수 메소드 참조
람다에 대한 매개변수 중 하나의 메소드를 참조할 수 있다
위와 같이 함수형 인터페이스 Finder가 있다고 가정하자
find 메소드는 s1 문자열에서 s2 문자열의 위치를 찾는다는 것을 의미한다
이제 String 의 indexOf 를 호출하는 람다식으로 구현한다
이 람다식은 다음의 람다식과 동일하다
Java 컴파일러는 두번째 매개변수를 참조된 메소드의 매개변수로 사용해서 첫번째 매개변수 타입에 대응하는 참조된 메소드를 일치시키려고 시도할 것이다
■ 인스턴스 메소드 참조
람다의 정의로 부터 인스턴스 메소드를 참조하는 것도 가능하다
계속해서 Finder 함수형 인터페이스를 사용하고, 위에서 정의했던 FinderClass 클래스의 doFind 메소드를 인스턴스 메소드로 변경하자
함수형 인터페이스에 있는 find 메소드 서명과 FinderClass 클래스 내에 정의된 doFind 메소드의 서명이 일치한다.
그렇기 때문에 우리는 FinderClass 클래스의 객체를 생성하고 나서 doFind 메소드를 참조할 수 있는 것이다
람다식은 첫줄에 생성된 fc 인스턴스의 doFind 메소드를 참조하는 것에 의해 생성된다
■ 생성자 참조
클래스의 생성자를 참조하는 것도 가능하다. 클래스 이름 뒤에 ::new 를 붙이면 된다
FinderClass::new
생성자를 어떻게 람다식으로 쓸 수 있는지 알아보기 위해서 함수형 인터페이스 하나를 정의하자
함수형 인터페이스에 정의된 create 추상 메소드의 서명은 String 클래스의 생성자 중 하나의 서명과 일치한다
그러므로 이 생성자를 람다처럼 사용할 수 있다
위의 식은 다음과 동일하다
[참조자료]
어서와 Java는 처음이지! - 교보문고
이 책 [어서와 Java는 처음이지!]는 그림으로 중요한 개념을 확실하게 설명한 다음, LAB을 통해 충분히 활용할 수 있게 한 책으로, 중요한 핵심 개념들에 대해서는 꼼꼼한 설명을 들려주고 자바의
www.kyobobook.co.kr
tutorials.jenkov.com/java/lambda-expressions.html#method-references-as-lambdas
Java Lambda Expressions
Java lambda expressions are Java's first steps into functional programming. This tutorial explains how Java lambda expressions work, how they are defined and how you use them.
tutorials.jenkov.com
'공부 > whiteship-java' 카테고리의 다른 글
14주차: 제네릭 (0) | 2021.02.27 |
---|---|
13주차: I/O (0) | 2021.02.25 |
12주차: 애노테이션 (0) | 2021.02.06 |
11주차: enum (0) | 2021.01.30 |
10주차: 멀티쓰레드 프로그래밍 (0) | 2021.01.22 |