Java 8 주요 내용(정리중)

오의석·2025년 1월 15일

1. 함수형 인터페이스

1개의 추상 메소드를 갖는 인터페이스. 주로 콜백 함수나 처리기 등을 표현하는 데 유용

  • 기본 정의
@FunctionalInterface
public interface MyFunction {
    void apply();  // 단 하나의 추상 메서드
}

...

//사용 방식
MyFunction myFunc = () -> System.out.println("Hello, Lambda!");
  • @FunctionalInterface : 해당 어노테이션은 선택적이지만, '2개 이상의 추상 메서드가 존재할 경우 컴파일 오류'를 발생시켜 인터페이스 규칙 파악하기 쉽다.
    (1) Predicate : 하나의 인자(T)를 받아 boolean 값을 반환하는 함수형 인터페이스

    Predicate isEven = n -> n % 2 == 0;
    System.out.println(isEven.test(4)); // true

(2) Consumer : 하나의 인자(T)를 받아 반환값 없이 처리하는 함수형 인터페이스

Consumer printer = s -> System.out.println(s);
printer.accept("Hello, Consumer!"); // "Hello, Consumer!" 출력

(3) Supplier : 인자를 받지 않고 값을 반환하는 함수형 인터페이스입니다.

Supplier randomValue = () -> Math.random();
System.out.println(randomValue.get()); // 랜덤한 값 출력

(4) Function<T,R> : 하나의 인자를 받아 다른 값을 반환하는 함수형 인터페이스(T -> R)

Function<Integer, String> intToString = num -> "Number: " + num;
System.out.println(intToString.apply(5)); // "Number: 5"

(5) Comparator : 두 인자를 비교하여 정렬 순서를 결정하는 함수형 인터페이스

List numbers = Arrays.asList(5, 2, 8, 1, 3);
Comparator ascendingOrder = (a, b) -> a - b;
Collections.sort(numbers, ascendingOrder)

(6) Runnable : 인자가 없고 반환값도 없는 run() 메서드를 가진 함수형 인터페이스

Runnable runnable = () -> System.out.println("Running...");
new Thread(runnable).start();

(7) Collable : Runnable과 달리 결과를 반환할 수 있는 제너릭 함수형 인터페이스

Callable task = () -> {
System.out.println("Task is executing in thread: " + Thread.currentThread().getName());
return 42; // 작업 결과 반환
};
ExecutorService executor = Executors.newSingleThreadExecutor();
Future future = executor.submit(task);

함수형 인터페이스와 연관된 메서드

TIP. 이외에도 직접 선언해서 만들어서 사용해도 된다.

함수형 인터페이스추상 메서드
Runnablerun()
Callable<V>call()
Comparator<T>compare(T o1, T o2)
Predicate<T>test(T t)
Function<T, R>apply(T t)
Consumer<T>accept(T t)
Supplier<T>get()
UnaryOperator<T>apply(T t)
BinaryOperator<T>apply(T t1, T t2)
BiFunction<T, U, R>apply(T t, U u)
BiPredicate<T, U>test(T t, U u)
BiConsumer<T, U>accept(T t, U u)
IntFunction<R>apply(int value)
IntPredicatetest(int value)

2. 메서드 레퍼런드

메서드 레퍼런스는 특정 메서드만을 호출하는 람다의 축약형

람다 표현식메서드 레퍼런스
(Apple a) → a.getWeight()Apple::getWeight
() → Thread.currentThread().dumpStack()Thread.currentThread()::dumpStack
(str, i) ⇒ str.substring(i)String::substring
(String s) → System.out.println(s)System.out::println

3. 함수형 인터페이스 조합

자주쓰이는 것으로 정리
(1) Comparator : thenComparing 메서드 사용. 첫 번째 비교 후, 두 객체가 같다고 판단되면 두 번째 비교.
(2) Predicate : negate, and, or 세 가지 메서드로 조합
(3) Function : andThen, compose 두 가지 메서드로 조합(andThen만 추천)

  • andThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달
  • compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공

4. stream api

명칭용도예시초기 데이터예시 적용 후 데이터
스트림 생성컬렉션, 배열, 특정 값 등에서 스트림을 생성하는 방법List<String> list = Arrays.asList("apple", "banana");
list.stream();
["apple", "banana"]Stream["apple", "banana"]
중간 연산 (Intermediate Operations)스트림의 데이터를 변형하거나 필터링하는 연산filter(), map(), sorted(), distinct()
filter()주어진 조건을 만족하는 요소만 필터링list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());["apple", "banana", "avocado"]["apple", "avocado"]
map()각 요소를 변환하는 연산list.stream().map(String::length).collect(Collectors.toList());["apple", "banana", "avocado"][5, 6, 7]
flatMap()중첩된 스트림을 평면화하여 처리list.stream().flatMap(List::stream).collect(Collectors.toList());[[1, 2], [3, 4], [5, 6]][1, 2, 3, 4, 5, 6]
sorted()스트림 요소를 정렬하는 연산list.stream().sorted().collect(Collectors.toList());["banana", "apple", "cherry"]["apple", "banana", "cherry"]
distinct()중복된 요소를 제거하는 연산list.stream().distinct().collect(Collectors.toList());["apple", "banana", "apple"]["apple", "banana"]
peek()스트림의 각 요소를 소비하지 않고 디버깅용으로 중간에 확인하는 연산list.stream().peek(System.out::println).collect(Collectors.toList());["apple", "banana", "cherry"]apple banana cherry (콘솔 출력)
최종 연산 (Terminal Operations)스트림을 종료하고 결과를 생성하는 연산collect(), forEach(), reduce(), anyMatch()
collect()스트림의 요소들을 컬렉션으로 수집list.stream().collect(Collectors.toList());["apple", "banana", "cherry"]["apple", "banana", "cherry"]
forEach()스트림의 각 요소를 처리하는 연산list.stream().forEach(System.out::println);["apple", "banana", "cherry"]apple banana cherry (콘솔 출력)
reduce()스트림의 모든 요소를 하나의 값으로 합치는 연산list.stream().reduce(0, Integer::sum);[1, 2, 3, 4]10
anyMatch()스트림에서 하나라도 조건을 만족하는 요소가 있는지 확인list.stream().anyMatch(s -> s.startsWith("a"));["apple", "banana"]true
allMatch()스트림의 모든 요소가 조건을 만족하는지 확인list.stream().allMatch(s -> s.length() > 3);["apple", "banana"]true
noneMatch()스트림의 요소가 조건을 하나도 만족하지 않는지 확인list.stream().noneMatch(s -> s.startsWith("z"));["apple", "banana"]true
findFirst()스트림에서 첫 번째 요소를 찾는 연산Optional<String> first = list.stream().findFirst();["apple", "banana", "cherry"]Optional[apple]
findAny()스트림에서 아무거나 첫 번째 요소를 찾는 연산Optional<String> any = list.stream().findAny();["apple", "banana", "cherry"]Optional[apple]
count()스트림의 요소 개수를 반환하는 연산long count = list.stream().count();["apple", "banana", "cherry"]3
min()스트림에서 최소값을 찾는 연산Optional<String> min = list.stream().min(String::compareTo);["apple", "banana", "cherry"]Optional[apple]
max()스트림에서 최대값을 찾는 연산Optional<String> max = list.stream().max(String::compareTo);["apple", "banana", "cherry"]Optional[cherry]
toArray()스트림의 요소를 배열로 변환String[] array = list.stream().toArray(String[]::new);["apple", "banana", "cherry"]["apple", "banana", "cherry"]
flatMapToInt(), flatMapToDouble(), flatMapToLong()기본 타입의 스트림으로 변환하여 평면화list.stream().flatMapToInt(s -> IntStream.range(1, s.length()));["apple", "banana"][1, 2, 3, 4, 5] (int 타입 스트림)
병렬 스트림 (Parallel Streams)데이터를 병렬로 처리하여 성능을 향상시킬 수 있는 방법list.parallelStream().forEach(System.out::println);["apple", "banana", "cherry"]apple banana cherry (콘솔 출력)
Stream.of()인자로 주어진 데이터를 기반으로 스트림을 생성Stream<String> stream = Stream.of("apple", "banana", "cherry");["apple", "banana", "cherry"]Stream["apple", "banana", "cherry"]
iterate()지정된 규칙에 따라 반복해서 요소를 생성하는 스트림 생성Stream<Integer> stream = Stream.iterate(1, n -> n + 1).limit(5);[] (초기값 없음)[1, 2, 3, 4, 5]
generate()지정된 함수에 의해 무한하게 스트림을 생성Stream<Double> stream = Stream.generate(Math::random).limit(5);[] (초기값 없음)[0.123, 0.234, 0.345, 0.456, 0.567]

- Stream API의 특징

선언형 : 더 간결하고 가독성이 좋아진다.
조립할 수 있음 : 유연성이 좋아진다.
병렬화 : 성능이 좋아진다.

- Collectors 클래스의 메서드

- Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 지정한다.
명칭용도초기 데이터예시 코드예시 적용 후 데이터
toList()스트림의 요소들을 List로 수집["apple", "banana", "cherry"]java List<String> words = Arrays.asList("apple", "banana", "cherry"); List<String> wordList = words.stream().collect(Collectors.toList()); System.out.println(wordList); ["apple", "banana", "cherry"]
toSet()스트림의 요소들을 Set으로 수집["apple", "banana", "apple", "cherry"]java List<String> words = Arrays.asList("apple", "banana", "apple", "cherry"); Set<String> wordSet = words.stream().collect(Collectors.toSet()); System.out.println(wordSet); ["banana", "apple", "cherry"] (중복 제거)
toMap()스트림의 요소들을 Map으로 수집, 키와 값을 생성하는 함수 제공["apple", "banana", "cherry"]java List<String> words = Arrays.asList("apple", "banana", "cherry"); Map<String, Integer> wordLengthMap = words.stream().collect(Collectors.toMap(word -> word, String::length)); System.out.println(wordLengthMap); {"apple": 5, "banana": 6, "cherry": 6} (단어 길이를 값으로 사용)
joining()스트림의 요소들을 하나의 문자열로 결합. 구분자, 프리픽스, 서픽스를 지정할 수 있음["apple", "banana", "cherry"]java List<String> words = Arrays.asList("apple", "banana", "cherry"); String result = words.stream().collect(Collectors.joining(", ")); System.out.println(result); "apple, banana, cherry" (구분자: ", ")
groupingBy()스트림의 요소들을 주어진 기준으로 그룹화하여 Map 형태로 반환["apple", "banana", "cherry", "date", "fig"]java List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig"); Map<Integer, List<String>> groupedByLength = words.stream().collect(Collectors.groupingBy(String::length)); System.out.println(groupedByLength); {3=[fig], 4=[date], 5=[apple, grape], 6=[banana, cherry]} (글자 길이 기준)
partitioningBy()스트림의 요소들을 조건에 맞는 두 그룹으로 나누어 Map<Boolean, List>로 반환[1, 2, 3, 4, 5, 6]java List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); Map<Boolean, List<Integer>> partitioned = numbers.stream().collect(Collectors.partitioningBy(n -> n % 2 == 0)); System.out.println(partitioned); {false=[1, 3, 5], true=[2, 4, 6]} (홀수/짝수 기준)
counting()그룹화된 데이터의 개수를 셈["apple", "banana", "cherry", "date", "fig"]java List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig"); Map<Integer, Long> wordCountByLength = words.stream().collect(Collectors.groupingBy(String::length, Collectors.counting())); System.out.println(wordCountByLength); {5=1, 6=2, 4=1, 3=1} (글자 길이 기준으로 각 그룹의 개수 계산)
summarizingInt()IntStream의 요약 통계를 계산 (합계, 평균, 최댓값, 최솟값, 개수)[1, 2, 3, 4, 5]java List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); IntSummaryStatistics stats = numbers.stream().collect(Collectors.summarizingInt(Integer::intValue)); System.out.println(stats); IntSummaryStatistics{count=5, sum=15, min=1, average=3.0, max=5}
reducing()스트림의 요소들을 결합하여 단일 값으로 축소 (예: 합계, 곱셈 등)[1, 2, 3, 4, 5]java List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); int sum = numbers.stream().collect(Collectors.reducing(0, Integer::sum)); System.out.println(sum); 15 (합계 계산)
summarizingDouble()DoubleStream의 요약 통계를 계산 (합계, 평균, 최댓값, 최솟값, 개수)[1.5, 2.5, 3.5, 4.5, 5.5]java List<Double> numbers = Arrays.asList(1.5, 2.5, 3.5, 4.5, 5.5); DoubleSummaryStatistics stats = numbers.stream().collect(Collectors.summarizingDouble(Double::doubleValue)); System.out.println(stats); DoubleSummaryStatistics{count=5, sum=17.5, average=3.5, min=1.5, max=5.5}
summarizingLong()LongStream의 요약 통계를 계산 (합계, 평균, 최댓값, 최솟값, 개수)[1L, 2L, 3L, 4L, 5L]java List<Long> numbers = Arrays.asList(1L, 2L, 3L, 4L, 5L); LongSummaryStatistics stats = numbers.stream().collect(Collectors.summarizingLong(Long::longValue)); System.out.println(stats); LongSummaryStatistics{count=5, sum=15, average=3.0, min=1, max=5}
mapping()스트림의 각 요소를 주어진 함수에 의해 변환하고, 결과를 다른 컬렉션으로 수집["apple", "banana", "cherry"]java List<String> words = Arrays.asList("apple", "banana", "cherry"); Set<Integer> wordLengths = words.stream().collect(Collectors.mapping(String::length, Collectors.toSet())); System.out.println(wordLengths); {5, 6, 4} (각 단어의 길이를 Set으로 수집)
flatMapping()Stream의 요소를 변환한 후, 그 결과를 평탄화하여 수집[[1, 2], [3, 4]]java List<List<Integer>> lists = Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)); List<Integer> flatList = lists.stream().collect(Collectors.flatMapping(Collection::stream, Collectors.toList())); System.out.println(flatList); [1, 2, 3, 4] (리스트를 평탄화하여 List로 수집)
toConcurrentMap()스트림의 요소들을 ConcurrentMap으로 수집["apple", "banana", "cherry"]java List<String> words = Arrays.asList("apple", "banana", "cherry"); Map<String, Integer> wordLengthMap = words.stream().collect(Collectors.toConcurrentMap(word -> word, String::length)); System.out.println(wordLengthMap); {"apple": 5, "banana": 6, "cherry": 6} (단어 길이를 값으로 사용)
filtering()스트림의 요소들을 필터링하고 특정 조건을 만족하는 요소들만 수집["apple", "banana", "cherry", "date", "fig"]java List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig"); Set<String> result = words.stream().collect(Collectors.filtering(word -> word.startsWith("b"), Collectors.toSet())); System.out.println(result); [banana] (글자 'b'로 시작하는 단어들만 필터링)

- collect vs reduce

- collect 메서드는 결과를 **누적**하는 컨테이너를 변경하도록 설계된 메서드
- reduce 메서드는 두 값을 **하나**로 도출하는 **불변형** 연산하는 메서드

5. 디폴트 메서드

// 인터페이스 정의
interface MyInterface {
    // 디폴트 메서드
    default void defaultMethod() {
        System.out.println("디폴트 메서드가 호출되었습니다.");
    }

    // 추상 메서드
    void abstractMethod();
}

// 인터페이스를 구현하는 클래스
class MyClass implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("추상 메서드가 구현되었습니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        
        // 추상 메서드 호출
        obj.abstractMethod();
		
        // 디폴트 메서드 호출
        obj.defaultMethod();
    }
}

결과 :

추상 메서드가 구현되었습니다.
디폴트 메서드가 호출되었습니다.

만약 MyClass에 defaultMethod 매서드를 선언해서 사용할 경우는 오버라이딩된다.

class MyClass implements MyInterface {
    ...
    @Override
    public void abstractMethod() {
        System.out.println("추상 메서드가 구현되었습니다.");
    }
}
public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.abstractMethod();
        obj.defaultMethod();
    }
}
추상 메서드가 구현되었습니다.
MyClass에서 오버라이드한 디폴트 메서드가 호출되었습니다.

TIP. 자주쓰이는 문법

  1. List 정렬
list.sort(comparing(Sample::getCode)
               // .reversed()
                .thenComparing(Sample::getQty)
                .reversed());

comparing 함수를 통해 정렬을 할 수 있다.
reversed가 있으면 역순으로도 정렬이 되며, thenComparing를 이용하여 앞의 경우가 동일할 때의 정렬 순서도 정할 수 있다.

profile
끊임없이 나아가는 사람이 되어볼게요.

0개의 댓글