14장 람다식
1절 람다식이란?
- 함수적 프로그래밍
- y = f(x) 형태의 함수로 구성된 프로그래밍 기법
- 객체 지향보다 효율적인 경우
- 대용량 데이터 처리시에 유리
- 데이터를 객체 생성 후 처리 보다는 데이터를 바로 처리하는 것이 유리
- 멀티코어에서 병렬 처리 및 취합시 객체보다는 함수가 유리
- 대용량 데이터 처리시에 유리
- 객체 지향보다 효율적인 경우
- y = f(x) 형태의 함수로 구성된 프로그래밍 기법
- 자바 8부터 함수적 프로그래밍 지원
- 람다식을 언어차원에서 지원함
- 람다 계산법에서 사용된 식을 프로그래밍 언어에 접목
- 익명함수를 생성하기 위한 식
- 자바에서 람다식을 수용한 이유
- 코드가 매우 간결해진다.
- 대용량 데이터를 필터링 또는 매핑에서 쉽게 집계 가능
- 람다식을 언어차원에서 지원함
- 자바에서 람다식을 함수적 인터페이스의 익명 구현 객체로 취급 한다.
- 함수적 인터페이스는 하나의 메서드만 가지고 있는 인터페이스
람다식 -> 매개변수를 가진 코드 블록 -> 익명구현객체
Runnable 확인
2절 람다식 기본 문법
- 기본적으로 함수적 스타일의 람다식을 작성하는 방법
(타입 매개변수, ...) -> {실행문}
- 매개타입은 런타임시에 대입값에 따라 자동으로 인식하기 떄문에 생략 가능
(a) -> {System.out.println(a);}
- 하나의 매개변수만 있을 경우 ()괄호 생략 가능
a -> {System.out.println(a);}
- 하나의 실행문만 있다면 중괄호 {} 생략 가능
a -> System.out.println(a);
- 매개변수가 없다면 괄호를 생략할 수 없음
() -> {실행문;...};
- 리턴값이 있는 경우, return 문을 사용
(x,y) -> {return x+y;};
- 중괄호{} 에 return 문만 있는 경우, 중괄호를 생략 가능
(x,y) -> x+y
3절 타겟 타입과 함수적 인터페이스
- 타겟 타입(target type)
- 람다식이 대입괴는 인터페이스를 말한다.
- 익명 구현 객체를 만들 때 사용할 인터페이스다.
//인터페이스를 타겟 타입이라 한다.
인터페이스 변수 = 람다식;
- 함수적 인터페이스(functional interface)
- 모든 인터페이스는 람다식의 타겟 타입이 될 수 없다.
- 람다식은 하나의 메서드를 정의 해야힌다.
- 하나의 추상 메서드만 선언된 인터페이스만 타겟 타입이 될 수 있다.
- 함수적 인터페이스
- 하나의 추상 메서드만 선언된 인터페이스를 말한다.
- @FunctionalInterface 어노테이션
- 하나의 추상 메서드만을 가지는지 컴파일러가 체크한다.
- 두개이상의 추상 메서드가 선언되어 있으면 컴파일 오류 발생
- 필수는 아니지만 위에서 말한것과 같이 두개 이상의 메서드를 선언하면 함수적인터페이스 사용이 불가하다.
- 모든 인터페이스는 람다식의 타겟 타입이 될 수 없다.
매개변수와 리턴값이 없는 람다식
public class MyfunctionalExample {
public static void main(String[] args){
MyFunctionalInterface mfi;
//MyFunctionalInterface method에 익명구현객체가 된다.
mfi = () -> {
String str = "method call1";
System.out.println(str);
};
mfi.method();
mfi = () -> {
System.out.println("method call2");
};
mfi.method();
mfi = () -> System.out.println("method call3");
mfi.method();
//람다식은 익명 구현 객체와 같다.
mfi = new MyFunctionalInterface() {
@Override
public void method() {
System.out.println("method call4");
}
};
mfi.method();
}
}
@FunctionalInterface //컴파일러는 하나의 메서드가 있는지 컴파일 단계에서 체크한다.
public interface MyFunctionalInterface {
//함수적 인터페이스가 된다.
public void method();
//컴파일 오류 발생 [확인]
//public void method2();
}
/**
* 메서드를 하나만 가지고 있다면 함수적인터페이스가 된다.
* @FunctionalInterface 가 필수는 아니다.
*/
매개변수가 있는 람다식
public class MyfunctionalExample {
public static void main(String[] args){
MyFunctionalInterface mfi;
mfi = (x) -> {
int result = 5 * x;
System.out.println(result);
};
mfi.method(2);
//약식표현
mfi = x -> System.out.println(5 * x);
mfi.method(3);
}
}
@FunctionalInterface
public interface MyFunctionalInterface {
//매개변수 하나 추가
public void method(int x);
}
리턴값이 잇는 람다식
public class MyfunctionalExample {
public static void main(String[] args) {
MyFunctionalInterface mfi;
//MyFunctionalInterface -> method가 실행될 때 람다식이 실행된다.
mfi = (x, y) -> {
int result = x + y;
return result;
};
System.out.println(mfi.method(2, 5));
//계산기 같은 확장 가능
mfi = (x, y) -> {
int result = x * y;
return result;
};
System.out.println(mfi.method(2, 5));
//약식
mfi = (x, y) -> x + y;
System.out.println(mfi.method(2, 5));
//약식
mfi = (x, y) -> sum(x, y);
System.out.println(mfi.method(2, 5));
//약식2 정적 메서드 테스트
// mfi = (x,y) -> sum2(x,y); //정적메서드만 가능함
// System.out.println(mfi.method(2,5));
/**
* 궁금
* 람다와 스트림이란 ?
*/
}
/**
* 람다안에서는 정적 메서드만 사용이 가능하다.
*
* @param x
* @param y
* @return
*/
public static int sum(int x, int y) {
return x + y;
}
/**
* 람다안에서는 정적 메서드만 사용이 가능하다.
*
* @param x
* @param y
* @return
*/
public int sum2(int x, int y) {
return x + y;
}
}
@FunctionalInterface
public interface MyFunctionalInterface {
//매개변수 하나 추가
public int method(int x, int y);
}
###4절 클래스 멤버와 로컬 변수 사용
- 클래스의 멤버 사용
- 람다식 실행 블록에는 클래스의 멤버인 필드와 메서드를 제약 없이 사용할 수 있다.
- 람다식 실행 블록내에서 this는 람다식을 실행한 객체의 참조이다.
public class UsingThis {
public int outterField = 10;
public int field = 10;
class Inner { //람다식을 실행한 객체
int innerField = 20;
int field = 20;
void method() {
MyFunctionalInterface mfi = () -> {
System.out.println("outterField : " + outterField);
System.out.println("innerfield : " + innerField);
System.out.println("outterField : " + UsingThis.this.field);
System.out.println("innerfield : " + this.field);
};
mfi.method();
}
}
}
공통 함수적 인터페이스
@FunctionalInterface
public interface MyFunctionalInterface {
//매개변수 하나 추가
public void method();
}
묵시적으로 final 적용이 된다.
public class UsingLocalVariableExample {
public static void main(String[] args){
UsingLocalVariable ulv = new UsingLocalVariable();
ulv.method(40);
}
}
public class UsingLocalVariable {
void method(int arg) {
int localVar = 40;
// final이 되어야 한다. 묵시적으로 final 적용이 된다.
// 주석을 풀게 되면 오류를 확인할 수 있다.
// arg = 1;
// localVar = 20;
MyFunctionalInterface mfi = () -> {
System.out.println("arg : " + arg);
System.out.println("localVar : " + localVar);
};
mfi.method();
}
}
외부 내부 클래스 호출
public class UsingThis {
public int outterField = 10;
public int field = 10;
class Inner {
int innerField = 20;
int field = 20;
void method() {
MyFunctionalInterface mfi = () -> {
System.out.println("outterField : " + outterField);
System.out.println("innerfield : " + innerField);
//field 의 이름의 같은경우
//바깥클래스 호출
System.out.println("outterField : " + UsingThis.this.field);
//내부클래스 호출
System.out.println("innerfield : " + this.field);
};
mfi.method();
}
}
}
public class UsingThisExample {
public static void main(String[] args) {
//중첩클래스에서 바깥(외부) 클래스의 객체 생성
UsingThis usingThis = new UsingThis();
//중첩클래스에서 내부 클래스의 객체 생성
UsingThis.Inner inner = usingThis.new Inner();
inner.method();
}
}
5절 표준 API의 함수적 인터페이스
- 한개의 추상 메서드를 가지는 인터페이스 들은 모두 람다식 사용가능
RunnableExample 클래스
public class RunnableExample {
public static void main(String[] args){
//잘 사용하지 않음
Runnable runnable = () -> {
for(int i=0; i<10; i++){
System.out.println(i);
}
};
Thread thread = new Thread(runnable);
thread.start();
//일반적으로 이렇게 사용
Thread thread2 = new Thread(() -> {
for(int i=0; i<10; i++){
System.out.println(i);
}
});
thread2.start();
}
}
Runnable 인터페이스
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run(); //하나의 추상메서드를 가지고 있다.
}
- 자바 8부터 표준 API로 제공되는 함수적 인터페이스
- java.util.function 패키지에 포함되어 있다.
- 매개타입으로 사용되어 람다식을 매개값으로 대입할 수 있도록 해준다.
5가지 함수적 인터페이스 특징 ~
- Consumer - 데이터를 소비한다. - 매개값은 사용하고 리턴값은 없음 - 소비
- Supplier - 공급자 데이터를 리턴 - 매개값은 없고 리턴값 있음 - 공급
- Function - 데이터 변환 시 사용 - 매개값 사용 - 매개값 변환 - 리턴값 존재
- Operator - 연산 - 매개값 사용 - 연산 후 - 리턴값 존재
- Predicate - 매개값을 조사해서 true, false를 리턴
Comsumer, Function Operator
- andThen(), compose() 디폴트 메서드
- 함수적 인터페이스가 가지고 있는 디폴트 메서드이다.
- 두개의 함수적 인터페이스를 순차적으로 연결해서 실행한다.
- 첫번째 리턴값을 두번째 매개값으로 제공해서 최종 결과값 리턴한다.
- andThen()과 compose()의 차이점은 어떤 함수적 인터페이스 부터 처리하느냐에 따름
Predicate
- and(), or(), negate() 디폴트 메서드 isEqual() 정적 메서드
- and() == &&와 대응
- or() == || 과 대응
- negate() == ! 과 대응
- isEqual()
Objects.equals(soruce, target)
source | targer | 리턴값 |
---|---|---|
null | null | true |
null or not null | null or not null | false |
not null | not null | source.equals(targer) 의 리턴값 |
###6절 메서드 참조
- 메서드를 참조해서 매개변수의 정보 및 리턴타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이 목적이다.
종종 람다식은 기존 메서드를 단순하게 호출만 하는 경루가 있다.
(left, right) -> Math.max(left, right) //실행부 확인 시 매개 변수 값이 같을 경우 메서드 참조 가능 (줄여보자)
Math :: max; //메서드 참조
- 메서드 참조도 람다식과 마찬가지로 인터페이스 익명 구현 객체로 생성된다.
- 타겟 타입에서 추상 메서드의 매개변수 및 리턴 차입에 따라 메서드 참조도 달라진다. (타입과 매개값이 같은 경우 사용 가능)
정적메서드, 인스턴스메서드 참조
public class MethodReferencesExample {
public static void main(String[] args){
IntBinaryOperator operator;
//정적 메소드 참조
operator = (x,y) -> Calculator.staticMethod(x,y);
System.out.println("결과1 : " + operator.applyAsInt(1,2));
operator = Calculator :: staticMethod;
System.out.println("결과2 : " +operator.applyAsInt(3,4));
//인스턴스 메소드 참조
Calculator obj = new Calculator();
operator = (x,y) -> obj.instanceMethod(x,y);
System.out.println("결과3 : " +operator.applyAsInt(5,6));
operator = obj :: instanceMethod;
System.out.println("결과4 : " +operator.applyAsInt(7,8));
//함수적 인터페이스 익명 구현 객체로 만들어 짐으로 함수적 인터페이스의 변수에다가 대입이 가능하다.
}
}
public class Calculator {
public static int staticMethod(int x, int y) {
return x + y;
}
public int instanceMethod(int x, int y){
return x + y;
}
}
- 생성자 참조
(a,b) -> {return new 클래스(a,b)} => 클래스 :: new (타입과 매개 변수가 같아야 함)
매개값의 수에 따라서 생성자가 생성 된다.
public class ConstructorReferencesExample {
public static void main(String[] args){
//매개변수와 타입이 맞는 생성자가 생성된다.
Function<String, Member> function1 = Member::new;
Member membe1 = function1.apply("변");
BiFunction<String, String, Member> function2 = Member::new;
Member member2 = function2.apply("변", "whydda");
Function<String, Member> function3 = Member::new;
Member membe3 = function1.apply("김");
}
}
public class Member {
private String name;
private String id;
public Member() {
System.out.println("Member() 실행");
}
public Member(String name) {
this.name = name;
System.out.println("Member(String name) 실행");
}
public Member(String name, String id) {
this.name = name;
this.id = id;
System.out.println("Member(String name, String id) 실행");
}
}
- 회고
역시 3번 이상은 읽어봐야할꺼 같다.
정리를 한다고 했는데 혹시나 좀 이상한 부분 있으면 알려주세요 :) - 참고링크
'Back-End' 카테고리의 다른 글
Spring Boot 특정 버전의 의존성은 어떻게 확인 할 수 있을까? (0) | 2021.07.23 |
---|---|
Class To Map, Map To Class 변환하는 함수를 만들어 보자! (2) | 2020.04.03 |