백기선 님의 <더 자바, Java 8 >를 보고 공부한 내용을 기록합니다.
1. 개념 소개
1-1. 함수형 인터페이스 (Functional Interface)
SAM (Single Abstrct Method) 인터페이스
Copy package me.whiteship.java8to11._01_fi;
@FunctionalInterface // 어노테이션을 달아서 명시해준다.
public interface RunSomething {
int plusTen(int num);
// java8부터 interface 에 쓰일 수 있는 함수의 형태가 다양해졌다.
// static method, default method 는 functional interface의 메소드로 카운트하지 않아서
// 아래와 같이 함수형 인터페이스에도 같이 사용 가능!
static void printName(String name) {
System.out.println(name);
}
default void printAge(int age) {
System.out.println(age);
}
}
Copy @Test
void 익명내부클래스로_표현한다() {
RunSomething runSomething = new RunSomething() {
@Override
public int plusTen(int num) {
return num + 10;
}
};
assertThat(runSomething.plusTen(10)).isEqualTo(20);
}
@Test
void 람다로_표현한다() {
RunSomething runSomething = num -> num + 10;
assertThat(runSomething.plusTen(10)).isEqualTo(20);
}
1-2. 람다 표현식 (Lambda Expression)
함수형 인터페이스의 인스턴스를 만드는 방법으로 쓰일 수 있다.
메소드 매개변수, 리턴타입, 변수로 만들어 사용할 수 있다.
1-3. 자바에서 함수형 프로그래밍시 주의할 점
순수함수(pure function)이다.
사이드 이펙트가 없다. 함수 밖에 있는 값을 변경하지 않는다.
상태가 없다. 함수 밖에 있는 값을 사용하지 않는다.
고차함수(higher-order function)
함수가 함수를 매개변수로 받을 수 있고, 함수를 리턴할 수도 있다.
불변성 : 같은 매개변수를 넣으면 항상 같은 값이 나온다.
Copy @Test
void 일급함수로사용할수있다() {
RunSomething runSomething = num -> num + 10;
assertThat(runSomething.plusTen(10)).isEqualTo(20);
}
@Test
void 순수함수이다() {
int baseNum = 10;
RunSomething runSomething = num -> baseNum += num; // 컴파일 에러
runSomething.plusTen(10);
}
@Test
void 고차함수이다() {
RunFactory runFactory = new RunFactory();
RunSomething runSomething = runFactory.runSomething(num -> num + 100); //매개변수로 전달
assertThat(runSomething.plusTen(10)).isEqualTo(110);
}
@Test
void 상태가없어야한다() {
int baseNum = 15;
RunSomething runSomething = num -> baseNum + num; // 외부 상태값을 사용함
assertThat(runSomething.plusTen(10)).isEqualTo(25);
}
2. 자바에서 제공하는 함수형 인터페이스
Java가 기본으로 제공하는 함수형 인터페이스가 존재한다.
java.lan.function package 에 속해있다.
자바 차원에서 미리 자주 사용할만한 함수 인터페이스를 만들어둔 것이다.
2-1. Function<T, R>
T 타입을 받아서 R타입을 리턴하는 인터페이스
Copy public class Plus10 implements Function<Integer, Integer> {
@Override
public Integer apply(Integer integer) {
return integer + 10;
}
}
Copy public class FunctionInterface {
@Test
void applyTest() {
//구현체
Plus10 plus10 = new Plus10();
assertThat(plus10.apply(10)).isEqualTo(20);
//lambda
Function<Integer, Integer> plus11 = num -> num + 11;
assertThat(plus11.apply(10)).isEqualTo(21);
}
@Test
void composeTest() {
Function<Integer, Integer> plus10 = num -> num + 10;
Function<Integer, Integer> multiply5 = num -> num * 5;
Function<Integer, Integer> both = plus10.compose(multiply5);
assertThat(both.apply(2)).isEqualTo(20);
}
@Test
void andThenTest() {
Function<Integer, Integer> plus10 = num -> num + 10;
Function<Integer, Integer> multiply5 = num -> num * 5;
Function<Integer, Integer> both = plus10.andThen(multiply5);
assertThat(both.apply(2)).isEqualTo(60);
}
}
2-2. BiFunction<T, U, R>
Copy public class BiFunctionTest {
@Test
void applyTest() {
BiFunction<Integer, Boolean, String> isTrue = (num, bool) -> bool ? "true" : "false";
assertThat(isTrue.apply(10, false)).isEqualTo("false");
}
}
2-3. Consumer<T>
Copy public class ConsumerSupplierTest {
@Test
void consumerAccept() {
Consumer<Integer> consumer = num -> System.out.println(num);
consumer.accept(10);
}
@Test
void andThenTest() {
Consumer<Integer> consumer = num -> System.out.println(num + 10);
Consumer<Integer> consumer2 = num -> System.out.println(num + 20);
Consumer<Integer> both = consumer.andThen(consumer2);
both.accept(10);
}
}
2-4. Supplier<T>
Copy public class ConsumerSupplierTest {
@Test
void supplierGet() {
Supplier<Integer> supplier = () -> 10;
assertThat(supplier.get()).isEqualTo(10);
}
}
2-5. Predicate<T>
Copy
public class PredicateTest {
@Test
void test() {
Predicate<Integer> isEven = num -> num % 2 == 0;
assertThat(isEven.test(11)).isFalse();
assertThat(isEven.test(10)).isTrue();
}
@Test
void combineTest() {
Predicate<String> length6 = str -> str.length() == 6;
Predicate<String> startsWithM = str -> str.startsWith("M");
Predicate<String> containsT = str -> str.contains("T");
Predicate<String> triple = length6.and(startsWithM).or(containsT);
assertThat(triple.test("Mountain")).isFalse();
assertThat(triple.test("moTher")).isTrue();
}
@Test
void negateTest() {
Predicate<String> length6 = str -> str.length() == 6;
assertThat(length6.negate().test("hello")).isTrue();
}
}
2-6. UnaryOperator<T>
Copy public class OperatorTest {
@Test
void unaryOperatorTest() {
Function<Integer, Integer> function = (i1) -> i1 + 10;
UnaryOperator<Integer> unaryOperator = i1 -> i1 + 10;
assertThat(function.apply(10)).isEqualTo(unaryOperator.apply(10));
}
}
2-7. BinaryOperator<T>
Copy public class OperatorTest {
@Test
void binaryOperatorTest() {
BiFunction<Integer, Integer, Integer> biFunction = (i1, i2) -> i1 * i2;
BinaryOperator<Integer> binaryOperator = (i1, i2) -> i1 * i2;
assertThat(biFunction.apply(10, 12)).isEqualTo(binaryOperator.apply(10, 12));
}
}
3. 람다 표현식
3-1. 람다
3-2. 인자 리스트
인자의 타입은 생략 가능하다.
컴파일러가 추론(infer)할 수 있기 때문!
하지만 명시할 수도 있다. (Integer one, Integer two)
3-3. 바디
한 줄인 경우에 생략 가능, return도 생략 가능하다.
3-4. 변수 캡처 (Variable Capture)
로컬변수캡처
final이거나 effective final 인 경우에만 참조할 수 있다.
그렇지 않을 경우 concurrency 문제가 생길 수 있어서 컴파일가 방지한다.
effective final
자바 8부터 지원하는 기능으로 “사실상" final인 변수.
final 키워드 사용하지 않은 변수를 익명 클래스 구현체 또는 람다에서 참조할 수 있다.
익명 클래스 구현체와 달리 ‘쉐도윙’하지 않는다.
익명 클래스는 새로 스콥을 만들지만, 람다는 람다를 감싸고 있는 스콥과 같다.
Copy public class LambdaTest {
@Test
void variableCapture() {
int baseNum = 100;
class NestedInnerClassTest {
int baseNum = 1; // 참조하는 변수
@Test
void plus10() {
System.out.println(baseNum + 10);
}
}
Consumer<Integer> consumer = new Consumer<Integer>() {
int baseNum = 5; // 참조하는 변수
@Override
public void accept(Integer integer) {
System.out.println(baseNum + integer);
}
};
// lambda
Function<Integer, Integer> function = num -> baseNum + num;
// baseNum++; //compile error : not final!
}
}
3-5. 참고
4. 메소드 레퍼런스
만약에 람다가 하는 일이 기존 메소드나 생성자를 호출하는 것뿐이라면, 메소드 레퍼런스를 사용해서 매우 간결하게 표현이 가능하다!
Copy
public class MethodReferenceTest {
@Test
void 스태틱_메소드_참조() {
Function<String, String> function = Greeting::hi;
assertThat(function.apply("flash")).isEqualTo("hi, flash");
}
@Test
void 특정_객체의_인스턴스_메소드_참조() {
Greeting greeting = new Greeting();
Function<String, String> function = greeting::hello;
assertThat(function.apply("flash")).isEqualTo("hello, flash");
}
@Test
void 임의_객체의_인스턴스_메소드_참조() {
String[] names = {"kildong", "michle", "doolly"};
Arrays.sort(names, String::compareToIgnoreCase);
assertThat(Arrays.toString(names))
.isEqualTo("[doolly, kildong, michle]");
}
@Test
void 생성자_참조() {
Function<String , Greeting> greetingFunction = Greeting::new; //with name
Supplier<Greeting> greetingSupplier = Greeting::new; //default
assertThat(greetingFunction.apply("flash")).isEqualTo(new Greeting("flash"));
assertThat(greetingSupplier.get()).isEqualTo(new Greeting());
}
}