출처 ‣ 더 자바 Java 8, 백기선 / 인프런
#람다 #메소드 레퍼런스 #스트림 API #Optional
추상메서드가 하나만 있는 인터페이스 (SAM:Single Abstract Method)
@FunctionalInterface // java standard lib에 들어서 별도 import가 되지 않음
public interface RunSomthing {
// 함수형 인터페이스
void doIt(); // interface는 abstract 생략가능
// <자바8의 추가 기능>
// 1. static 메서드
static void printName() {
System.out.println("esjo");
}
// 2. default 메서드
default void pringAge() {
System.out.println("28");
}
}
public class Main {
public static void main(String[] args) {
/** 익명 내부 클래스 anonymous inner class */
// 자바8 이전
RunSomthing runSomthing1 = new RunSomthing() {
@Override
public void doIt() {
System.out.println("Hello");
}
};
// 자바 8
RunSomthing runSomthing2 = () -> System.out.println("Hello");
RunSomthing runSomthing3 = () -> {
System.out.println("Hello");
System.out.println("Bye");
};
runSomthing1.doIt();
runSomthing2.doIt();
runSomthing3.doIt();
}
}
*First class object?
과거 자바에서 Method는 변수에 담을 수 없었다.
모던자바에서는 가능해짐. 함수를 하나의 Object(객체로 취급)
// 인터페이스
@FunctionalInterface
public interface RunSomthing {
int doIt(int number);
}
// 구현
public class Foo {
public static void main(String[] args) {
RunSomthing runSomthing = (number) -> {
return number + 10;
}
runSomthing.doIt(1); // 11
runSomthing.doIt(1); // 11
runSomthing.doIt(1); // 11
}
}
// 인터페이스
@FunctionalInterface
public interface RunSomthing {
int doIt(int number);
}
// 구현
public class Foo {
public static void main(String[] args) {
RunSomthing runSomthing1 = new RunSomthing() {
int baseNumber = 10;
@Override
public int doIt(int number) {
baseNumber++; // 2) 외부의 값을 변경
return number + baseNumber; // 1) 외부의 값을 사용
}
};
}
}
함수가 함수를 매개변수로 받고, 리턴 할 수 있다.
docs : https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html
자주 사용할 만한 함수 인터페이스
// 방법1. 클래스로 구현해서 사용
import java.util.function.Function;
public class Plus10 implements Function<Integer, Integer> {
@Override
public Integer apply(Integer number) {
return number + 10;
}
}
// 사용
public class Foo {
public static void main(String[] args) {
Plus10 plus10 = new Plus10();
System.out.println(plus10.apply(1));
}
}
// 방법2. 람다식으로 바로 사용
import java.util.function.Function;
public class Foo {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = (i) -> i + 10;
System.out.println(plus10.apply(1));
}
}
import java.util.function.Function;
public class Foo {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = (i) -> i + 10;
Function<Integer, Integer> multiply2 = (i) -> i * 2;
Function<Integer, Integer> plus10Andmultiply2 = plus10.andThen(multiply2);
System.out.println(plus10Andmultiply2.apply(2)); // (2 + 10) * 10 = 120
}
}
import java.util.function.Function;
public class Foo {
public static void main(String[] args) {
Function<Integer, Integer> plus10 = (i) -> i + 10;
Function<Integer, Integer> multiply2 = (i) -> i * 2;
Function<Integer, Integer> multiply2AndPlus10 = plus10.compose(multiply2);
System.out.println(multiply2AndPlus10.apply(2)); // (2 * 2) + 10 = 14
}
}
Supplier<Integer> get10 = () -> {
return 10;
}
// 함수가 한줄
Supplier<Integer> get10 = () -> 10;
// 파라미터가 두개 이상
BinaryOperator<Integer> sum = (a, b) -> a + b;
// 타입을 적어줄 수 있지만 선언부의 타입으로 유추 가능해서 생략가능
BinaryOperator<Integer> sum = (Integer a, Integer b) -> a + b;
람다 밖의 변수 = 자유변수
자유변수를 사용하는것 = 람다 캡쳐링
import java.util.function.Consumer;
import java.util.function.IntConsumer;
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() {
final int baseNumber = 10; // final을 생략 할수 있는경우 = 실제 더이상 변경하지 않는경우 (effectively final)
// 로컬 클래스 (local class)
class LocalClass {
void printBaseNumber() {
int baseNumber = 12; // 쉐도잉됨
System.out.println(baseNumber);
}
}
// 익명 클래스 (anonymous class)
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer i) {
int baseNumber = 11; // 쉐도잉됨
System.out.println(i + baseNumber);
}
};
// 람다 (Lamda)
IntConsumer printInt = (i) -> {
// int baseNumber = 1; // 같은 scope안에 같은 변수명을 사용할 수 없다. 컴파일 에러
System.out.println(i + baseNumber); // 로컬변수 참조
};
new LocalClass().printBaseNumber(); // 12
integerConsumer.accept(10); // 21
printInt.accept(10); // 20
}
}
기존 메서드 or 생성자 호출시 간결한 표현 가능
// 아래 예제를 위한 참조 클래스
class Greeting {
private String name;
public Greeting() {
}
public Greeting(String name) {
this.name = name;
}
static String hi(String name) {
return "hi " + name;
}
String hello(String name) {
return "hello " + name;
}
}
UnaryOperator<String> hi = Greeting::hi;
System.out.println(hi.apply("eunsol")); // SOUT = hi eunsol
Greeting greeting = new Greeting();
UnaryOperator<String> hello = greeting::hello;
System.out.println(hello.apply("eunsol")); // SOUT = hello eunsol
String[] names = {"esjo", "cho", "jeo"};
// 1. 익명함수
Arrays.sort(names, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return 0; // 구현
}
});
// 2. 람다
Arrays.sort(names, (o1, o2) -> { /*int값을 리턴하는 로직 구현*/ });
// 3. 메소드 참조
// - compareToIgnoreCase : 자기자신과 파라미터 String 값을 비교해서 int 값을 넘겨줌
// - compare함수와 파라미터+리턴값이 동일
Arrays.sort(names, String::compareToIgnoreCase); // arrayList내부의 임의의 객체들을 참조함
타입::new
// 기본 생성자 (파라미터X)
Supplier<Greeting> newGreeting = Greeting::new;
Greeting greeting = newGreeting.get();
// 파라미터 있는 생성자
Function<String, Greeting> newGreeting = Greeting::new;
Greeting greeting = newGreeting.apply();
// => 메서드 레퍼런스 만으로 판단하기 어렵다.