
함수형 프로그래밍은 자료 처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임의 하나이다. 함수형 프로그래밍은 함수의 응용을 강조한다. 다수의 함수형 프로그래밍 언어들은 람다 계산을 발전시킨 것으로 볼 수 있다.
프로그램을 함수들의 조합으로 만드는 방식.
데이터나 상태를 변경하지 않고 새로운 값을 반환.
(동일한 입력값을 넣으면 동일한 결과를 반환 → 순수 함수(pure function))
단점 : 가독성
장점 : 코드의 재사용성
⇒ 무엇(what)을 해야 하는지에 집중하며, 어떻게(how) 할지를 명시하지 않음
//함수형
let numbers = [ 1, 2, 3 ];
function muliply(numbers, multiplier) {
for (let i = 0; i < numbers.length; i ++) {
numbers[i] = numbers[i] * multiplier;
}
}
-------------------------------------------------------------
//선언형
function multiply(numbers, multiplier) {
return numbers.map(n => n * multiplier);
}
객체지향 언어인 자바에서 함수형 프로그래밍 기법을 지원하는 자바의 문법.
간결하게 코드 블록을 작성할 수 있는 표현 방법으로, 주로 함수형 인터페이스와 함께 사용.
자바는 새로운 함수 문법을 정의하는 대신, 이미 있는 인터페이스의 문법을 활용해서 람다식을 표현한다.
(매개변수) -> { 코드블록 }
하나의 추상 메서드만 가지는 인터페이스.
@FunctionalInterface 어노테이션으로 함수형 인터페이스임을 명시할 수 있음.
→ 컴파일러가 두 개 이상의 추상 메서드를 선언하고 있으면 오류를 반환
예시 : Runnable, Callable, Consumer<T>, Supplier<T>, Function<T, R>
case 1. 인터페이스의 구현 클래스를 만든 후, 해당 클래스의 생성자(new B)를 이용해서 객체를 생성하고, 객체의 참조 변수(case1)를 이용해서 메서드를 호출
(여러번 만들 때 사용)
case 2. 익명 inner 클래스를 사용해서 객체를 생성하고, 이 객체를 이용해서 메서드를 호출
(한번 만들 때 사용)
case 3. 람다식을 활용 : 익명 inner 클래스의 메서드 정의 부분만 가져와 메서드를 정의하고 호출 (화살표 함수) ---> 메서드가 하나여서 정의 부분만 가져와서 쓸 수 있는 것!
@FunctionalInterface
interface A {
void method();
}
class B implements A {
@Override
public void method() {
System.out.println("CASE1");
}
}
public class Test {
public static void main(String[] args) {
// CASE1
A case1 = new B();
case1.method();
// CASE2
A case2 = new A() {
@Override
public void method() {
System.out.println("CASE2");
}
};
case2.method();
// CASE3
A case3 = () -> {
System.out.println("CASE3");
};
case3.method();
}
}
void method() { System.out.println("hello"); }
() -> { System.out.println("hello"); }
() -> System.out.println("hello");
void method(int i) { System.out.println(i); }
(int i) -> { System.out.println(i); }
i -> System.out.println(i);
int method() { return 100; }
() -> { return 100; }
int method(int a, int b) { return a + b; }
(int a, int b) -> { return a + b; }
(a, b) -> a + b;
// 매개변수 X, 반환값 X
@FunctionalInterface
interface XX {
void method(); // public abstract void method();
}
// 매개변수 X, 반환값 O
@FunctionalInterface
interface XO {
int method(); // 숫자 100을 반환
}
// 매개변수 O, 반환값 X
@FunctionalInterface
interface OX {
void method(int i); // 매개변수에 10을 더한 수를 출력
}
// 매개변수 O, 반환값 O
@FunctionalInterface
interface OO {
double method(int i, double d); // 매개변수 값들을 더한 결과를 반환
}
public class Lambda {
static void caseXx() {
class XXClass implements XX {
@Override
public void method() {
System.out.println("XX1");
}
}
XX xx1 = new XXClass();
xx1.method();
XX xx2 = new XX() {
@Override
public void method() {
System.out.println("XX2");
}
};
xx2.method();
XX xx3 = () -> System.out.println("XX3");
xx3.method();
}
static void caseXo() {
class XOClass implements XO {
@Override
public int method() {
return 100;
}
}
XO xo1 = new XOClass();
System.out.println(xo1.method());
XO xo2 = new XO() {
@Override
public int method() {
return 100;
}
};
System.out.println(xo2.method());
XO xo3 = () -> 100;
System.out.println(xo3.method());
}
static void caseOx() {
OX ox1 = i -> System.out.println(i + 10);
ox1.method(100); // 110
OX ox2 = new OX() {
@Override
public void method(int i) {
System.out.println(i + 10);
}
};
ox2.method(100);
class OXClass implements OX {
@Override
public void method(int i) {
System.out.println(i + 10);
}
}
OX ox3 = new OXClass();
ox3.method(100);
}
public static void caseOO() {
OO oo1 = (int i, double d) -> i + d;
System.out.println(oo1.method(100, 10.0)); // 110.0
OO oo2 = new OO() {
@Override
public double method(int i, double d) {
return i + d;
}
};
System.out.println(oo2.method(100, 10.0));
class OOClass implements OO {
@Override
public double method(int i, double d) {
return i + d;
}
}
OO oo3 = new OOClass();
System.out.println(oo3.method(100, 10.0));
}
public static void main(String[] args) {
caseXx();
caseXo();
caseOx();
caseOO();
}
}
객체참조변수::인스턴스메서드명 (단, 반드시 객체를 먼저 생성해야 함)
interface I {
void iii(); // C 클래스의 ccc() 메서드를 호출
}
class C {
void ccc() {
System.out.println("ccc()");
}
}
public class Lambda {
public static void main(String[] args) {
// 익명 이너클래스
I i1 = new I() {
@Override
public void iii() {
C c = new C();
c.ccc();
}
};
i1.iii();
// 람다식
I i2 = () -> {
C c = new C();
c.ccc();
};
i2.iii();
// 메서드 참조로 변경
C cc = new C();
I i3 = cc::ccc;
i3.iii();
}
}
package com.test;
interface I {
// 매개변수로 전달된 i 값을 출력
void printNumber(int i);
}
public class Lambda {
public static void main(String[] args) {
// 구현 클래스 정의 후 인스턴스를 생성해서 실행
class II implements I {
@Override
public void printNumber(int i) {
System.out.println(i);
}
}
I i1 = new II();
i1.printNumber(100);
// 익명 이너 클래스를 이용해서 실행
I i2 = new I() {
@Override
public void printNumber(int i) {
System.out.println(i);
}
};
i2.printNumber(100);
// 람다식을 이용해서 실행
I i3 = i -> System.out.println(i);
i3.printNumber(100);
// 메서드 참조를 이용해서 실행
I i4 = System.out::println;
i4.printNumber(100);
}
}
클래스이름::정적메서드이름
@FunctionalInterface
interface I {
// 클래스 C의 smile() 메서드를 호출
void printSmile();
}
class C {
static void smile() {
System.out.println("^_^");
}
}
public class Lambda {
public static void main(String[] args) {
class IClass implements I {
@Override
public void printSmile() {
C.smile();
}
}
I i1 = new IClass();
i1.printSmile();
I i2 = new I() {
@Override
public void printSmile() {
C.smile();
}
};
i2.printSmile();
I i3 = () -> C.smile();
i3.printSmile();
I i4 = C::smile;
i4.printSmile();
}
}
@FunctionalInterface
interface I {
// 클래스 C의 print() 메서드를 이용해서 매개변수 i의 출력
void printNumber(C c, int i);
}
class C {
void print(int i) {
System.out.println(i);
}
}
public class Lambda {
public static void main(String[] args) {
class IClass implements I {
@Override
public void printNumber(C c, int i) {
c.print(i);
}
}
I i1 = new IClass();
i1.printNumber(new C(), 100);
I i2 = new I() {
@Override
public void printNumber(C c, int i) {
c.print(i);
}
};
i2.printNumber(new C(), 100);
I i3 = (C c, int i) -> c.print(i);
i3.printNumber(new C(), 100);
I i4 = C::print;
i4.printNumber(new C(), 100);
}
}
@FunctionalInterface
interface I {
// 매개변수로 전달된 s의 길이를 반환
int stringLength(String s);
}
public class Lambda {
public static void main(String[] args) {
I i4 = String::length;
System.out.println(i4.stringLength("hello, lambda")); // 13
I i3 = s -> s.length();
System.out.println(i3.stringLength("hello, lambda"));
I i2 = new I() {
@Override
public int stringLength(String s) {
return s.length();
}
};
System.out.println(i2.stringLength("hello, lambda"));
class IClass implements I {
@Override
public int stringLength(String s) {
return s.length();
}
}
I i1 = new IClass();
System.out.println(i1.stringLength("hello, lambda"));
}
}
배열타입[]::new
import java.util.Arrays;
@FunctionalInterface
interface Arr {
// 매개변수로 전달된 len 크기의 int[]를 반환
int[] createArray(int len);
}
public class Lambda {
public static void main(String[] args) {
class ArrClass implements Arr {
@Override
public int[] createArray(int len) {
return new int[len];
}
}
Arr arr1 = new ArrClass();
System.out.println(Arrays.toString(arr1.createArray(3))); // [0, 0, 0]
Arr arr2 = new Arr() {
@Override
public int[] createArray(int len) {
return new int[len];
}
};
System.out.println(Arrays.toString(arr2.createArray(3)));
Arr arr3 = len -> new int[len];
System.out.println(Arrays.toString(arr3.createArray(3)));
Arr arr4 = int[]::new;
System.out.println(Arrays.toString(arr4.createArray(3)));
}
}
클래스명::new
@FunctionalInterface
interface RefDefaultConstructor {
// Cls 클래스의 Cls() 생성자를 이용해서 인스턴스를 생성해서 반환
Cls getInstance();
}
@FunctionalInterface
interface RefParamConstructor {
// Cls 클래스의 Cls(int i) 생성자를 이용해서 인스턴스를 생성해서 반환
Cls getInstance(int i);
}
class Cls {
Cls() {
System.out.println("첫번째 생성자");
}
Cls(int i) {
System.out.println("두번째 생성자");
}
}
public class Lambda {
public static void main(String[] args) {
RefDefaultConstructor r1 = new RefDefaultConstructor() {
@Override
public Cls getInstance() {
return new Cls();
}
};
r1.getInstance(); // 첫번째 생성자
RefDefaultConstructor r2 = () -> new Cls();
r2.getInstance();
RefDefaultConstructor r3 = Cls::new;
r3.getInstance();
RefParamConstructor p1 = new RefParamConstructor() {
@Override
public Cls getInstance(int i) {
return new Cls(i);
}
};
p1.getInstance(100); // 두번째 생성자
RefParamConstructor p2 = i -> new Cls(i);
p2.getInstance(100);
RefParamConstructor p3 = Cls::new;
p3.getInstance(100);
}
}
자바 표준 API 를 보면, 함수형 문법을 쓰는 케이스가 많다. 대표적인 케이스는 public interface Runnable이 있다. 이 외에도 더 많은 유틸을 보려면 java.util.function를 참고하면 된다.
@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();
}
public class Lambda {
public static void main(String[] args) {
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i ++)
System.out.println("#1 : " + i);
}
}
Runnable runner1 = new MyRunnable();
Runnable runner2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i ++)
System.out.println("#2 : " + i);
}
};
Runnable runner3 = () -> { ⇐ 람다식으로 run() 메서드를 정의하고 실행 가능
for (int i = 0; i < 10; i ++)
System.out.println("#3 : " + i);
};
Thread thread1 = new Thread(runner1);
Thread thread2 = new Thread(runner2);
Thread thread3 = new Thread(runner3);
thread1.start();
thread2.start();
thread3.start();
}
}
람다식의 다양한 사례를 공부할 수 있었습니다. 람다식 역시 현업에서 많이 다루지 않았는데, 코드에서 람다식이 나왔을 때 이제는 어렵지 않게 이해할 수 있을 것 같습니다.