[Java 자바] 14. 람다식
14-1. 람다식이란?
- 익명 함수를 생성하기 위한 식으로 객체 지향 언어보다는 함수 지향 언어에 가까움
- "(매개변수) -> {실행코드} " 형태로 작성
// Runnable 인터페이스의 익명 구현 객체를 생성하는 코드
Runnable runnable = new Runnable() {
public void run() {...}
}
// 람다식을 이용해 표현할 경우
Runnable runnable = () -> {...};
14-2. 람다식 기본 문법
// 기본 형태
(타입 매개변수, ...) -> { 실행문; ... }
// int 매개 변수 a 값을 콘솔에 출력할 때
(int a) -> {System.out.println(a);}
// 매개 변수 타입은 런타임 시 대입값에 따라 자동으로 인식될 수 있어,
// 람다식에서는 일반적으로 매개변수의 타입을 언급하지 않음
(a) -> {System.out.println(a);}
// 하나의 매개변수만 있다면 괄호() 생략 가능
// 하나의 실행문만 있다면 중괄호{} 생략 가능
a -> System.out.println(a);
// 매개변수가 없을 경우 빈 괄호 사용
() -> {실행문, ...}
// 중괄호를 실행하고 결과값을 리턴해야 할 경우
(x, y) -> { return x + y; }
// 중괄호에 return문만 있을 경우, return과 중괄호 생략 가능
(x, y) -> x + y
14-3. 타겟 타입과 함수적 인터페이스
- 람다식은 단순히 메소드를 선언하는 것이 아닌, 메소드를 가지고 있는 객체를 생성
- 람다식은 인터페이스 변수에 대입되어, 인터페이스의 익명 구현 객체를 생성함
- 람다식의 타겟 타입: 람다식이 대입될 인터페이스
인터페이스 변수 = 람다식;
14-3-1. 함수적 인터페이스 (@FunctionalInterface)
- 람다식은 하나의 메소드를 정의하기 때문에 두 개 이상의 추상 메소드가 선언된 인터페이스는 람다식을 이용할 수 없음
=> 하나의 추상 메소드가 선언된 인터페이스만이 람다식의 타겟 타입이 될 수 있으며, 이러한 인터페이스를 함수적 인터페이스라 함
- @FunctionalInterface 어노테이션을 통해 두 개 이상의 추상 메소드가 선언되지 않도록 컴파일러가 체킹해줌
@FunctionalInterface
public interface MyFunctionalInterface {
public void method();
public void otherMethod(); // 컴파일 오류
}
14-3-2. 매개 변수와 리턴값이 없는 람다식
// MyFunctionalInterface.java
@FunctionalInterface
public interface MyFunctionalInterface {
public void method();
}
// MyFunctionalInterfaceTest.java
public class MyFunctionalInterfaceTest {
public static void main(String[] args) {
MyFunctionalInterface fi;
// method()의 매개변수가 없으므로 람다식의 매개변수도 없음
fi = () -> {
String str = "method call1";
System.out.println(str);
};
fi.method();
fi = () -> { System.out.println("method call2"); };
fi.method();
fi = () -> System.out.println("method call3");
fi.method();
}
}
14-3-3. 매개 변수가 있고 리턴값이 없는 람다식
// MyFunctionalInterface.java
@FunctionalInterface
public interface MyFunctionalInterface {
public void method(int x);
}
// MyFunctionalInterfaceTest.java
public class MyFunctionalInterfaceTest {
public static void main(String[] args) {
MyFunctionalInterface fi;
fi = (x) -> {
int result = x * 5;
System.out.println(result);
};
fi.method(2);
fi = (x) -> {System.out.println(x*5);};
fi.method(2);
fi = x -> System.out.println(x*5);
fi.method(2);
}
}
14-3-4. 매개 변수가 있고 리턴값이 있는 람다식
// MyFunctionalInterface.java
@FunctionalInterface
public interface MyFunctionalInterface {
public int method(int x, int y);
}
// MyFunctionalInterfaceTest.java
public class MyFunctionalInterfaceTest {
public static void main(String[] args) {
MyFunctionalInterface fi;
fi = (x, y) -> {
int result = x + y;
return result;
};
System.out.println(fi.method(2, 5));
fi = (x, y) -> { return x + y; };
System.out.println(fi.method(2, 5));
fi = (x, y) -> x + y;
System.out.println(fi.method(2, 5));
fi = (x, y) -> sum(x, y);
System.out.println(fi.method(2, 5));
}
public static int sum (int x, int y) {
return (x + y);
}
}
14-4. 클래스 멤버와 로컬 변수 사용
14-4-1. 클래스 멤버 사용
- 람다식 실행 블록에서는 클래스의 멤버인 필드와 메소드를 제약 사항 없이 사용 가능
- 단, this 키워드를 사용할 경우 익명 객체의 참조가 아닌 람다식을 실행한 객체의 참조이므로 주의
// MyFunctionalInterface.java
public interface MyFunctionalInterface {
public void method();
}
// UsingThis.java
public class UsingThis {
public int outterField = 10;
class Inner {
int innerField = 20;
void method() {
// 람다식
MyFunctionalInterface fi = () -> {
System.out.println("outterField: " + outterField);
// 바깥 객체의 참조를 얻기 위해서는 클래스명.this 사용
System.out.println("outterField: " + UsingThis.this.outterField + "\n");
System.out.println("innerField: " + innerField);
System.out.println("innerField: " + this.innerField + "\n");
};
fi.method();
}
}
}
// UsingThisTest.java
public class UsingThisTest {
public static void main(String[] args) {
UsingThis usingThis = new UsingThis();
UsingThis.Inner inner = usingThis.new Inner();
inner.method();
}
}
14-4-2. 로컬 변수 사용
- 람다식은 메소드 내부에서 주로 작성되기에 로컬 익명 구현 객체를 생성시킨다고 할 수 있음
- 메소드의 매개변수 또는 로컬 변수를 사용하려면 이 두 변수는 final 특성을 가져야 함
// MyFunctionalInterface.java
public interface MyFunctionalInterFace {
public void method();
}
// UsingLocalVariable.java
public class UsingLocalVariable {
void method(int arg) { // arg는 final특성을 가짐
int localVar = 40; // localVar은 final 특성을 가짐
// arg = 31; // final 특징 때문에 수정 불가
// localVar = 41; // final 특징 때문에 수정 불가
// 람다식
MyFunctionalInterface fi = () -> {
System.out.println("arg: " + arg);
System.out.println("localVar: " + localVar + "\n");
};
fi.method();
}
}
//UsingLocalVariableTest.java
public class UsingLocalVariableTest {
public static void main(String[] args) {
UsingLocalVariable ulv = new UsingLocalVariable();
ulv.method(20);
}
}
14-5. 표준 API의 함수적 인터페이스
- 자바 표준 API에서 한 개의 추상 메소드를 가지는 인터페이스는 모두 람다식을 이용해 익명 객체 구현 가능
14-5-1. Consumer 함수적 인터페이스
- 리턴값이 없는 accept() 메소드를 가지고 있음
- accept() 메소드는 매개값을 받아 소비하는 역할만 함 (사용만 할 뿐 리턴값이 없다)
- 매개 변수와 타입에 따라 여러 Consumer 인터페이스가 존재함
인터페이스명 | 추상 메소드 | 설명 |
Consumer<T> | void accept(T t) | 객체 T를 받아 소비 |
BiConsumer<T> | void accept(T t, U u) | 객체 T와 U를 받아 소비 |
DoubleConsumer | void accept(double value) | double 값을 받아 소비 |
IntConsumer | void accept(int value) | int 값을 받아 소비 |
LongConsumer | void accept(long value) | long 값을 받아 소비 |
ObjDoubleConsumer<T> | void accept(T t, double value) | 객체 T와 double 값을 받아 소비 |
ObjIntConsumer<T> | void accept(T t, int value) | 객체 T와 int 값을 받아 소비 |
ObjLongConsumer<T> | void accept(T t, long value) | 객체 T와 long 값을 받아 소비 |
// ConsumerExample.java
public class ConsumerExample {
public static void main(String[] args) {
Consumer<String> consumer = t -> System.out.println(t + "8");
consumer.accept("Java");
}
}
14-5-2. Supplier 함수적 인터페이스
- 매개 변수가 없고 리턴값이 있는 getXXX() 메소드를 가지고 있음
- 메소드는 실행 후 호출한 곳으로 데이터를 리턴(공급)하는 역할을 함
- get() 메소드가 매개값을 가지지 않으므로 람다식도 ()를 사용
인터페이스명 | 추상 메소드 | 설명 |
Supplier<T> | T get() | T 객체를 리턴 |
BooleanSupplier | boolean getAsBoolean() | boolean 값을 리턴 |
DoubleSupplier | double getAsDouble() | doulbe 값을 리턴 |
IntSupplier | int getAsInt() | int 값을 리턴 |
LongSupplier | long getAsLong() | long 값을 리턴 |
// SupplierTest.java
public class SupplierTest {
public static void main(String[] args) {
IntSupplier intSupplier = () -> {
int num = (int) (Math.random() * 6) + 1;
return num;
};
int num = intSupplier.getAsInt();
System.out.println("눈의 수: " + num);
}
}
14-5-3. Function 함수적 인터페이스
- 매개값과 리턴값이 있는 applyXXX() 메소드를 가지고 있음
- 매개값을 리턴값으로 매핑(타입 변환) 하는 역할을 함
// Student.java
public class Student {
private String name;
private int englishScore;
private int mathScore;
public Student(String name, int englishScore, int mathScore) {
this.name = name;
this.englishScore = englishScore;
this.mathScore = mathScore;
}
public String getName() {return name;}
public int getEnglishScore() {return englishScore;}
public int getMathScore() {return mathScore;}
}
// FunctionTest.java
public class FunctionTest {
private static List<Student> list = Arrays.asList(
new Student("a", 90, 96),
new Student("b", 95, 93)
);
public static void printString(Function<Student, String> function) {
for(Student student: list {
System.out.print(function.apply(student) + " ");
}
System.out.println();
}
public static void printInt (ToIntFunction<Student> function) {
for(Student student : list {
System.out.print(function.applyAsInt(student) + " ");
}
System.out.println();
}
public static void main(String[] args) {
System.out.println("[학생 이름]");
printString(t -> t.getName());
System.out.println("[영어 점수]");
printInt(t -> t.getEnglishScore());
System.out.println("[수학 점수]");
printInt(t -> t.getMathScore());
}
}
14-5-4. Operator 함수적 인터페이스
- Function과 동일하게 매개 변수와 리턴값이 있는 applyXXX() 메소드를 가지고 있음
- 하지만 매개값을 이용해 연산을 수행한 후 동일한 타입으로 리턴값을 제공한다는 점에서 차이
// OperatorTest.java
public class OperatorTest {
private static int[] scores = {92, 95, 87};
public static int maxOrMin (IntBinaryOperator operator) {
int result = scores[0];
for(int score = scores) {
result = operator.applyAsInt(result, score);
}
return result;
}
public static void main(String[] args) {
int max = maxOrMin(
(a, b) -> {
if (a >= b) return a;
else return b;
}
);
int min = maxOrMin(
(a, b) -> {
if (a <= b) return a;
else return b;
}
);
System.out.println("최소값 :" + min);
}
}