Lambda expression
Functional interface
Method reference
Stream
Optional
Java 8은 자바에 함수형 프로그래밍을 본격 도입하며, API 및 문법이 크게 확장된 버전입니다.

ClassName::methodName@FunctionalInterface 지원Function, Predicate, Consumer, Supplier 등 (java.util.function 패키지)map, filter, collect 등Optional.of, Optional.ofNullable, orElse 등IntStream, LongStream, DoubleStreamLocalDate, LocalDateTime, Duration 등// 인터페이스 구현 (Runnable)
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("익명 클래스의 run 메소드 실행");
}
};
Thread thread = new Thread(runnable);
thread.start();
// 추상 클래스 상속
abstract class Animal {
abstract void sound();
}
Animal dog = new Animal() {
@Override
void sound() {
System.out.println("멍멍!");
}
};
dog.sound(); // "멍멍!" 출력
// 버튼 이벤트 처리(GUI)
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("버튼 클릭!");
}
});
// 메소드의 이름과 반환 타입을 제거하고 '->'를 블록{} 앞에 추가
(int a, int b) -> {
return a > b ? a : b;
}
// 반환 값이 있는 경우 식이나 값만 적고 return문 생략 가능(세미콜론을 안 붙임)
(int a, int b) -> a > b ? a : b
// 매개변수 타입이 추론 가능하면 생략 가능
(a, b) -> a > b ? a : b
// 매개변수가 하나인 경우 괄호 생략 가능(타입 생략시에만)
a -> a * a
// 블록 안의 문장이 하나뿐일 때 괄호 생략 가능(세미콜론을 안 붙임)
(int a) -> System.out.println(a)
// 하나뿐인 문장이 return문이면 괄호 생략 불가(return도 생략하면 가능)
(int a, int b) -> { return a > b ? a : b; }
컴파일러가 컨텍스트에서 추론할 수 있는 경우 타입 생략 가능
invokedynamic 명령어와 런타임에 생성되는 팩토리 메소드 활용.class 파일로 명확하게 생성됨this는 람다가 선언된 바깥 클래스의 인스턴스를 가리킴this는 익명 클래스 자신을 가리킴class Outer {
void test() {
Runnable r1 = () -> {
System.out.println(this); // Outer의 this
};
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println(this); // 익명 클래스의 this
}
};
r1.run(); // Outer@xxx 출력
r2.run(); // Outer$1@yyy (익명클래스) 출력
}
}
@FunctionalInterface가 없더라도 단 하나의 추상 메소드만 가지면 람다식으로 활용은 가능 (예: Comparator)정적 메소드 참조
public class Utils {
public static void printUpper(String s) {
System.out.println(s.toUpperCase());
}
}
List<String> list = List.of("a", "b", "c");
// 람다
list.forEach(s -> Utils.printUpper(s));
// 메서드 참조
list.forEach(Utils::printUpper);
특정 객체의 인스턴스 메서드 참조
Printer printer = new Printer();
list.forEach(printer::print);
class Printer {
public void print(String s) {
System.out.println("출력: " + s);
}
}
임의 객체의 인스턴스 메소드 참조
List<String> list = List.of("spring", "boot", "java");
// 정렬 기준: 소문자 알파벳 순
list.sort(String::compareToIgnoreCase);
내부적으로는 이렇게 동작:
(a, b) -> a.compareToIgnoreCase(b)
생성자 참조
Supplier<List<String>> s = ArrayList::new;
// () -> new ArrayList<>()와 동일
Function<String, Integer> f = Integer::new;
// (s) -> new Integer(s)와 동일 (Deprecated됨에 주의)
java.util.function 패키지의 기본 인터페이스 모음java.lang.Runnable: void run() 매개변수도 없고 반환 값도 없음Supplier<T>: T get() 매개변수는 없고 반환 값만 있음Consumer<T>: void accept(T t) 매개변수만 있고 반환 값이 없음Function<T, R>: R apply(T t) 하나의 매개변수를 받아 반환 값을 반환Predicate<T>: boolean test(T t) 조건식을 표현하는데 사용UnaryOperator<T>: T apply(T t) Function의 자손으로 매개변수와 반환 값의 타입이 같음BinaryOperator<T>: T apply(T t, T t) BiFunction의 자손으로 매개변수와 반환 값의 타입이 같음default void foEach(Consumer<? super T> action)
List<String> names = List.of("Alice", "Bob");
names.forEach(name -> System.out.println(name));
Random random = new Random();
IntSupplier intSupplier = () -> random.nextInt(100) + 1;
IntStream.generate(intSupplier).limit(5).forEach(System.out::println);
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isGreaterThan10 = n -> n > 10;
Stream<Integer> stream = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).stream();
stream.filter(isEven).filter(isGreaterThan10).forEach(System.out::println);
Function<String, Integer> strLength = s -> s.length();
int len = strLength.apply("Hello");
UnaryOperator<Integer> square = x -> x * x;
BinaryOperator<Integer> sum = (a, b) -> a + b;
Predicate<String> startsWithA = s -> s.startsWith("a");
predicate<String> lengthIs3 = s -> s.length() == 3;
Predicate<?> combined = startsWithA.and(lengthIs3);

// 스트림은 데이터 소스로부터 데이터를 읽기만할 뿐 변경하지 않습니다.
List<Integer> list = Arrays.asList(3, 1, 5, 4, 2);
List<Integer> sortedList = list.stream().sorted() // list를 정렬해서
.collect(Collectors.toList()); // 새로운 List에 저장
System.out.println(list); // [3, 1, 5, 4, 2]
System.out.println(sortedList); // [1, 2, 3, 4, 5]
// 스트림은 Iterator처럼 일회용입니다.
strStream.forEach(System.out::println); // 모든 요소를 화면에 출력(최종 연산)
int numOfStr = strStream.count(); // 스트림이 이미 닫혀서 에러
// 최종 연산 전까지 중간 연산이 수행되지 않습니다.(지연된 연산 - lazy evaluation)
IntStream intSteram = new Random().ints(1,46); // 1~45 범위의 무한 스트림
intStream.distinct().limit(6).sorted() // 중간 연산
.forEach(i->System.out.print(i+",")); // 최종 연산
// 스트림은 작업을 내부 반복으로 처리합니다.
for (String str : strList) {
System.out.println(str); // -> stream.forEach(System.out::println);
}
void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action); // 매개변수의 널 체크
for (T t : src) { // 내부 반복(for 문을 메소드 안으로 넣음)
action.accept(T);
}
}


Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallelStream()
.mapToInt(s -> s.length()).sum();
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum = list.stream()
.map(i -> i * 2)
.reduce(0, Integer::sum);
.map(i -> i * 2)Integer) map에 전달i를 int로 언박싱i(Integer)가 i * 2 연산을 위해 int로 자동 언박싱i.intValue() * 2Integer로 박싱map의 리턴 타입이 Stream<Integer>이기 때문에Integer로 오토박싱.reduce(0, Integer::sum)int sum = IntStream.of(1, 2, 3, 4, 5)
.map(i -> i * 2) // int 연산
.sum();
NullPointerException을 방지하기 위해 활용됨NPE(NullPointerException) null 값을 참조하려 할 때 발생하는 예외Optional<T>, OptionalInt, OptionalLong, OptionalDouble 등
Optional.of(T value) 절대 null이 아님을 보장할 때 (null이면 예외 발생)Optional.ofNullable(T value) value가 null일 수도 있을 때Optional.empty() 비어있는 Optional, null이 저장됨get() 남용 금지: 값이 없을 때 예외. 항상 orElse/ifPresent 등으로 안전하게 처리해야 함Serializable이 아님 (Java 9부터 Serializable)// 'T'타입 객체의 래퍼 클래스 - Optional<T>
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(null); // NullPointerException 발생
Optional<String> optVal = Optional.ofNullable(null) // OK
// Optional 객체의 값 가져오기 - get(), orElse(), orElseGet(), orElseThrow()
Optional<String> optVal = Optional.of("abc");
// T get()
String str1 = optVal.get(); // optVal에 저장된 값을 반환, null이면 예외 발생
// T orElse(T other)
String str2 = optVal.orElse(""); // optVal에 저장된 값이 null일 때는 ""를 반환
// T orElseGet(Supplier<? extends T> other)
String str3 = optVal.orElseGet(String::new) // 람다식 사용 가능 () -> new String()
// T orElseThrow(Supplier<? extends X> exceptionSupplier)
String str4 = optVal.orElseThrow(NullPointerException::new); // null이면 예외 발생