반응형
✔️ 람다식(Lambda expression)
- 메서드를 하나의 식으로 표현한 것으로 익명 클래스, 익명 함수라고도 하며 1급 객체로 취급 받는다
- 익명 함수(anonymous function) : 메소드의 이름과 반환값이 없고 단 하나의 객체만을 생성할 수 있는 클래스
- 1급 객체(First-class Object)
- 변수나 데이터 구조 안에 담을 수 있다
- 파라미터로 전달할 수 있다
- 반환값으로 사용할 수 있다
- 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다
- 장점
- 코드를 간결하게 만들 수 있고 식에 개발자의 의도가 명확히 드러나 가독성이 향상된다
- 함수를 만드는 과정없이 한 번에 처리할 수 있기에 코딩하는 시간이 줄어든다
- 병렬 처리, 이벤트 처리 등에 용이하다
- 단점
- 람다로 만든 익명 함수는 재사용이 불가능하다
- 디버깅이 어렵다
- 함수를 중복 생성 할 수 있으므로 남발하면 코드가 지저분해 질 수 있다
- 재귀에 부적합하다
📌 람다식 작성규칙
- 공통
- 이름과 반환타입은 작성하지 않는다
- Parameter
- 추론이 가능한 매개변수의 타입은 생략할 수 있다 (두 개 이상인 경우 몇개만 생략하는건 불가능)
- 선언된 매개변수가 하나인 경우 타입+괄호()를 생략할 수 있다
- Body {}
- return문 대신 식(expression)으로 대체할 수 있다 (식의 끝에는 세미콜론(;)을 붙이지 않는다)
- 괄호{} 안의 문장이 하나인 경우 생략할 수 있다 (return문인 경우엔 같이 생략하거나 괄호{}로 감싸야한다)
📌 람다식 작성방법
- 람다식은 함수형 인터페이스에 선언된 추상 메서드를 익명 클래스로 구현해주는 것이다
매개변수가 없고, 리턴값이 없는 람다식
@FunctionalInterface
interface Function {
void abstractMethod(); //매개변수 X 반환타입 X
}
class Ex {
public static void main(String[] args) {
//함수형 인터페이스 선언
Function func;
//{} 뒤에 세미콜론 붙여야 함
func = () -> { System.out.println("매개변수가 없고 리턴값이 없는 람다식"); };
func.abstractMethod();
//{}의 실행코드가 1줄이면 생략가능
func = () -> System.out.println("매개변수가 없고 리턴값이 없는 1줄짜리 람다식");
func.abstractMethod();
}
}
매개변수가 있고, 리턴값이 없는 람다식
@FunctionalInterface
interface Function {
void abstractMethod(String str); //매개변수 O 반환타입 X
}
class Ex {
public static void main(String[] args) {
//함수형 인터페이스와 매개변수로 쓰일 변수 선언
Function func;
//{} 뒤에 세미콜론 붙여야 함
func = (a) -> { System.out.println(a + "매개변수가 있고 리턴값이 없는 람다식"); };
func.abstractMethod("[매개변수]");
//{}의 실행코드가 1줄이면 생략가능
func = (a) -> System.out.println(a + "매개변수가 있고 리턴값이 없는 1줄짜리 람다식");
func.abstractMethod("[매개변수]");
}
}
매개변수가 없고, 리턴값이 있는 람다식
@FunctionalInterface
interface Function {
String abstractMethod(); //매개변수 X 반환타입 O
}
class Ex {
public static void main(String[] args) {
//함수형 인터페이스 선언
Function func;
//{} 뒤에 세미콜론 붙여야 함
func = () -> { return "매개변수가 없고 리턴값이 있는 람다식"; };
System.out.println(func.abstractMethod());
//{}의 실행코드가 return만 있는 경우 생략가능
func = () -> "매개변수가 없고 리턴값이 있으면서 return만 있는 람다식";
System.out.println(func.abstractMethod());
}
}
매개변수가 있고, 리턴값이 있는 람다식
@FunctionalInterface
interface Function {
String abstractMethod(String str); //매개변수 O 반환타입 O
}
class Ex {
public static void main(String[] args) {
//함수형 인터페이스 선언
Function func;
//{} 뒤에 세미콜론 붙여야 함
func = (a) -> { return a + "매개변수가 있고 리턴값이 있는 람다식"; };
System.out.println(func.abstractMethod("[매개변수]"));
//{}의 실행코드가 return만 있는 경우 생략가능
func = (a) -> a + "매개변수가 있고 리턴값이 있으면서 return만 있는 람다식";
System.out.println(func.abstractMethod("[매개변수]"));
}
}
📌 람다식의 타입과 형변환
- 람다식의 타입은 익명 객체로 컴파일러가 임의로 이름을 정하기 때문에 알 수 없다
- 람다식은 오직 함수형 인터페이스 타입으로만 형변환이 가능하며 이는 생략이 가능하다
@FunctionalInterface
interface MyFunction { //함수형 인터페이스 생성
void MyMethod(); //추상 메서드 생성
}
class Ex {
public static void main(String[] args) {
MyFunction f = (MyFunction) () -> {}; //람다식을 MyFunction 타입으로 형변환해서 참조
MyFunction f2 = () -> {}; //형변환 생략해서 참조
Object obj = (Object) (MyFunction) () -> {}; //다른 타입으로 형변환 하려면 2번 형변환
}
}
📌 참조변수, 매개변수, 반환값을 통해 람다식 주고 받기
- 람다식을 주고 받기 위해선 어떻게든 함수형 인터페이스 타입의 참조변수를 통해 추상 메서드를 호출해야 한다
@FunctionalInterface
interface MyFunction { //함수형 인터페이스 생성
void MyMethod(); //추상 메서드 생성
}
class Ex {
public static void main(String[] args) {
//f에 참조된 람다식을 MyFunction의 MyMethod로 구현
MyFunction f = () -> System.out.println("Hello Function");
f.MyMethod();
//람다식을 aMethod에게 MyFunction 타입의 인자로 전달해 MyFunction의 MyMethod로 구현
aMethod(() -> System.out.println("Hello Method"));
//람다식을 반환 받아 f2로 참조하고 MyFunction의 MyMethod로 구현
MyFunction f2 = returnMethod();
f2.MyMethod();
}
static void aMethod(MyFunction f) { //람다식을 전달받을 메서드 생성
f.MyMethod(); //함수형 인터페이스 타입으로 전달받은 인자의 추상 메서드를 호출
}
static MyFunction returnMethod() { //람다식을 반환할 메서드 생성
return () -> { System.out.println("Hello Return"); };
}
}
📌 람다식으로 외부 변수 참조
- 람다식 내에서 참조하는 지역변수는 final이 붙지 않아도 상수로 간주하므로 값을 변경할 수 없다
- 외부 지역변수와 람다식의 매개변수는 중복이 불가능하다
@FunctionalInterface
interface MyFunction { //함수형 인터페이스 생성
void MyMethod(); //추상 메서드 생성
}
class Outer {
int val = 10;
class Inner {
int val = 20;
void method(int i) { //파라미터 final로 간주해 변경불가
int val = 30; //지역변수 final로 간주해 변경불가
MyFunction f = () -> {
System.out.println(i);
System.out.println(val);
System.out.println(this.val);
System.out.println(Outer.this.val);
};
f.MyMethod();
}
}
}
class Ex {
public static void main(String[] args) {
Outer outer = new Outer(); //Outer 인스턴스화
Outer.Inner inner = outer.new Inner(); //Inner 인스턴스화
inner.method(100);
}
}
📌 람다식으로 메서드 참조
class Calculator {
static int staticSum(int x, int y) {
return x + y;
}
int instanceSum(int x, int y) {
return x + y;
}
}
class Student {
String name;
Student(String name) {
this.name = name;
}
}
class Ex {
public static void main(String[] args) {
IntBinaryOperator operator;
//static 메서드
operator = (x, y) -> Calculator.staticSum(x, y); //기존
System.out.println(operator.applyAsInt(1, 3));
operator = Calculator::staticSum; //메서드 참조
System.out.println(operator.applyAsInt(2, 5));
//instance 메서드
Calculator cal = new Calculator();
operator = (x, y) -> cal.instanceSum(x, y); //기존
System.out.println(operator.applyAsInt(4, 3));
operator = cal::instanceSum; //메서드 참조
System.out.println(operator.applyAsInt(3, 7));
//생성자
Function<String, Student> f = (name) -> new Student(name); //기존
System.out.println(f.apply("이름").name);
Function<String, Student> f1 = Student::new; //메서드 참조
System.out.println(f1.apply("이름").name);
//매개변수의 메서드 참조
ToIntBiFunction<String, String> bf = String::compareToIgnoreCase;
System.out.println(bf.applyAsInt("test", "TEST") == 0 ? "true" : "false");
}
}
✔️ 함수형 인터페이스(Functional Interface)
- jdk 8부터 인터페이스의 변경점
- ~ jdk 7 : 추상 메서드, 상수만 정의 가능
- jdk 8 ~ : 추상 메서드, 상수 + default 메서드, static 메서드 정의 가능
- jdk 8부터 추가된 람다식은 추상 메서드가 오직 하나인 함수형 인터페이스를 사용한다 (default나 static 상관 X)
- @FunctionalInterface 어노테이션은 선택사항으로 추상 메서드가 1개인지 컴파일러가 확인해준다
📌 java.util.function 패키지
- 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 놓은 패키지이다
- 이 패키지를 활용하면 메서드 이름이 통일되고 재사용성이나 유지보수 측면에서 용이해진다
주요 함수형 인터페이스
- Runnable
@FunctionalInterface
interface Runnable {
void run();
}
class Ex {
public static void main(String[] args) {
Runnable r = () -> System.out.println("run");
r.run(); //run 출력
}
}
- Supplier<T>
@FunctionalInterface
interface Supplier<T> {
T get();
}
class Ex {
public static void main(String[] args) {
Supplier<String> s = () -> "Supplier";
System.out.println(s.get()); //Supplier 출력
}
}
- Consumer<T>
@FunctionalInterface
interface Consumer<T> {
void accept(T t);
}
class Ex {
public static void main(String[] args) {
Consumer<String> c = (str) -> System.out.println(str);
c.accept("Consumer"); //Consumer 출력
}
}
- Function<T, R>
@FunctionalInterface
interface Function<T, R> {
R apply(T t);
}
class Ex {
public static void main(String[] args) {
Function<Integer, String> f = i -> "입력 받은 숫자 : " + i;
System.out.println(f.apply(3)); //입력 받은 숫자 : 3 출력
}
}
- Predicate<T>
@FunctionalInterface
interface Predicate<T> {
boolean test(T t);
}
class Ex {
public static void main(String[] args) {
Predicate<String> p = (str) -> str == "test";
System.out.println(p.test("test")); //true 출력
}
}
매개변수가 두 개인 함수형 인터페이스
- BiConsumer<T, U>
@FunctionalInterface
interface BiConsumer<T, U> {
void accept(T t, U u);
}
class Ex {
public static void main(String[] args) {
BiConsumer<String, Integer> bc = (String str, Integer itg) ->
System.out.println("String : " + str + ", Integer : " + itg);
bc.accept("테스트", 3); //String : 테스트, Integer : 3 출력
}
}
- BiPredicate<T, U>
@FunctionalInterface
interface BiPredicate<T, U> {
boolean test(T t, U u);
}
class Ex {
public static void main(String[] args) {
BiPredicate<Integer, Integer> bp = (Integer itg, Integer itg2) -> itg == itg2;
System.out.println(bp.test(1, 1)); //true 출력
}
}
- BiFunction<T, U, R>
@FunctionalInterface
interface BiFunction<T, U, R> {
R apply(T t, U u);
}
class Ex {
public static void main(String[] args) {
BiFunction<Integer, Integer, String> bf = (Integer itg, Integer itg2) ->
itg + "+" + itg2 + "=" + (itg+itg2);
System.out.println(bf.apply(3, 5)); //3+5=8 출력
}
}
UnaryOperator와 BinaryOperator
- Function의 자손들로 매개변수의 타입과 반환타입이 일치하는 점을 제외하곤 Function과 같다
컬렉션 프레임워크와 함수형 인터페이스
- 컬렉션 프레임워크 인터페이스에 추가된 디폴트 메서드 중 함수형 인터페이스를 사용하는 메서드의 목록이다
기본형 특화 함수형 인터페이스
- 함수형 인터페이스는 제네릭 타입을 사용하기 때문에 오토박싱을 통해 래퍼 클래스를 이용한다
- 하지만 기본형을 사용하는게 훨씬 효율적이므로 이 인터페이스를 통해 박싱의 한 과정을 덜어준다
함수형 인터페이스의 합성
//f.andthen(g) : f -> g
default <V> Function<T,V> andThen(Function<? super R,? extends V> after)
//f.compose(g) : g -> f
default <V> Function<V,R> compose(Function<? super V,? extends T> before)
//f.identity() : f -> f
static <T> Function<T,T> identity() //항등 함수
함수형 인터페이스의 논리연산
//And
default Predicate<T> and(Predicate<? super T> other)
//Or
default Predicate<T> or(Predicate<? super T> other)
//Predicate<T> notP = p.negate(); : !p
default Predicate<T> negate()
//boolean result = Predicate.isEqual(str1).test(str2); : str == str
static <T> Predicate<T> isEqual(Object targetRef)
📄 참고
자바의 정석 3rd Edition
반응형
'📚 Study > JAVA' 카테고리의 다른 글
[JAVA] 입출력과 스트림 (0) | 2022.05.13 |
---|---|
[JAVA] 스트림(Stream) (0) | 2022.05.12 |
[JAVA] 스레드 (0) | 2022.05.11 |
[JAVA] 어노테이션(Annotation) (0) | 2022.05.09 |
[JAVA] 열거형(enums) (0) | 2022.05.09 |