티스토리 뷰
1. 람다식 ( Lamda Expression )
- 자바8부터 함수적 프로그래밍을 위해 람다식 지원하기 시작했다.
- 익명 함수(Anonymous function)를 생성하기 위한 식이다.
- 람다식을 통해서 코드가 매우 간결해지고, 컬렉션 요소(대용량 데이터)를 필터링 또는 매핑하기 쉽게 집계해준다.
- 자바는 람다식을 함수적 인터페이스의 익명 구현 객체로 취급한다.
- 람다식이 구현대상이 되는 함수적 인터페이스를 해당 람다식의 Target Type이라 부른다.
- 모든 인터페이스가 람다식의 구현 대상이 될 수는 없다.
- 람다식의 조건 : 구격(추상메소드)이 오로지 단 1 개만 있는 인터페이스
- 람다식의 조건에서는 추상메소드의 개수만 확인하기 때문에 상수나 디폴트 메소드의 개수는 2개 이상이어도 상관이 없다.
- 인터페이스의 위에 @FunctionalInterface를 붙이면 추상 메소드가 1개인지 확인해 주는 기능을 해주는데, 이를 사용한다.
2. 람다식 기본 문법
- ( 타입 매개변수 ) - > { 실행문 ; }
- Ex. ( int a ) - > { System.out.println(a) ; }
- 람다식은 다음과 같은 조건에 해당하면 생략이 가능한데, 최대한 람다식을 생략하는 것이 좋다.
- 1 ) 매개변수 타입(int)은 런타임시에 대입 값(a)에 따라 자동으로 인식이 가능하기에 생략이(int a -> a) 가능하다.
- 2 ) 하나의 매개변수만 있을 경우에는 ( )는 생략이 가능하다.
- 3 ) 하나의 실행문만 있다면 중괄호 { }는 생략이 가능하다.
- 4 ) 매개변수가 없을 경우에는 ( )는 생략이 불가능하다.
- 5 ) 리턴값이 있는 경우에는 return을 사용해야 한다.
- 6 ) 중괄호 { }에 return문만이 있을 경우에는, 중괄호를 생략하는 것이 가능하다.
3. 타겟타입
- 타겟 타입은 람다식이 대입되는 인터페이스로, 익명 구현 객체를 만들 때 사용하는 인터페이스를 의미한다.
- Ex. 인터페이스 변수 = 람다식;
- Ex. MyfunctionalInterface fi;
- Ex. fi = () -> System.out.println("method call2");
- Ex. fi.method();
4. 자바 표준 API
- 자바 8부터는 표준 API로 제공되는 함수적 인터페이스는 java.util.function 패키지에 포함되어 있다.
- 매개타입으로 사용되어 람다식을 매개 값으로 대입할 수 있도록 한다.
- 단 1 개의 추상 메소드를 가지고 있는 인터페이스들은 모두 람다식을 사용할 수 있다.
- 인터페이스에 선언된 추상 메소드의 매개 값과 리턴 값의 유무에 따라서 구분된다.
- 함수적 인터페이스의 종류 :
- 1 ) Consumer 함수적(functional) 인터페이스 :
- 매개 값만 있고 리턴 값이 없는 추상 메소드를 가진다.
- 매개 값을 가지고 데이터를 소개하는 함수적 인터페이스다.
- 추상 메소드 : void accept ( 매개변수 )
- 2 ) Supplier 함수적 인터페이스 :
- 매개 값은 없고 리턴 값만 있는 추상 메소드를 가진다.
- 추상 메소드 : 타입 + ( get+As+타입 ) ( ) / get ( )
- 3 ) Function 함수적 인터페이스 :
- 매개 값과 리턴 값이 모두 있는 추상 메소드를 가진다.
- 주로 매개 값을 리턴 값으로 타입변환(매핑)을 하는 경우 사용한다.
- 이때 타입 변환은 강제형변환이나 자동형변환을 의미하는 것은 아니다.
- 추상 메소드1 : 변화 후 타입 + ( apply+As+변화 후 타입 ) ( 변하기 전 타입 ) /
- 추상 메소드2 : 변화 후 타입 + apply ( 변하기 전의 타입 + 매개 변수 )
- 4 ) Operator 함수적 인터페이스 :
- 매개 값과 리턴 값이 모두 있는 추상 메소드를 가진다.
- 매개 값을 연산하고 그 결과를 리턴하는 경우에 사용한다.
- 5 ) Predicate 함수적 인터페이스 :
- 매개 값을 조사해서 true 또는 false를 리턴할 때 사용한다.
01. 람다식 - 1st case. 매개변수와 리턴값이 없는 경우
package no_argument_no_return;
@FunctionalInterface // 추상 메소드의 개수 확인
public interface MyfunctionalInterface { // 람다식 - @FunctionalInterface ( 함수적 인터페이스 )
public abstract void method(); // 1st case
// public abstract void method (int age); // 2nd case - 매개변수가 있는 버전
// public abstract int method3(); // 3rd case - 리턴 타입이 있는 버전
// public abstract int method4 ( int age ); // 4th case - 리턴타입과 매개변수가 있는 버전
// 추상 메소드가 1개가 아니라 2개 이상이면 오류가 발생한다.
// 각 케이스에 따라서 람다식의 작성 방법이 달라진다.
} // end interface
package no_argument_no_return;
public class MyfunctionalInterfaceExampel {
public static void main(String[] args) { // 람다식 (***)
MyfunctionalInterface fi; // functionalInterface타입의 변수 생성 = 람다식 적용 가능!
// ======================================
// 1. 람다식 적용 ( 1st case )
fi = () -> {
// ( )는 public abstract void method();에 있는 ( )를 그대로 가져 온 것으로
// 가져온 이후에 { };를 통해 오버 라이딩을 하는 것이다.
String str = "method call"; // 값을 넣어 주고
System.out.println(str); // 이를 출력하는 메소드로 오버 라이딩하였다.
// 1st case의 경우 리턴타입이 없기에 리턴값을 주지 않는다.
}; // 람다식 : f1에 선언된 추상 매소드를 오버라이딩하는 익명구현객체를 생성 (**)
fi.method(); // fi에 선언된 추상 메소드 호출 ( 다형성 2 )
// ======================================
// 2-1 인터페이스의 구현객체 작성방법 : 직접 구현 클래스 선언
fi = new MyfunctionalInterfaceImpl(); // 다형성 1
fi.method(); // 다형성 2
// ======================================
// 2-2 인터페이스의 구현객체 작성방법 : 익명구현객체 코딩기법
fi = new MyfunctionalInterface() {
@Override
public void method() {
System.out.println("Anonymous::method() invoked.");
} // 메소드
}; // 익명구현객체 생성
fi.method(); // 다형성 2
// ======================================
// 2-3 인터페이스의 구현객체 작성방법 : 람다식으로 구현하자!!
fi = () -> { // 다형성 1 적용 - 오버라이드
System.out.println("Anonymous by Lambda::method() invoked.");
};
fi.method();
// ======================================
// 2-1과 2-2의 방법은 추상 메소드의 개수가 몇개인지 상관이 없지만
// 2-3의 방법인 람다식은 메소드의 이름을 가져오지 않기에 추상 메소드가 1개일때만 사용이 가능하다!
// 단, 람다식을 사용하면 코드가 매우 간결해지는 장점이 있다.
// ======================================
// 3. 오버라이딩된 실행문이 1개면 { };는 생략이 가능하다!
fi = () -> System.out.println("method call2");
fi.method();
// ======================================
fi = () -> {
System.out.println("method call3");
};
fi.method();
// ======================================
// 출력 :
// method call
// MyfunctionalInterfaceImpl::method() invoked.
// Anonymous::method() invoked.
// Anonymous by Lambda::method() invoked.
// method call2
// method call3
// ======================================
} // main
} // end class
02. 람다식 - 2nd case. 매개변수가 있는 경우
package no_argument_no_return;
@FunctionalInterface // 추상 메소드의 개수 확인
public interface MyfunctionalInterface2 { // 람다식 - @FunctionalInterface ( 함수적 인터페이스 )
// public abstract void method(); // 1st case
public abstract void method (int age); // 2nd case - 매개변수가 있는 버전
// public abstract int method3(); // 3rd case - 리턴 타입이 있는 버전
// public abstract int method4 ( int age ); // 4th case - 리턴타입과 매개변수가 있는 버전
// 추상 메소드가 1개가 아니라 2개 이상이면 오류가 발생한다.
// 각 케이스에 따라서 람다식의 작성 방법이 달라진다.
} // end interface
package no_argument_no_return;
public class MyfunctionalInterfaceExampel2 {
public static void main(String[] args) { // 람다식 - 매개변수 있는 타입 (***)
MyfunctionalInterface2 fi; // functionalInterface타입의 변수 생성 = 람다식 적용 가능!
// ======================================
// 1. 람다식 적용 ( 2nd case )
fi = (int age) -> {
System.out.println("Anonymous::method2 invoked.-1");
};
fi.method(23);
// ======================================
// 2. 생략 1 - > 하나의 실행문이면 생략가능 & 매개변수의 타입은 생략이 가능하다.
fi = (age) -> System.out.println("Anonymous::method2 invoked.-2");
fi.method(12);
// =====================================
// 3. 생략 2 - 하나의 매개변수이면 생략 가능!
fi = age -> System.out.println("Anonymous::method2 invoked.-3");
fi.method(25);
// =====================================
// 출력 :
// Anonymous::method2 invoked.-1
// Anonymous::method2 invoked.-2
// Anonymous::method2 invoked.-3
} // main
} // end class
03. 람다식 - 3rd case. 리턴값이 있는 경우
package no_argument_no_return;
@FunctionalInterface // 추상 메소드의 개수 확인
public interface MyfunctionalInterface3 { // 람다식 - @FunctionalInterface ( 함수적 인터페이스 )
// public abstract void method(); // 1st case
// public abstract void method (int age); // 2nd case - 매개변수가 있는 버전
public abstract int method3(); // 3rd case - 리턴 타입이 있는 버전
// public abstract int method4 ( int age ); // 4th case - 리턴타입과 매개변수가 있는 버전
// 추상 메소드가 1개가 아니라 2개 이상이면 오류가 발생한다.
// 각 케이스에 따라서 람다식의 작성 방법이 달라진다.
} // end interface
package no_argument_no_return;
public class MyfunctionalInterfaceExampel3 {
public static void main(String[] args) { // 람다식 - 리턴타입이 있는 타입 (***)
MyfunctionalInterface3 fi; // functionalInterface타입의 변수 생성 = 람다식 적용 가능!
// ======================================
// 1. 람다식 적용 ( 3st case )
fi = () -> {
int number1 = 100;
int number2 = 200;
return number1 + number2;
};
fi.method3();
// ======================================
// 2. 생략 1 - 리턴값의 생략
fi = () -> 100+300; // 타입에 맞게 값을 생성하는 것이면 리턴 값이 있을 확률이 높다.
fi.method3();
// ======================================
// 3. 생략 2 - 리턴값의 생략
int value = 100;
fi = () -> value; // 타입에 맞게 값만 생성할 수 있으면 메소드도 된다.
fi.method3();
// ======================================
// 4. 생략 3
int value1 = 200;
fi = () -> 220 + value1;
fi.method3();
} // main
} // end class
04. 람다식 - 4th case. 매개변수와 리턴값이 모두 있는 경우
package no_argument_no_return;
@FunctionalInterface // 추상 메소드의 개수 확인
public interface MyfunctionalInterface4 { // 람다식 - @FunctionalInterface ( 함수적 인터페이스 )
// public abstract void method(); // 1st case
// public abstract void method (int age); // 2nd case - 매개변수가 있는 버전
// public abstract int method3(); // 3rd case - 리턴 타입이 있는 버전
public abstract int method4 ( int age ); // 4th case - 리턴타입과 매개변수가 있는 버전
// 추상 메소드가 1개가 아니라 2개 이상이면 오류가 발생한다.
// 각 케이스에 따라서 람다식의 작성 방법이 달라진다.
} // end interface
package no_argument_no_return;
public class MyfunctionalInterfaceExampel4 {
public static void main(String[] args) { // 람다식 - 리턴타입과 매개변수가 있는 타입 (***)
MyfunctionalInterface4 fi; // functionalInterface타입의 변수 생성 = 람다식 적용 가능!
// ======================================
// 1. 람다식 적용 ( 4st case )
fi = (age) -> {
return 1+2*3-4;
};
fi.method4(23);
// ======================================
// 2. 생략1 - 강제형변환
fi = age -> (int) ( 2+5+1*7+9-5/3.14);
fi.method4(23);
// ======================================
// 3. 생략2 - 매개변수 간단하게 표시
fi = a -> (int)(1+2*3 - 4 / a);
System.out.println("fi : " + fi);
// 출력 : fi : no_argument_no_return.MyfunctionalInterfaceExampel4$$Lambda$3/0x0000000800c01218@85ede7b
// 이 중 $$Lambda$3/0x0000000800c01218@85ede7b가 람다식을 의미한다.
// $3는 이 클래스 내에서 이 람다식이 몇 번째 람다식인지 의미하는 것이다.
// ======================================
// fi = a -> { // this를 출력하고 싶어도 static 메소드 안에서는 정적 멤버만 사용가능해서 출력이 불가능하다.
// System.out.println(this);
// return 1000;
// };
// 그렇다면 인스턴스 안에서는 ...? - - > TTT 클래스에서 생성
// ======================================
TTT ttt = new TTT (); // 인스턴스이기에 객체 생성
System.out.println("1. ttt : " + ttt);
// 출력 : 1. ttt : no_argument_no_return.TTT@63961c42
ttt.instanceMethod(23);
// 출력 : no_argument_no_return.TTT@6b884d57
// 출력 결과가 같은 것을 알 수 있다.
// 람다식의 내부에서 this는 람다식이 만든 익명구현객체의 주고를 가지고 있지 않고 (***)
// 오히려 람다식을 포함하고 있는 바깥타입의 객체의 주소를 가지고 있다!!
// 그렇기에 ttt클래스에 있는 필드를 람다식 블록 내에서 사용하는 것이 가능하다.
// + 익명구현객체는 힙영역에 생성된다.
// 출력 :
// 1. ttt : no_argument_no_return.TTT@63961c42
// 2. no_argument_no_return.TTT@63961c42
// 3. name : Yoseph
// 4. age : 23
// ======================================
fi = new MyfunctionalInterface4() {
@Override
public int method4(int age) {
System.out.println(this);
return 1000;
}
};
fi.method4(23); // 출력 : no_argument_no_return.MyfunctionalInterfaceExampel4$1@63961c42
} // main
} // end class
05. 람다식 - 클로져
package no_argument_no_return;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class TTT {
String name = "Yoseph";
int age = 23;
private void instanceMethod2() {;;} // instanceMethod2
public void instanceMethod(int param) { // 4th case - 리턴타입과 매개변수가 있는 버전
// final int temp = 333;
int temp = 333;
// param = 23333; // 람다 내부에서 사용하는 지역변수는 자동으로 final이 붙여지기에 값을 바꿀 수가 없다. ( 신 버전 )
MyfunctionalInterface4 fi = a -> {
System.out.println("2. " + this); // 여기서 this는 람다식을 가르치지 않고 ttt를 의미하기에 name과 age를 사용할 수 있다!! (**)
System.out.println("3. name : " + this.name);
System.out.println("4. age : " + this.age);
this.instanceMethod2(); // 메소드도 사용이 가능하다!
System.out.println("5. param : " + param);
System.out.println("6. temp : " + temp);
return 777;
}; // 람다식
// name이나 age는 블록이 끝나게 되면 파괴되지만,
// 람다식을 통해서 만들어지는 익명구현객체는 힙영역에서 파괴되지 않고 살아있는데
// 그렇게 되면 System.out.println("3. name : " + this.name);와 같이 필드를 가지고 왔을 경우
// 파괴된 시점이 달라서 예외가 발생될 수 있다. - - - > 이러한 문제를 "클로져"라고 한다.
// 정리 :
// 생명주기가 다른 지역변수를 익명객체가 사용한 경우,
// 먼저 파괴되는 지역변수의 값을 익명객체가 결정할 수 없는 상태에 빠지는데
// 이 상태를 "Closure(클로져)"라고 한다!!
// 이때 final을 붙여 상수로 만들게 되면, clazz객체가 저장되는 메소드영역에 저장되기 때문에 생명이 길어지게 된다.
// 이를 통해서 클로져 문제를 해결하였다. ( 구 버전 )
// 어? 그런데 그러면 변수의 값을 바꿀 수가 없잖아!!
// 람다 내부에서 사용한 변수는 컴파일러가 실행할 때 자동으로 final을 붙여준다. ( 신 버전 )
// 그렇다 해도, 그냥 람다 내부에서는 지역변수 사용하는 것을 최대한 지양하는 것이 좋다.
// 또한 람다식은 클래스 내부에서 바로 사용되는 것이 아니라, 메소드 블록 내에서만 사용이 가능하다!!
fi.method4(23); // 오버 라이딩하도록 작성해야 한다!!(**)
} // instanceMethod
} // end class
06. 람다식 연습
package sample;
public class Sample1 {
public static void main(String [] args) { // 람다식 연습 1
Runnable task = null;
// Runnable은 java.lang에 있는 @FunctionalInterface이기에 활용이 가능하다.
// public abstract void run();
task = () -> System.out.println("실행이 되고 있습니다.");
task.run();
// ===============================================================
I1 i1;
// public abstract void method1 ( String name, int age );
i1 = (n, a) -> System.out.println("이름은 " + n + "이며, 나이는 " + a + "입니다."); // 변수명을 간결하게 바꿀수도 있다.
i1.method1("Yoseph", 23);
// ===============================================================
I2 i2;
// public abstract double method2 ( String name, int age ); // 자동형변환을 생각해야 한다.
i2 = (n, a) -> {
System.out.println("이름은 " + n + "이며, 나이는 " + a + "입니다.");
return (double)a; };
i2.method2("Yoseph", 23);
} // main
} // end class
07. Consumer 함수적 인터페이스
package consumer;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.ObjIntConsumer;
// 데이터를 변수로 받고 돌려주는 것은 없는 데이터 소비로직을 사용 ( 규격화 )
public class ConsumerExample {
public static void main(String [] args) {
// void accept(T t);
Consumer<String> consumer = t -> System.out.println(t + "8"); // import문 사용!!! - 다형성 1
consumer.accept("java"); // 다형성 2
// ===================================================================
// void accept(T t, U u);
BiConsumer<String, String> bigConsumer = (t,u) -> System.out.println(t+u);
bigConsumer.accept("java", "8");
// ===================================================================
// void accept(double value);
DoubleConsumer doubleConsumer = d -> System.out.println("java" + d);
doubleConsumer.accept(8.0);
// ===================================================================
// void accept(T t, int value);
ObjIntConsumer<String> objIntConsumer = (t,i) -> System.out.println(t+i); // 람다식에서는 메소드명을 사용하지 않는다.
objIntConsumer.accept("java", 8);
}// main
} // end class
08. Supplier 함수적 인터페이스
package supplier;
import java.util.function.IntSupplier;
public class SupplierExample {
public static void main (String [] args) {
// int getAsInt();
IntSupplier intsupplier = () -> {
int num = (int) ( Math.random() * 6) +1;
return num;
}; // 람다식
int num = intsupplier.getAsInt();
System.out.println("눈의 수 : "+ num);
} // main
} // end class
09. Function 함수적 인터페이스
package function;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.function.ToIntFunction;
public class FunctionExample1 { // function (****)
// 1. 여러 요소를 목록으로 보관하는 타입 List
public static List<Student> list = Arrays.asList( // Import문!!
new Student("홍길동",90,63),
new Student("신용권",95,93)
);
// ===================================================================================
// 2. Function 인터페이스의 목적 : 매핑 ( A -> B )
public static void printString(Function<Student, String> function) {
// R apply(T t);
for (Student student : list ) {
System.out.print(function.apply(student)+ " ");
} // enhanced for
System.out.println();
} // printString
// ===================================================================================
public static void printInt(ToIntFunction<Student> function) {
// int applyAsInt(T value);
for (Student student : list ) {
System.out.print(function.applyAsInt(student)+ " ");
} // enhanced for
System.out.println();
} // printInt
// ===================================================================================
public static void main (String [] args) {
System.out.println("[ 학생 이름 ] ");
FunctionExample1.printString(
t -> t.getName() // 람다식으로 익명구현객체를 생성하여 전달 ( 전달 인자 )
); // 람다식
// ===================================================================================
System.out.println(" [ 영어 점수 ] "); // Getter 메소드 활용
FunctionExample1.printInt(t -> t.getEnglishScore());
// ===================================================================================
// int applyAsInt(T value); // 람다식 - 익명구현객체 활용
ToIntFunction<Student> mapping = v -> v.getEnglishScore();
for( Student student : list ) {
int mathScore = mapping.applyAsInt(student);
System.out.println(mathScore);
// System.out.println(mapping.applyAsInt(student)); // 이렇게 작성할 수도 있다. 이 방법을 권장한다.
} // enhanced for
// ===================================================================================
System.out.println(" [ 수학 점수 ] "); // Setter 활용
FunctionExample1.printInt(t -> t.getMathScore());
} // main
} // end class
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 22일차 - 컬렉션 프레임워크 (0) | 2022.03.29 |
---|---|
KH 21일차 - 함수적 인터페이스 및 컬렉션 프레임 워크 (0) | 2022.03.28 |
KH 19일차 - 제네릭 타입 (0) | 2022.03.25 |
KH 18일차 - 예외처리코드 및 제네릭 (0) | 2022.03.24 |
KH 17일차 - 인터페이스와 예외처리 (0) | 2022.03.22 |