자바의 람다식에 대해 학습하세요.
함수를 하나의 식(expression) 으로 표현한것.
함수를 람다식으로 표현하면 메소드의 이름이 필요없기 때문에 익명 함수 익명 함수(Anonymous Function)
의 한 종류라고 볼 수 있다.
메서드 이름과 반환타입을 제거해서 간결하게 만들 수 있다
//일반적 메서드
반환티입 메소드명 (매개변수, ...) {
실행문
}
// 예시
public String hello() {
return "Hello World!";
}
하지만 람다 방식으로는 위와 같이 메소드 명이 불필요하며, 다음과 같이 괄호() 와 화살표-> 를 이용해 함수를 선언하게 된다.
// 람다 방식
(매개변수, ... ) -> { 실행문 ... }
// 예시
() -> "Hello World!";
불필요한 코드를 줄이고, 가독성을 높이기 위함.
그렇게 때문에 함수형 인터페이스의 인스턴스를 생성하여 변수처럼 선언하는 람다식에서는 메소드의 이름이 불필요하다고 여겨져셔 이를 사용하지 않는다.
대신 컴파일러가 문맥을 살피고 타입을 추론한다.
람다식으로 순수 함수를 선언할 수 있게 되었지만 Java는 기본적으로 객체지향 언어이기 때문에 순수함수와 일반 함수를 다르게 취급하고 있으며, Java에서는 이를 구분하기 위해 함수형 인터페이스가 등장하게 되었다.
함수형 인터페이스란 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로
, 인터페이스에 선언하여 단 하나의 추상메소드만을 갖도록 제한하는 역할을 한다.
public interface FunctionalInterface {
public abstract void doSomething(String text);
}
람다식은 함수형 인터페이스로만 접근이 가능하다.
public interface FunctionalInterface {
public abstract void doSomething(String text);
}
//func는 람다식으로 생성한 객체를 가르키고있음
FunctionalInterface func = text -> System.out.println(text);
//인자를 전달
func.doSomething("do something");
//실행결과
//do someting
익명 클래스를 사용하여 리팩토링한 코드, 함수형인터페이스와 람다식으로 익명 클래스르 간단히 표현할 수 있다.
FunctionalInterface func = new FunctionalInterface() {
@Override
public void doSomething(String text) {
System.out.println(text);
}
};
func.doSomething("do something");
자바에서 기본으로 제공하는 함수형 인터페이스
Runnable
인자를 받지 않고 리턴값도 없는 인터페이스
public interface Runnable {
public abstract void run();
}
Runnable runnable = () -> System.out.println("run anything!");
runnable.run();
// 결과
// run anything!
Supplier<T>
인자를 받지 않고 T 타입의 객체를 리턴함
// 정의
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 사용 예시
Supplier<String> supplier = () -> "Hello World!";
System.out.println(supplier.get());
Consumer<T>
T타입의 객체를 인자로 받고 리턴 값은 없음
// 정의
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
// 예시
Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World");
// 출력
Hello
Hello World
Function<T, R>
T타입의 인자를 받고 R타입의 객체를 리턴함
// 정의
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
// 예시, 메소드 참조로 간소화 가능(String::length;)
Function<String, Integer> function = str -> str.length();
function.apply("Hello World");
Predicate<T>
T타입의 인자를 받고 결과로 boolean을 리턴
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
Predicate<Integer> isBiggerThanFive = num -> num > 5;
System.out.println("10 is bigger than 5? -> " + isBiggerThanFive.test(10));
// 결과
// 10 is bigger than 5? -> true
로컬 클래스를 덮고 있는 주변 블록의 지역 변수 또는 매개 변수에 접근하는 것을 해당 변수 또는 매개 변수를 캡쳐 한다고 표현한다
캡쳐라는 것은 실제로도 변수를 copy 하는 것과 같이 동작한다.
JVM에서 지역 변수는 쓰레드 마다의 스택 영역에 저장되는데, 로컬 클래스는 인스턴스화 되는 시점에서 이 지역 변수를 참조하여 자신의 쓰레드의 스택 영역에 저장하게 된다.
그렇게 때문에 접근하는 지역 변수를 복사한 이 후로 변하지 않도록 final과 같은 제한이 생긴것이다.
public void addMusicOnList(String name, String singer) {
final int musicNumber = 1;
//listNumber는 로컬 클래스에서 변경되지 않는다면, '사실상 final' 이다.
int listNumber = 1;
class Music {
public String name;
public String singer;
public Music(String name, String singer) {
this.name = name;
this.singer = singer;
}
public void addMusic() {
//listNumber = 9; //final이 아님에도 불구하고 변경하면 안된다.
musicList.add(this.name);
System.out.println(listNumber + "번 리스트에 "+musicNumber+"음악을 추가합니다.");
}
}
Music music = new Music(name, singer);
music.addMusic();
}
람다식은 간단하게 기존에 선언되어 있는 메소드를 호출하는 기능만 가지고 있는 경우가 있는데, 이럴 때는 메소드 레퍼런스를 사용하여 기존 메소드를 이름으로 참조하는 것이 가능하며 보기에 더 명확하고 간결하게 코딩할 수 있다.
class Music {
public void download() {
System.out.println("Download Music");
}
}
class CafeMusic extends Music{
List<String> musicList = Arrays.asList("Perm", "Dolphin", "Piano Man");
public CafeMusic() {}
public void showList() {
for (String s : musicList) {
System.out.println(s);
}
}
public static void on() {
System.out.println("Music On");
}
}
public class Cafe {
public void setUpMusic(CafeMusic cafeMusic) {
//특정 타입의 임의 객체의 인스턴스 메서드 레퍼런스
//ContainingType::methodName
//Runnable runnable1 = () -> cafeMusic.download();
Runnable runnable1 = cafeMusic::download;
runnable1.run();
System.out.println("=================");
//특정 객체의 인스턴스 메소드 레퍼런스
//containingObject::instanceMethodName
//Runnable runnable2 = () -> cafeMusic.showList();
Runnable runnable2 = cafeMusic::showList;
runnable2.run();
System.out.println("=================");
//정적 메소드 레퍼런스
//ContainingClass::staticMethodName
//Runnable runnable3 = () -> CafeMusic.on();
Runnable runnable3 = CafeMusic::on;
runnable3.run();
};
public static void main(String[] args) {
Cafe cafe = new Cafe();
//생성자 레퍼런스
//ClassName::new
// Supplier<CafeMusic> supplier = () -> {
// return new CafeMusic();
// };
Supplier<CafeMusic> supplier = CafeMusic::new;
cafe.setUpMusic(supplier.get());
}
}
https://mangkyu.tistory.com/113
https://rockintuna.tistory.com/107