자바의 스트림(Stream) API는 데이터를 다룰 때 효율적이고 간결한 처리를 가능하게 합니다. 이번 글에서는 스트림 요소 정렬, 요소를 하나씩 처리하는 루핑, 요소의 조건 만족 여부 검사에 대해 살펴보겠습니다.


🔹 1. 요소 정렬 (Sorting)

스트림에서 요소를 정렬하려면 sorted() 메소드를 사용합니다.

📌 sorted() 메소드 종류

메소드 설명
sorted() 요소가 Comparable을 구현한 경우 자동 정렬
sorted(Comparator<T> comparator) Comparator를 사용하여 맞춤형 정렬

📌 예제 1: 기본 정렬 (Comparable이 구현된 경우)

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .sorted() // 알파벳순 정렬
     .forEach(System.out::println);

// 결과:
// C++
// Java
// Kotlin
// Python
  • String 클래스는 Comparable을 구현하고 있어 sorted()만 호출해도 정렬됩니다.

📌 예제 2: Comparator를 사용한 정렬 (내림차순)

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .sorted(Comparator.reverseOrder()) // 내림차순 정렬
     .forEach(System.out::println);

// 결과:
// Python
// Kotlin
// Java
// C++

📌 예제 3: 객체 정렬 (Comparable 구현)

객체가 Comparable을 구현하면 sorted()로 정렬할 수 있습니다.

class Student implements Comparable<Student> {
    String name;
    int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.score, other.score); // 점수 오름차순 정렬
    }
}

List<Student> students = List.of(
    new Student("홍길동", 85),
    new Student("이순신", 90),
    new Student("강감찬", 80)
);

students.stream()
        .sorted() // 점수 오름차순 정렬
        .forEach(s -> System.out.println(s.name + ": " + s.score));

// 결과:
// 강감찬: 80
// 홍길동: 85
// 이순신: 90

📌 예제 4: Comparator를 사용한 정렬

객체가 Comparable을 구현하지 않아도 Comparator를 직접 제공하면 정렬이 가능합니다.

students.stream()
        .sorted(Comparator.comparingInt(s -> s.score)) // 점수 오름차순 정렬
        .forEach(s -> System.out.println(s.name + ": " + s.score));

// 내림차순 정렬
students.stream()
        .sorted((s1, s2) -> Integer.compare(s2.score, s1.score))
        .forEach(s -> System.out.println(s.name + ": " + s.score));

 


🔹 2. 요소를 하나씩 처리 (루핑)

스트림에서 요소를 하나씩 처리하는 방법은 크게 두 가지입니다.

📌 forEach() vs peek()

메소드 설명
forEach(Consumer<T>) 최종 처리 메소드 (출력, 저장 등 최종 작업)
peek(Consumer<T>) 중간 처리 메소드 (디버깅, 중간 확인 용도)

📌 예제 1: forEach() - 최종 처리

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .forEach(System.out::println); // 요소 하나씩 출력
  • forEach()는 최종 처리 메소드이므로 스트림이 끝납니다.

📌 예제 2: peek() - 중간 처리 (디버깅 용도)

List<String> names = List.of("Java", "Python", "C++", "Kotlin");

names.stream()
     .peek(name -> System.out.println("처리 전: " + name)) // 중간 출력
     .sorted()
     .forEach(name -> System.out.println("처리 후: " + name));
  • peek()은 스트림 요소를 변환하지 않고 중간 상태를 확인하는 데 사용됩니다.
  • 반드시 forEach() 같은 최종 처리 메소드가 뒤에 와야 동작합니다.

📌 예제 3: peek() 활용 (짝수 필터링 후 합산)

int sum = IntStream.of(1, 2, 3, 4, 5)
                   .filter(n -> n % 2 == 0) // 짝수 필터링
                   .peek(n -> System.out.println("필터링된 값: " + n))
                   .sum(); // 최종 연산

// 결과:
// 필터링된 값: 2
// 필터링된 값: 4
// sum = 6

 


🔹 3. 요소 조건 만족 여부 (매칭)

스트림의 모든 요소가 특정 조건을 만족하는지 확인하는 기능입니다.

📌 매칭 메소드 종류

메소드 설명
allMatch(Predicate<T>) 모든 요소가 조건을 만족하는지 검사
anyMatch(Predicate<T>) 하나라도 조건을 만족하는 요소가 있는지 검사
noneMatch(Predicate<T>) 모든 요소가 조건을 만족하지 않는지 검사

📌 예제 1: allMatch() - 모든 요소가 조건을 만족하는지 검사

boolean allEven = IntStream.of(2, 4, 6, 8, 10)
                           .allMatch(n -> n % 2 == 0); // 모든 요소가 짝수인가?

System.out.println(allEven); // true

📌 예제 2: anyMatch() - 하나라도 조건을 만족하는 요소가 있는지 검사

boolean hasOdd = IntStream.of(2, 4, 6, 7, 10)
                          .anyMatch(n -> n % 2 != 0); // 홀수가 하나라도 있는가?

System.out.println(hasOdd); // true

📌 예제 3: noneMatch() - 모든 요소가 조건을 만족하지 않는지 검사

boolean noNegative = IntStream.of(2, 4, 6, 8, 10)
                              .noneMatch(n -> n < 0); // 음수가 없는가?

System.out.println(noNegative); // true

🎯 정리

기능 메소드 설명
정렬 sorted() 기본 정렬 (Comparable 필요)
sorted(Comparator<T>) 커스텀 정렬
요소 개별 처리 forEach() 최종 처리 (출력, 저장 등)
peek() 중간 처리 (디버깅, 확인 용도)
조건 검사 allMatch(Predicate<T>) 모든 요소가 조건을 만족하는지 확인
anyMatch(Predicate<T>) 하나라도 조건을 만족하는지 확인
noneMatch(Predicate<T>) 모든 요소가 조건을 만족하지 않는지 확인

 

스트림을 활용하면 데이터를 보다 효율적으로 정렬하고, 개별 요소를 처리하고, 조건을 검사할 수 있습니다. 이를 잘 활용하면 복잡한 루프를 최소화하면서도 가독성이 좋은 코드를 작성할 수 있습니다. 🚀

 

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

 

 

자바의 스트림(Stream)은 컬렉션, 배열, 파일 등의 데이터를 다룰 때 매우 강력한 기능을 제공합니다.

이번 글에서는 리소스로부터 스트림을 얻는 방법, 요소를 필터링하는 방법, 그리고 요소를 변환하는 방법(매핑) 에 대해 다룹니다.


🔹 1. 리소스로부터 스트림 얻기

스트림은 꼭 컬렉션(Collection)에서만 생성할 수 있는 것이 아닙니다. 다양한 리소스(데이터를 저장하는 객체 또는 파일)에서 스트림을 생성할 수 있습니다.

📌 스트림 인터페이스 개요

자바의 스트림 패키지에는 BaseStream 인터페이스가 있고, 이를 상속하여 여러 스트림이 만들어집니다.

스트림 타입 설명
Stream<T> 객체 요소를 처리하는 스트림
IntStream int 기본 타입 요소가 흘러가는 스트림
LongStream long 기본 타입 요소가 흘러가는 스트림
DoubleStream double 기본 타입 요소가 흘러가는 스트림

📌 리소스별 스트림 생성 방법

자바에서 스트림을 생성할 수 있는 대표적인 리소스는 다음과 같습니다.

리소스 스트림 생성 방법
컬렉션(Collection) stream(), parallelStream()
배열(Array) Arrays.stream(array), Stream.of(array)
숫자 범위 IntStream.range(start, end), LongStream.rangeClosed(start, end)
파일(File) Files.lines(path, charset) (한 줄씩 읽어서 스트림 생성)
랜덤(Random) 값 new Random().ints(), new Random().doubles()

📌 컬렉션에서 스트림 얻기

List<String> list = List.of("Java", "Python", "C++", "Java");
Stream<String> stream = list.stream(); // 순차 스트림
Stream<String> parallelStream = list.parallelStream(); // 병렬 스트림

📌 배열에서 스트림 얻기

String[] array = {"Java", "Python", "C++"};
Stream<String> stream1 = Arrays.stream(array);
Stream<String> stream2 = Stream.of(array);

📌 숫자 범위에서 스트림 얻기

IntStream intStream = IntStream.range(1, 10); // 1~9 (끝 포함 X)
IntStream intStreamClosed = IntStream.rangeClosed(1, 10); // 1~10 (끝 포함 O)

📌 파일에서 스트림 얻기

Path path = Paths.get("data.txt");
Stream<String> fileStream = Files.lines(path, StandardCharsets.UTF_8);
fileStream.forEach(System.out::println);

🔹 2. 요소 걸러내기(필터링)

필터링은 스트림에서 특정 조건을 만족하는 요소만 걸러내는 중간 처리 작업입니다.

📌 필터링 메소드

메소드 설명
distinct() 중복 제거
filter(Predicate<T> predicate) 특정 조건에 맞는 요소만 선택

📌 예제 1: 중복 제거

List<String> names = List.of("Java", "Python", "Java", "C++", "Python");
names.stream()
     .distinct()
     .forEach(System.out::println); 
// 결과: Java, Python, C++

📌 예제 2: 특정 조건의 요소 필터링

List<String> names = List.of("신용권", "홍길동", "신사임당", "이순신");
names.stream()
     .filter(name -> name.startsWith("신"))
     .forEach(System.out::println);
// 결과: 신용권, 신사임당

📌 예제 3: 숫자 필터링

IntStream.rangeClosed(1, 10)
         .filter(n -> n % 2 == 0) // 짝수만 선택
         .forEach(System.out::println);
// 결과: 2, 4, 6, 8, 10

🔹 3. 요소 변환(매핑)

맵핑은 스트림의 요소를 변환하여 새로운 스트림을 만드는 과정입니다.

📌 변환 메소드

메소드 설명
map(Function<T, R> mapper) 객체 -> 다른 객체 변환
mapToInt(ToIntFunction<T> mapper) 객체 -> int 변환
mapToLong(ToLongFunction<T> mapper) 객체 -> long 변환
mapToDouble(ToDoubleFunction<T> mapper) 객체 -> double 변환
flatMap(Function<T, Stream<R>> mapper) 하나의 요소 -> 여러 개의 요소 변환

📌 예제 1: 객체를 특정 값으로 변환

 
class Student {
    String name;
    int score;
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    public int getScore() {
        return score;
    }
}

List<Student> students = List.of(
    new Student("홍길동", 85),
    new Student("이순신", 90),
    new Student("강감찬", 80)
);

students.stream()
        .mapToInt(Student::getScore) // 객체 -> int 변환
        .forEach(System.out::println);
// 결과: 85, 90, 80

📌 예제 2: 문자열을 단어 단위로 변환 (flatMap)

List<String> sentences = List.of("I am a Java Developer", "Learning Java Streams");

sentences.stream()
         .flatMap(sentence -> Arrays.stream(sentence.split(" "))) // 문장을 단어로 분리
         .forEach(System.out::println);
// 결과: I, am, a, Java, Developer, Learning, Java, Streams

🎯 정리

 

기능
메소드 설명
스트림 생성 stream() 컬렉션에서 스트림 생성
Arrays.stream() 배열에서 스트림 생성
Files.lines() 파일에서 스트림 생성
필터링 distinct() 중복 제거
filter() 조건에 맞는 요소만 선택
변환(매핑) map() 요소를 변환하여 새로운 스트림 생성
flatMap() 하나의 요소를 여러 개로 변환

 

자바 스트림을 활용하면 데이터를 보다 효율적으로 처리할 수 있습니다.

특히 컬렉션, 배열, 파일 등의 데이터를 스트림으로 변환하여 필터링 및 변환하는 과정을 잘 이해하면 더 강력한 프로그래밍이 가능합니다.

 

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

 

 

자바 8부터 등장한 스트림(Stream) API컬렉션 데이터를 효율적으로 처리할 수 있도록 설계된 기능입니다.
스트림을 활용하면 코드를 간결하게 작성할 수 있으며, 병렬 처리(Parallel Processing)도 용이합니다.

이번 포스트에서는 스트림의 개념과 내부 반복자, 중간 처리 & 최종 처리에 대해 자세히 알아보겠습니다.

✅ 1. 스트림이란?

🎯 1. 스트림(Stream)이란?

스트림(Stream)은 "데이터의 흐름"을 의미합니다.
데이터가 하나씩 흐르면서 처리되는 방식을 제공하는 것이 스트림 API입니다.
객체를 하나씩 흘려보내면서 처리하는 개념
컬렉션(리스트, 셋 등)에서 데이터를 하나씩 읽어와 처리하는 방식
✔ 기존 for문, while문을 이용한 외부 반복 방식과 다르게 내부 반복을 사용


🎯 2. 기존 방식(외부 반복자) vs. 스트림(내부 반복자)

외부 반복자(기존 방식)

List<String> list = Arrays.asList("홍길동", "김길동", "박길동");

// for문 사용 (외부 반복자)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 향상된 for문 사용
for (String name : list) {
    System.out.println(name);
}

for문을 사용하여 요소를 하나씩 가져와 직접 처리
데이터를 가져오는 작업이 코드에 포함됨 (수동적 반복)

내부 반복자(스트림 방식)

list.stream().forEach(name -> System.out.println(name));

데이터를 직접 가져오는 것이 아니라 "흘러가는 데이터"를 처리하는 방식
컬렉션 내부에서 자동으로 데이터를 하나씩 흘려보내면서 처리
람다식을 활용하여 간결한 코드 작성 가능


✅ 2. 내부 반복자 (Internal Iterator)

스트림 API내부 반복자를 활용하여 데이터를 자동으로 처리합니다.
내부 반복자는 컬렉션 내부에서 요소를 처리하는 방식을 의미합니다.

🎯 1. 외부 반복자 vs 내부 반복자 비교

반복 방식 코드 예시 특징
외부 반복자for (String s : list) {}직접 요소를 하나씩 가져와서 처리
내부 반복자list.stream().forEach(s -> {})요소가 스트림을 따라 자동으로 처리됨

🎯 2. 내부 반복자 코드 예제

 
import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("홍길동", "김길동", "박길동");

        // 스트림을 이용한 내부 반복자
        list.stream().forEach(name -> System.out.println(name));
    }
}

스트림을 생성한 후 forEach()를 통해 요소를 하나씩 처리
코드가 훨씬 간결해지고, 유지보수도 쉬워짐


🎯 3. 내부 반복자가 더 좋은 이유

코드가 간결하고 직관적
데이터를 가져오는 과정이 자동화됨 → 속도 향상
병렬 처리가 용이하여 성능 최적화 가능


✅ 3. 중간 처리 & 최종 처리

🎯 1. 스트림의 처리 과정

스트림의 데이터 처리는 "중간 처리" → "최종 처리" 단계로 나뉩니다.

처리 단계
설명 예제
중간 처리데이터를 가공하는 과정 (필터링, 변환 등)filter(), map()
최종 처리데이터를 최종적으로 사용하는 과정 (출력, 집계 등)forEach(), sum(), count()

 


🎯 2. 중간 처리 (Intermediate Operations)

중간 처리스트림을 변환하는 과정입니다.
대표적인 중간 처리 메소드로는 filter(), map(), sorted() 등이 있습니다.

필터링(filter) 예제

List<String> names = Arrays.asList("홍길동", "김철수", "이영희");

// 이름이 "김"으로 시작하는 데이터만 필터링
names.stream()
    .filter(name -> name.startsWith("김"))
    .forEach(System.out::println);

 
filter() : 조건에 맞는 요소만 걸러서 새로운 스트림 생성

변환(map) 예제

List<String> names = Arrays.asList("홍길동", "김철수", "이영희");

// 모든 이름을 대문자로 변환
names.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);

map() : 요소를 다른 형태로 변환하여 새로운 스트림 생성


🎯 3. 최종 처리 (Terminal Operations)

최종 처리스트림의 데이터를 집계하거나 출력하는 과정입니다.
대표적인 최종 처리 메소드로는 forEach(), sum(), count(), average() 등이 있습니다.

집계(count, sum, average) 예제

import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;

public class StreamAggregationExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        // 개수 카운트
        long count = numbers.stream().count();
        System.out.println("개수: " + count);  // 출력: 개수: 5

        // 총합
        int sum = numbers.stream().mapToInt(Integer::intValue).sum();
        System.out.println("총합: " + sum);  // 출력: 총합: 150

        // 평균
        OptionalDouble avg = numbers.stream().mapToInt(Integer::intValue).average();
        System.out.println("평균: " + avg.getAsDouble());  // 출력: 평균: 30.0
    }
}
 

count() : 요소 개수 구하기
sum() : 총합 구하기
average() : 평균 구하기



🎯 4. 중간 처리 + 최종 처리 (스트림 파이프라인)

중간 처리와 최종 처리를 연결하여 사용(파이프라인)할 수 있습니다.

 
List<String> names = Arrays.asList("홍길동", "김철수", "이영희");

// "김"으로 시작하는 이름만 필터링 후 대문자로 변환하여 출력
names.stream()
    .filter(name -> name.startsWith("김"))
    .map(String::toUpperCase)
    .forEach(System.out::println);
 

중간 처리(filter + map) : "김"으로 시작하는 데이터를 필터링 후 대문자로 변환
최종 처리(forEach) : 변환된 데이터를 출력


✅ 결론 (왜 스트림을 사용할까?)

내부 반복자 방식으로 데이터 처리 속도가 빠름
람다식과 함께 사용하여 코드가 간결하고 가독성이 좋음
필터링, 변환, 정렬, 집계 등 다양한 데이터 처리를 쉽게 구현 가능
병렬 처리(Parallel Processing)가 쉬움
 
 
 
 
 

참조:
이것이 자바다 _ 신용권

 

자바 8부터 등장한 람다식은 코드의 간결함을 추구하지만,
때때로 가독성이 떨어질 수 있습니다.
이 문제를 해결하기 위해 메소드 참조(Method Reference)와 생성자 참조(Constructor Reference)가 도입되었습니다.
이번 포스트에서는 메소드 참조와 생성자 참조의 개념과 활용법을 자세히 알아보겠습니다!

✅ 1. 메소드 참조 (Method Reference)

람다식에서 불필요한 매개변수를 제거하여 코드의 가독성을 높이는 기능입니다.
즉, 이미 존재하는 메소드를 람다식에서 직접 참조하여 사용하는 방식입니다.

🎯 1. 메소드 참조의 기본 문법

클래스이름::메소드이름       // 정적 메소드 참조
참조변수::메소드이름        // 인스턴스 메소드 참조

메소드 참조는 ::(콜론 두 개) 문법을 사용합니다.
정적 메소드와 인스턴스 메소드에서 다르게 동작합니다.


🎯 2. 정적 메소드 참조 (Static Method Reference)

정적 메소드를 참조할 때는 클래스 이름과 메소드 이름을 :: 연산자로 연결합니다.

정적 메소드 예제

import java.util.function.BiFunction;

public class MethodReferenceExample {
    public static double findMax(double a, double b) {
        return Math.max(a, b);
    }

    public static void main(String[] args) {
        // 람다식 방식
        BiFunction<Double, Double, Double> lambdaMax = (a, b) -> Math.max(a, b);
        
        // 메소드 참조 방식
        BiFunction<Double, Double, Double> referenceMax = Math::max;

        System.out.println(lambdaMax.apply(3.5, 7.2));  // 출력: 7.2
        System.out.println(referenceMax.apply(3.5, 7.2));  // 출력: 7.2
    }
}

✔ Math.max(a, b)는 두 개의 값을 받아 더 큰 값을 반환하는 정적 메소드입니다.
✔ Math::max를 사용하여 불필요한 람다식의 매개변수를 제거하고 더 간결한 코드로 작성했습니다.


 

🎯 3. 인스턴스 메소드 참조 (Instance Method Reference)

객체(인스턴스)의 메소드를 참조하는 방식입니다.

인스턴스 메소드 예제

import java.util.function.Function;

public class InstanceMethodReferenceExample {
    public String toUpperCase(String str) {
        return str.toUpperCase();
    }

    public static void main(String[] args) {
        InstanceMethodReferenceExample example = new InstanceMethodReferenceExample();

        // 람다식 방식
        Function<String, String> lambdaFunc = (s) -> example.toUpperCase(s);

        // 메소드 참조 방식
        Function<String, String> referenceFunc = example::toUpperCase;

        System.out.println(lambdaFunc.apply("hello"));  // 출력: HELLO
        System.out.println(referenceFunc.apply("hello"));  // 출력: HELLO
    }
}

✔ toUpperCase() 메소드는 문자열을 대문자로 변환하는 인스턴스 메소드입니다.
✔ example::toUpperCase를 사용하여 더 간결한 형태로 메소드 참조를 적용했습니다.


🎯 4. 특정 객체의 메소드 참조

일반적으로 두 개의 매개변수가 있는 람다식을 단순화할 때 활용합니다.

예제

import java.util.Comparator;

public class StringSortExample {
    public static void main(String[] args) {
        Comparator<String> lambdaComparator = (s1, s2) -> s1.compareToIgnoreCase(s2);
        Comparator<String> referenceComparator = String::compareToIgnoreCase;

        System.out.println(lambdaComparator.compare("apple", "Banana"));  // 출력: -1
        System.out.println(referenceComparator.compare("apple", "Banana"));  // 출력: -1
    }
}

✔ compareToIgnoreCase()는 두 개의 문자열을 비교하는 인스턴스 메소드입니다.
람다식: (s1, s2) -> s1.compareToIgnoreCase(s2)
메소드 참조: String::compareToIgnoreCase
가독성이 훨씬 향상되었습니다.


✅ 2. 생성자 참조 (Constructor Reference)

메소드 참조뿐만 아니라 생성자도 참조할 수 있습니다.
즉, 람다식에서 객체를 생성하는 부분을 단순화할 수 있습니다.

🎯 1. 생성자 참조 기본 문법

클래스이름::new

클래스 이름과 new를 :: 연산자로 연결하여 사용합니다.
✔ 인터페이스의 추상 메소드 시그니처(매개변수 개수, 타입)에 맞는 생성자가 호출됩니다.


🎯 2. 기본 생성자 참조

람다식 방식

Supplier<Person> lambdaConstructor = () -> new Person();

생성자 참조 방식

Supplier<Person> referenceConstructor = Person::new;

람다식에서 new Person()을 반환하는 것과 동일합니다.
✔ Person::new를 사용하여 더 깔끔하게 표현 가능합니다.


🎯 3. 매개변수가 있는 생성자 참조

생성자에 매개변수가 있는 경우 람다식에서 매개변수를 그대로 전달하는 구조라면 생성자 참조를 사용할 수 있습니다.

예제

import java.util.function.Function;

class Person {
    String name;

    public Person(String name) {
        this.name = name;
    }
}

public class ConstructorReferenceExample {
    public static void main(String[] args) {
        // 람다식 방식
        Function<String, Person> lambdaConstructor = (name) -> new Person(name);

        // 생성자 참조 방식
        Function<String, Person> referenceConstructor = Person::new;

        Person p1 = lambdaConstructor.apply("Alice");
        Person p2 = referenceConstructor.apply("Bob");

        System.out.println(p1.name);  // 출력: Alice
        System.out.println(p2.name);  // 출력: Bob
    }
}

람다식: (name) -> new Person(name)
생성자 참조: Person::new
매개변수를 받아 그대로 전달하는 형태라면 생성자 참조를 적용 가능


🎯 메소드 참조 vs 람다식 비교 정리

유형
람다식 방식메소드 참조 방식
정적 메소드(a, b) -> Math.max(a, b)Math::max
인스턴스 메소드(s) -> s.toUpperCase()String::toUpperCase
특정 객체의 메소드(a, b) -> a.compareToIgnoreCase(b)String::compareToIgnoreCase
기본 생성자() -> new Person()Person::new
매개변수 있는 생성자(name) -> new Person(name)Person::new

 


🎯 결론

메소드 참조람다식을 더 간결하고 가독성 좋게 만들어주는 기능입니다.
생성자 참조객체를 생성할 때 불필요한 표현을 줄이고 직관적으로 작성할 수 있도록 도와줍니다.
매개변수의 개수와 순서를 그대로 전달하는 경우에만 사용 가능하다는 점을 주의해야 합니다.
 
 
 
 
 
 
 

참조:
이것이 자바다 _ 신용권

 
 

자바에서 람다식(lambda expression)은 함수형 프로그래밍을 지원하기 위해 도입된 기능으로,
코드를 간결하게 작성하고 가독성을 높이는 데 중요한 역할을 합니다.

이번 포스트에서는
매개변수가 없는 람다식, 매개변수가 있는 람다식, 리턴값이 있는 람다식을 중심으로 람다식의 개념과 활용법을 정리해 보겠습니다.

🔹 1. 매개변수가 없는 람다식

람다식은 함수형 인터페이스를 기반으로 동작합니다.
함수형 인터페이스란, "하나의 추상 메소드"만을 포함하는 인터페이스를 의미하며, 대표적인 예시로 Runnable이 있습니다.

람다식 기본 형태

@FunctionalInterface
public interface Workable {
    void work();  // 추상 메소드 (매개변수 없음)
}

이제, 위 인터페이스를 구현하는 방법을 보겠습니다.

익명 객체 방식 (전통적인 방식)

Workable worker = new Workable() {
    @Override
    public void work() {
        System.out.println("일을 합니다.");
    }
};
worker.work();  // 출력: 일을 합니다.

위와 같은 코드가 람다식을 사용하면 훨씬 간결해집니다.

람다식 사용 방식

Workable worker = () -> {
    System.out.println("일을 합니다.");
};
worker.work();  // 출력: 일을 합니다.

람다식에서는
매개변수가 없으면 ()를 사용하고,
실행문이 하나라면 {}를 생략할 수 있습니다.

Workable worker = () -> System.out.println("일을 합니다.");

✔ 실행문이 2개 이상일 경우 반드시 {}를 사용해야 합니다.


🔹 2. 매개변수가 있는 람다식

람다식에서 매개변수가 있는 경우, 메소드의 매개변수와 동일한 형태로 작성하면 됩니다.

람다식 기본 형태

@FunctionalInterface
public interface Speakable {
    void speak(String message);
}

이 인터페이스를 구현하는 방법을 보겠습니다.

익명 객체 방식

Speakable speaker = new Speakable() {
    @Override
    public void speak(String message) {
        System.out.println("말하기: " + message);
    }
};
speaker.speak("안녕하세요!");  // 출력: 말하기: 안녕하세요!

람다식 사용 방식

Speakable speaker = (message) -> {
    System.out.println("말하기: " + message);
};
speaker.speak("안녕하세요!");  // 출력: 말하기: 안녕하세요!

매개변수가 1개라면 ()를 생략할 수 있습니다.

 
Speakable speaker = message -> System.out.println("말하기: " + message);

매개변수가 2개 이상이면 반드시 ()를 사용해야 합니다.
✔ 실행문이 2개 이상이면 {}를 사용해야 합니다.


🔹 3. 리턴값이 있는 람다식

람다식에서 리턴값을 반환하는 경우, return 키워드를 활용합니다.

람다식 기본 형태

@FunctionalInterface
public interface Calculable {
    double calculate(double x, double y);
}

이제 위 인터페이스를 구현하는 다양한 방법을 보겠습니다.

익명 객체 방식

Calculable calculator = new Calculable() {
    @Override
    public double calculate(double x, double y) {
        return x + y;
    }
};
System.out.println(calculator.calculate(10, 5));  // 출력: 15.0

람다식 사용 방식

Calculable calculator = (x, y) -> {
    return x + y;
};
System.out.println(calculator.calculate(10, 5));  // 출력: 15.0

리턴문만 존재하는 경우 return과 {}를 생략할 수 있습니다.

Calculable calculator = (x, y) -> x + y;

메소드 참조를 활용하면 더욱 간결하게 작성할 수 있습니다.

Calculable calculator = Double::sum;
System.out.println(calculator.calculate(10, 5));  // 출력: 15.0

 

🔹 4. 람다식 활용 예제 (버튼 클릭 이벤트)

람다식은 GUI 프로그래밍에서 이벤트 처리에도 자주 사용됩니다.
예를 들어, onClickListener를 설정할 때 익명 객체를 사용하던 방식이 람다식으로 간단하게 변경될 수 있습니다.

익명 객체 방식

Button button = new Button();
button.setOnClickListener(new Button.ClickListener() {
    @Override
    public void onClick() {
        System.out.println("버튼이 클릭되었습니다.");
    }
});

람다식 사용 방식

button.setOnClickListener(() -> System.out.println("버튼이 클릭되었습니다."));

코드가 훨씬 간결해지며 가독성이 좋아집니다.
✔ 안드로이드 개발에서도 람다식이 매우 자주 사용됩니다.


🔹 5. 람다식의 장점과 활용

코드가 간결해진다 – 불필요한 익명 객체 코드를 줄이고 가독성을 높임.
함수형 프로그래밍 지원 – 스트림 API, 컬렉션 API와 결합하여 강력한 기능 제공.
이벤트 처리 간소화 – UI 프로그래밍에서 클릭 이벤트 등을 간단하게 처리 가능.
병렬 처리에 용이 – 멀티코어 환경에서 병렬 프로그래밍 활용 가능.
 
 
 
 
 
 

참조:
이것이 자바다 _ 신용권

 
 
 
 

 

🔥 람다식이란?

람다식(Lambda Expression)은 자바 8(Java 8)에서 도입된 기능으로,
익명 함수(Anonymous Function)를 보다 간결하게 표현할 수 있는 문법
함수형 프로그래밍(Functional Programming)을 지원
코드를 간결하게 작성할 수 있도록 도와주는 기법


🏗 1. 함수형 프로그래밍(Functional Programming)이란?

함수형 프로그래밍이란?

  • 프로그램을 함수(메소드) 단위로 작성하고, 데이터 처리를 함수에 맡기는 방식
  • 메소드(객체 소속)와 함수(독립적 실행 코드)의 차이
    • 메소드: 반드시 클래스 내부에 존재해야 함
    • 함수: 클래스와 무관하게 독립적으로 실행될 수 있음

📌 자바는 원래 객체지향 언어이지만, 함수형 프로그래밍을 지원하기 위해 람다식을 도입


🎯 2. 람다식의 기본 개념

람다식은 이름이 없는 함수(익명 함수)를 표현하는 방법입니다.

일반적으로 다음과 같은 형태로 작성됩니다.
📌 람다식 기본 문법

(매개변수) -> { 실행 코드 }

 
예제 1: 두 수를 더하는 람다식

(int x, int y) -> { return x + y; }

 
예제 2: 매개변수가 하나인 경우

x -> { return x * 2; }

 
예제 3: 실행 코드가 한 줄이면 중괄호 생략 가능

(x, y) -> x + y

 


🛠 3. 람다식과 익명 구현 객체의 관계

자바에서는 람다식을 익명 구현 객체(Anonymous Implementation Object)로 변환하여 실행합니다.
즉, 람다식은 사실상 익명 클래스를 줄여서 표현하는 방법입니다.
 
📌 익명 구현 객체 방식

interface Calculator {
    int calculate(int x, int y);
}

public class Main {
    public static void main(String[] args) {
        Calculator add = new Calculator() {
            @Override
            public int calculate(int x, int y) {
                return x + y;
            }
        };
        System.out.println(add.calculate(5, 3)); // 8
    }
}

 
위 코드를 람다식으로 변환

Calculator add = (x, y) -> x + y;
System.out.println(add.calculate(5, 3)); // 8

 
📌 결과는 동일하지만 코드가 훨씬 간결해짐! 🚀


🎯 4. 람다식을 사용하기 위한 조건

람다식은 함수형 인터페이스(Functional Interface)에서만 사용 가능합니다.
즉, 추상 메소드가 1개만 있는 인터페이스에서만 람다식을 사용할 수 있습니다.
 
📌 함수형 인터페이스(Functional Interface) 예제

@FunctionalInterface
interface Calculator {
    int calculate(int x, int y);
}

추상 메소드가 1개만 존재하므로 람다식 사용 가능!
만약 2개 이상의 추상 메소드가 존재하면 람다식 사용 불가능!


🏗 5. 람다식을 활용한 데이터 처리

람다식을 사용하면 데이터 처리 방식을 함수로 전달할 수 있음
즉, 함수를 변수처럼 사용 가능!
 
📌 람다식을 활용한 데이터 처리 예제

public class Main {
    public static void processNumbers(int a, int b, Calculator calculator) {
        int result = calculator.calculate(a, b);
        System.out.println("결과: " + result);
    }

    public static void main(String[] args) {
        processNumbers(5, 3, (x, y) -> x + y); // 더하기
        processNumbers(5, 3, (x, y) -> x - y); // 빼기
        processNumbers(5, 3, (x, y) -> x * y); // 곱하기
    }
}​
 

함수를 변수처럼 전달하여 유연하게 데이터 처리 가능!


🛠 6. 자바 표준 함수형 인터페이스

자바는 람다식을 쉽게 사용할 수 있도록 표준 함수형 인터페이스를 제공합니다.

📌 대표적인 함수형 인터페이스

인터페이스 추상 메소드 설명
Function<T, R>apply(T t)입력값을 받아 변환하여 반환
Consumer<T>accept(T t)입력값을 받아 처리 (반환값 없음)
Supplier<T>get()매개변수 없이 결과를 반환
Predicate<T>test(T t)조건식을 검사하여 true 또는 false 반환

 
예제: Function 인터페이스 활용

Function<String, Integer> lengthFunction = s -> s.length();
System.out.println(lengthFunction.apply("Hello")); // 5

 
예제: Consumer 인터페이스 활용

Consumer<String> printConsumer = s -> System.out.println(s);
printConsumer.accept("Hello, World!"); // Hello, World!​

 

 
예제: Predicate 인터페이스 활용

Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(5)); // false

 


🏁 정리: 람다식의 개념과 활용

📌 람다식(Lambda Expression)은 자바 8부터 도입된 기능으로, 익명 함수를 표현하는 방법
📌 함수형 인터페이스(Functional Interface)에서만 사용 가능 (추상 메소드 1개 필요)
📌 람다식을 사용하면 코드가 간결해지고, 데이터 처리 방식이 유연해짐
📌 자바에서 표준 함수형 인터페이스를 제공하여 람다식 활용을 쉽게 지원


이 블로그 포스트를 통해 배운 점

  • 람다식이 무엇이며, 왜 필요한지 이해했다.
  • 기존 익명 구현 객체 방식과 비교하여 코드가 간결해지는 것을 확인했다.
  • 자바의 표준 함수형 인터페이스를 활용하면 코드 재사용성이 높아진다는 점을 배웠다.
  • 람다식을 사용하여 데이터 처리 방식을 함수로 전달할 수 있음을 학습했다.

 
 
 
 
 

참조:
이것이 자바다 _ 신용권

 
 
 

+ Recent posts