2010년대 병렬 처리, 이벤트 지향 프로그래밍에 적합한 함수적 프로그래밍이 다시 부각되면서 자바도 새로운 개편을 하게 된다.
그 중에 하나의 개념이 람다.
람다식은 함수(메서드)를 간단한 식(expression)으로 표현하는 방법으로 익명함수를 생성하기 위한 식 이라고도 할 수 있다.
자바에서는 하나의 객체로 취급이 된다.
자바에서 수용한 이유 - 자바8부터 람다식 지원
객체지향언어 - 람다식 (=함수를 간단하게 표현한 형태)
Runnable 인터페이스의 익명 구현 객체를 생성하는 전형적인 코드와 람다식 코드
// 전형적인 코드
Runnable runnable = new Runnable() { // 익명 구현 객체
public void run() {...}
}
//람다식 표현 (매개변수) -> {실행코드}
Runnable runnable = () -> {...}; // () 이후 람다식
(타입 매개변수, ...) -> { 실행문; ...; }
(타입 매개변수, …)는 오른쪽 중괄호 {} 블록을 실행하기 위해 필요한 값을 제공하는 역할을 함.
매개변수 이름은 개발자가 자유롭게 설정.
→ 해당 기호는 매개변수를 이용하여 중괄호 {}를 실행한다는 의미.
// int 매개변수 a의 값을 콘솔에 출력하기 위해 지정한 람다식 그리고 축약 과정
(int a) -> { System.out.println(a);}
// 1. 매개변수 타입은 런타입 시 대입되는 값에 따라 자동으로 인식될 수 있기 때문에 람다식에서는 매개 변수의 타입을 일반적으로 언급하지 않음.
(a) -> {System.out.println(a);}
// 2. 하나의 매개변수만 있다면 괄호 생략 가능, 하나의 실행문이 있다면 중괄호도 생략 가능.
a -> System.out.println(a)
// 매개변수가 없다면 람다식에서 매개변수 자리가 없어지므로 빈괄호 반드시 사용
() -> {실행문 ...}
// 중괄호 안의 실행문을 실행하고 결과값을 리턴해야한다면
(x, y) -> {return x + y;}; // 해당 식을 더 축약한다면 (x , y) -> x + y;
int max(int a, int b) {
return a > b ? a : b;
}
// (a, b) -> a > b ? a : b;
int print(String name, int i) {
System.out.println(name+"="+i);
}
//(name, i) -> System.out.println(name + "=" + i);
int square(int x) {
return x * x;
}
// x -> x * x;
함수형 인터페이스는 단 하나의 추상 메서드만 선언된 인터페이스를 이야기한다.
아래 gif를 보면 method()라는 기존 메서드가 있는데 otherMethod를 선언하니 발생하는 오류.
@FunctionalInterface
public interface MyFuntionalInterface {
public void method();
public void method2(); // 컴파일 오류 발생
}
@FunctionalInterface
public interface MyFuntionalInterface {
public void method();
}
해당 인터페이스를 타겟 타입으로 갖는 람다식의 형태
MyFunctionalInterface finter = () -> {...}
// 람다식이 대입된 인터페이스의 참조변수를 호출하는 방법
finter.method();
//매개변수가 있고 리턴값이 없는 추상 메소드를 가진 함수형 인터페이스
@FunctionalInterface
public interface MyFuntionalInterface2 {
void method(int x);
}
MyFunctionalInterface finter = (x) -> { ... };
// 해당 메서드 호출 실행문
finter.method(5);
// 리턴값이 있는 람다식
@FunctionalInterface
public interface MyFuntionalInterface3 {
int method(int x, int y);
}
MyFunctionalInterface fi = (x, y) -> {...; retrun 값; }
//만약 중괄호에 return문만 있고 그 뒤에 연산식이나 메서드 호출이 오는 경우라면 다음과 같이 작성 가능
MyFunctionalInterface fi = (x, y) -> {return x + y;} == MyFunctionalInterface fi = (x, y) -> x + y;
함수형 인터페이스는 재사용 가능한 코드를 만들 수 있는 장점이 있다.
반복적인 조건이나 동작을 매번 새롭게 클래스 혹은 메소드를 만들어서 작성하는 대신, 필요한 부분만 매개변수화 하여 사용할 수 있다.
또한 간결하고 직관적인 코드를 작성할 수 있다.
// 람다식을 사용하지 않음
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Hello from a thread!");
}
};
new Thread(task).start();
// 람다식을 이용한 방식
Runnable task = () -> System.out.println("Hello from a thread!");
new Thread(task).start();
JDK에서 제공해주는 함수형 인터페이스
package java.lang;
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
아래 예시처럼 매개변수와 리턴값(타입)이 없는 람다식을 참조 가능하다.
Runnable r = () -> System.out.println("출력문 테스트");
r.run(); // "출력문 테스트"
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
아래 예시처럼 매개변수가 없고, 리턴타입이 있는 람다식을 참조 가능하다.
Supplier<String> s = **() -> "리턴되는 값";**
String result = s.get();
System.out.println(result); // "리턴되는 값"
package java.util.function;
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Consumer<String> c = (a) -> System.out.println(a);
c.accept("consumer");
package java.util.function;
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Function<Integer, String> f = a -> String.valueOf(a);
Function<String, Integer> f2 = b -> {
return Integer.valueOf(b) + 100;
};
package java.util.function;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
하나의 매개변수, 리턴타입이 boolean인 람다식을 참조한다.
Predicate<String> isEmptyStr = s -> s.length()==0;
String str = "";
if (isEmptyStr.test(str)) { // if(s.length()==0)
System.out.println("This is an empty String.")
}
// 스트림에서 filter메소드 내부에는 Predicate 타입이 들어감
List<Integer> list = Arrays.asList(1,2,3,4,5);
list.stream()
.filter(x -> x%2==0)
.collect(Collectors.toList()); // [2,4]
package chap12.custom;
@FunctionalInterface
public interface MyFuntionalInterface {
void run();
}
package chap12.custom;
@FunctionalInterface
public interface MyFuntionalInterface2 {
void method(int x);
}
package chap12.custom;
// 매개변수와 리턴타입이 있는 추상 메서드
@FunctionalInterface
public interface MyFuntionalInterface3 {
int method(int a);
}
package chap12.custom;
// 매개변수와 리턴타입이 있는 추상 메서드
@FunctionalInterface
public interface MyFuntionalInterface4 {
int method(int a, int b);
}
package chap12.custom;
// 매개변수와 리턴타입이 있는 추상 메서드
public class MyFuntionalInterface4Impl implements MyFuntionalInterface4 {
@Override
public int method(int a, int b){
return a + b;
};
}
package chap12;
import chap12.custom.MyFuntionalInterface;
import chap12.custom.MyFuntionalInterface2;
import chap12.custom.MyFuntionalInterface3;
import chap12.custom.MyFuntionalInterface4;
public class LamdaExample {
public static void main(String[] args) {
// Runnable runnable = new Runnable() {
// @Override
// public void run() {
// System.out.println("익명 구현 객체의 메서드")
// }
// }; // 익명 구현 객체 (구현체의 이름이 익명)
// 람다식 : 메소드를 간결하게 표현한 식
// 자바에서는 람다식을 객체로 취급할 수 있음 (Java 8 부터)
Runnable runnable = () -> System.out.println("익명 구현 객체의 메서드"); // 람다식
MyFuntionalInterface inter = new MyFuntionalInterface() {
@Override
public void run() {
System.out.println("Hello Lambda");
}
}; // 익명 구현 객체
inter.run();
MyFuntionalInterface inter2 = () -> System.out.println("Hello Lambda");
inter2.run();
// 매개변수가 있는 람다식
MyFuntionalInterface2 inter3 = (x) -> System.out.println(x);
inter3.method(12);
// 매개변수와 리턴타입이 있는 람다식 (매개변수 1개)
MyFuntionalInterface3 inter4 = (i) -> i*2;
int result = inter4.method(12);
System.out.println("result: " + result);
// 매개변수와 리턴타입이 있는 람다식 (매개변수 2개)
MyFuntionalInterface4 inter5 = (i, j) -> {
System.out.println("매개변수가 2개인 method");
return i * j;
};
int result02 = inter5.method(12, 5);
System.out.println("result02: " + result02); // 60
MyFuntionalInterface4 inter6 = (i, j) -> i + j; // 익명 구현 객체
int result03 = inter6.method(12, 5);
}
}
package chap12;
import java.util.function.*;
public class LamdaExample2 {
public static void main(String[] args) {
Supplier<String> supplier = () -> "str";
String getStr = supplier.get();
System.out.println(getStr);
IntSupplier supplier2 = () -> 310;
int getInt = supplier2.getAsInt();
System.out.println(getInt);
//Consumer
Consumer<String> consumer = a -> System.out.println(a);
consumer.accept("nwjns");
//Function
Function<Integer, String> function = a -> String.valueOf(a) + ":문자열"; // == x + "문자열"
System.out.println(function.apply(3458));
Function<String, Double> function2 = x -> Double.valueOf(x) * 1.2;
Double resultFunction = function2.apply("56.7");
System.out.println("String to Double: " + resultFunction);
// Predicate 구현 객체를 람다식으로 작성
Predicate<String> isEmpty = x -> x.length() == 0; // == Predicate<String> isEmpty = String::isEmpty;
System.out.println("빈값 체크 : " + isEmpty.test(""));
System.out.println("빈값 체크 : " + isEmpty.test("123433"));
Predicate<Integer> predicate = x -> x % 2 == 0;
System.out.println("짝수 체크 : " + predicate.test(0));
System.out.println("짝수 체크 : " + predicate.test(19));
}
}
Method Reference, 말 그대로 메서드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내, 람다식에서 불필요한 매개 변수를 제거하는 것이 목적.
종류 | 람다 | 메소드 참조 |
---|---|---|
정적(static) 메소드 참조 | (x) → ClassName.method(x) | ClassName::method |
인스턴스 메소드 참조 | (obj, x) → obj.method(x) | ClassName::method |
// 두 개의 값을 받아 큰 수를 리턴하는 Math 클래스의 max() 정적 메소드를 호출하는 람다식
(left, right) -> Math.max(left, right) // == Math::max
메소드 참조도 람다식과 마찬가지로 인터페이스의 익명 구현 객체로 생성되므로 타겟 타입인 인터페이스의 추상 메소드가 어떤 매개 변수를 가지고, 리턴 타입이 무엇인가에 따라 달라진다.
// IntBinaryOperator 인터페이스는 두 개의 int 매개값을 받아 int 값을 리턴하므로 Math::max 메소드 참조를 대입할 수 있다.
@FunctionalInterface
public interface IntBinaryOperator {
int applyAsInt(int left, int right);
}
// Math.max 메서드
public static int max(int a, int b){
return Math.max(a, b);
}
// 람다식을 '메서드 참조'로 변경 과정
IntBinaryOperator operator = (a, b) -> Math.max(a, b); // 1단계
IntBinaryOperator operator = Math::max; // 2단계
메서드 참조는 정적 메서드 또는 인스턴스 메서드를 참조할 수 있고, 생성자 참조도 가능.