🧩 자바의 필드(Field)와 프로퍼티(Property) 차이 완벽 정리 - getter/setter와 실제 변수, 무엇이 다를까?

 

자바로 객체 지향 프로그래밍을 하다 보면 "필드"와 "프로퍼티"라는 용어를 자주 접하게 됩니다.
두 용어가 비슷하게 사용되지만, 의미와 쓰임은 명확히 다릅니다.
이번 글에서는 이 둘의 개념과 차이를 정확히 정리해보겠습니다.


✅ 필드(Field)란?

필드는 클래스 내부에 정의된 멤버 변수(실제 데이터 저장소)입니다.
객체가 상태(state)를 가지기 위해 보유하는 값이며, 보통 private으로 선언됩니다.

public class Person {
    private String name; // ← 이게 필드!
}
  • name은 클래스 안에 존재하는 실제 데이터 공간
  • 클래스 내부에서 this.name처럼 직접 접근 가능

✅ 프로퍼티(Property)란?

프로퍼티는 객체 외부에서 getter/setter 메서드를 통해 간접적으로 접근하는 속성입니다.
즉, 필드와 연결된 외부 공개 인터페이스라고 볼 수 있습니다.

public class Person {
    private String name;

    public String getName() {
        return name; // ← 이게 name이라는 "프로퍼티"
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • getName()과 setName() 메서드는 name 필드의 프로퍼티 역할
  • 자바빈(JavaBeans) 규약에서는 이 메서드 집합을 "name 프로퍼티"라고 부릅니다.

📊 필드 vs 프로퍼티 비교 정리

구분 필드(Field) 프로퍼티(Property)
정의 클래스 내부에 선언된 멤버 변수 필드에 접근하기 위한 getter/setter 메서드 집합
접근 방식 직접 접근 (this.name) 간접 접근 (getName(), setName())
존재 유무 실제로 메모리에 존재 개념적으로 존재 (인터페이스로서의 접근 수단)
사용 목적 객체의 내부 상태 저장 외부에서 안전하게 상태를 읽거나 수정할 수 있도록 함
예시 private String name; getName(), setName(String name)

⚠️ 헷갈리지 마세요!

❗ "필드 = 변수", "프로퍼티 = 변수 + 접근자(getter/setter)" 개념입니다.
프로퍼티는 "상태" 자체라기보단, 상태를 다루는 접근 수단에 가까워요.


🛠️ 실무에서 왜 중요할까?

📌 예시 1: JPA에서의 접근 전략

JPA에서는 데이터를 읽고 쓸 때 어떻게 접근할지를 선택할 수 있습니다:

  • 필드 접근(Field Access) → 필드에 직접 접근
  • 프로퍼티 접근(Property Access) → getter/setter를 통해 접근
@Access(AccessType.PROPERTY)
@Entity
public class Member {

    private String name;

    @Column(name = "member_name")
    public String getName() {
        return name; // ← 프로퍼티 접근 방식
    }
}

✔ JPA는 이 getName()을 통해 name 필드를 읽고 씁니다.
따라서 어디에 어노테이션을 붙이느냐에 따라 접근 방식이 달라집니다.


📌 예시 2: JSON 직렬화 (Jackson, Gson 등)

Jackson도 기본적으로 getter를 기준으로 직렬화/역직렬화합니다.

public class User {
    private String name;

    public String getName() {
        return name; // ← 이걸 통해 JSON에 포함됨
    }
}

✔ 필드에 직접 접근할 수도 있지만, 설정에 따라 다르기 때문에 "필드냐, 프로퍼티냐"를 명확히 구분해야 합니다.


✅ 마무리 정리

항목 필드 프로퍼티
존재 방식 실제 객체 내부의 변수 getter/setter를 통해 노출되는 속성
객체 내부 사용 this.name 등으로 직접 접근 내부보단 외부에서 접근 시 활용
프레임워크 사용 시 JPA, Jackson 등에서 접근 방식 차이 발생 getter/setter에 따라 매핑 방식 달라짐

👉 필드 = 저장소, 프로퍼티 = 인터페이스
이 구분만 정확히 기억해도 실무에서 큰 도움이 됩니다.

 

 

 

1️⃣ 요소 병렬 처리란?

스트림을 사용하면 내부 반복자(Internal Iterator)를 통해 데이터를 자동으로 분할하여 처리할 수 있다.
이러한 병렬 처리(Parallel Processing)는 요소를 여러 개의 스트림으로 나누고, 각 스트림을 여러 개의 스레드에서 동시에 실행하는 방식이다.

🔹 병렬 처리의 목적

  • 대량의 데이터 처리 속도를 향상
  • 멀티코어 CPU의 성능을 극대화
  • 병렬 연산을 통해 빠른 결과 도출

하지만!
항상 병렬 처리가 빠른 것은 아니다.
적절한 상황에서 사용해야 효과적이다.


2️⃣ 동시성과 병렬성의 차이

💡 동시성(Concurrency)과 병렬성(Parallelism)의 개념을 정확히 이해해야 한다.

개념 설명
동시성 (Concurrency) 하나의 코어에서 여러 작업을 번갈아 가며 실행하여 마치 동시에 실행되는 것처럼 보이는 방식
병렬성 (Parallelism) 여러 개의 코어에서 여러 작업을 동시에 실행하는 방식

 

🔸 동시성 예시
→ 하나의 코어에서 작업1 → 작업2 → 작업3 → 작업1 … 순차적으로 실행됨.
→ 여러 작업이 번갈아 실행되므로 실제로는 동시에 실행되지 않지만 빠르게 전환되므로 동시 실행처럼 보임.

 

🔸 병렬성 예시
코어 1에서 작업1 실행, 코어 2에서 작업2 실행
진짜 동시에 여러 작업을 실행하는 방식

 

💡 즉, 병렬 처리는 여러 개의 CPU 코어를 활용하여 작업을 동시에 수행하는 것이며, 성능 향상이 목적이다. 🚀


3️⃣ 병렬 스트림 (Parallel Stream)

자바는 병렬 처리를 위해 **병렬 스트림(Parallel Stream)**을 제공한다.

🔹 병렬 스트림을 사용하는 방법

1. 컬렉션에서 병렬 스트림을 생성

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = list.parallelStream(); // 병렬 스트림 생성

 

2. 기존 스트림을 병렬 스트림으로 변환

Stream<Integer> stream = list.stream();
Stream<Integer> parallelStream = stream.parallel(); // 기존 스트림을 병렬 스트림으로 변환

 

두 방법의 차이

  • parallelStream() → 처음부터 병렬 스트림 생성
  • parallel() → 기존의 스트림을 병렬 스트림으로 변환

💡 기본적으로 스트림은 순차적으로 실행되지만, 위 방법을 사용하면 병렬 처리 가능!


4️⃣ 병렬 스트림의 원리

🔹 병렬 스트림은 내부적으로 포크 조인(Fork-Join) 프레임워크를 사용

포크(Fork) 단계

  • 데이터를 분할하여 여러 개의 작은 데이터 덩어리(서브셋)로 나눈다.
  • 각 덩어리는 별도의 스레드에서 실행된다.

조인(Join) 단계

  • 각각의 결과를 모아서 최종 결과를 생성한다.

🔹 포크 조인(Fork-Join) 처리 과정

1️⃣ 원본 데이터를 여러 개로 분할
2️⃣ 여러 개의 코어에서 동시에 처리
3️⃣ 처리된 결과를 병합하여 최종 결과 생성


5️⃣ 순차 스트림과 병렬 스트림 성능 비교

대량 데이터를 대상으로 순차 스트림과 병렬 스트림의 성능을 비교해 보자.

📝 예제 코드: 1억 개의 난수 평균 구하기

import java.util.*;
import java.util.stream.*;

public class ParallelStreamExample {
    public static void main(String[] args) {
        // 1억 개의 랜덤 숫자 리스트 생성
        Random random = new Random();
        List<Integer> numbers = IntStream.range(0, 100_000_000)
                                         .map(i -> random.nextInt(101)) // 0~100 사이 난수 생성
                                         .boxed()
                                         .collect(Collectors.toList());

        // 순차 스트림 성능 테스트
        long startTime = System.nanoTime();
        double avg1 = numbers.stream()
                             .mapToInt(Integer::intValue)
                             .average()
                             .orElse(0);
        long endTime = System.nanoTime();
        System.out.println("순차 스트림 처리 시간: " + (endTime - startTime) / 1_000_000 + " ms");

        // 병렬 스트림 성능 테스트
        startTime = System.nanoTime();
        double avg2 = numbers.parallelStream()
                             .mapToInt(Integer::intValue)
                             .average()
                             .orElse(0);
        endTime = System.nanoTime();
        System.out.println("병렬 스트림 처리 시간: " + (endTime - startTime) / 1_000_000 + " ms");
    }
}

📌 실행 결과 예시

순차 스트림 처리 시간: 5200 ms
병렬 스트림 처리 시간: 1200 ms

 

병렬 스트림이 훨씬 빠르게 처리됨!


6️⃣ 병렬 스트림 사용 시 고려할 점

1️⃣ 요소 개수가 적으면 병렬 스트림을 사용하지 않는 것이 좋다.

  • 병렬 처리는 데이터를 분할하는 과정과 스레드를 관리하는 오버헤드가 발생한다.
  • 데이터 개수가 적을 경우 오히려 순차 스트림이 더 빠를 수 있다.

2️⃣ 요소 하나를 처리하는 시간이 짧다면 병렬 스트림의 효과가 미미하다.

  • 한 요소당 처리 시간이 짧다면 병렬 처리의 효과가 크지 않다.
  • 반대로, 한 요소당 처리 시간이 길다면 병렬 처리가 효과적이다.

3️⃣ 스트림 소스의 종류에 따라 성능이 다르다.

  • ArrayList, 배열(Array) → 인덱스를 사용하여 빠르게 요소 분할 가능 → 병렬 스트림 효과적
  • LinkedList, HashSet, TreeSet → 요소를 분할하는 비용이 크다 → 병렬 처리 효과 적음

4️⃣ CPU 코어 수가 많을수록 병렬 처리가 유리하다.

  • CPU 코어 개수가 많으면 병렬 처리를 할 수 있는 스레드가 증가하여 성능 향상 가능.

정리

개념 설명
동시성 (Concurrency) 하나의 코어에서 여러 작업을 번갈아 실행 (빠르게 교대하며 실행)
병렬성 (Parallelism) 여러 개의 코어에서 여러 작업을 동시에 실행
병렬 스트림 내부적으로 Fork-Join 프레임워크를 이용하여 데이터 분할 후 병렬 처리
사용 방법 parallelStream(), stream().parallel()
주의점 요소 수가 적거나 처리 속도가 빠르면 병렬 처리가 더 느릴 수 있음

 

요소 개수가 많고, 하나의 요소 처리 시간이 길다면 병렬 스트림을 사용하면 성능 향상 효과가 크다! 🚀

 

 

 

 

 

 

 

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

 

 

 

1. 요소 기본 집계 (Aggregation)

집계(Aggregation)는 스트림의 요소들을 하나의 값으로 축소하는 기능을 의미하며, 이는 최종 처리 기능에 속한다.

즉, 집계 메소드들은 스트림 연산의 끝에서 실행된다.


스트림 API에서 제공하는 기본 집계 함수들은 다음과 같다:

🔹 기본 집계 메소드:

메소드 설명
count() 요소 개수를 반환 (long)
findFirst() 첫 번째 요소 반환 (Optional)
max(Comparator<T>) 최대값 반환 (Optional)
min(Comparator<T>) 최소값 반환 (Optional)
average() 평균값 반환 (OptionalDouble)
sum() 합 반환 (int, long, double)

 

📌 반환값의 특징

  • count(), sum() 등의 일부 메소드는 기본 데이터 타입(int, long, double)을 반환한다.
  • max(), min(), average() 등의 메소드는 Optional<T> 형태로 값을 반환한다. 이는 집계 대상 요소가 없을 경우 예외를 방지하기 위해 사용된다.

📝 예제 코드

int[] numbers = {2, 4, 6};
IntStream intStream = Arrays.stream(numbers);

// 요소 개수
long count = intStream.count();

// 요소 합
int sum = Arrays.stream(numbers)
                .sum();

// 요소 평균
OptionalDouble average = Arrays.stream(numbers)
                               .average();
double avgValue = average.orElse(0.0); // 값이 없을 경우 기본값 0.0 반환

// 최대값
OptionalInt max = Arrays.stream(numbers)
                        .max();
int maxValue = max.orElseThrow(); // 값이 없으면 예외 발생

 


17.11 요소 커스텀 집계 (Custom Aggregation)

자바의 기본 집계 메소드(sum(), average()) 외에도 사용자가 직접 집계 기능을 구현할 수 있도록 제공되는 메소드가 reduce()이다.

🔹 reduce() 메소드

스트림의 요소들을 하나의 값으로 축소(리덕션)하기 위해 사용된다.

 
  • accumulator: 두 개의 요소를 받아 하나로 줄이는 함수 (람다식으로 구현)
  • identity: 초기값 (집계 대상이 없을 때 기본값으로 사용)
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)

 

📌 reduce()를 사용한 합계 계산 예제

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 요소들의 합 구하기
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);
System.out.println("합계: " + sum); // 출력: 15

// 요소들의 곱 구하기
int product = numbers.stream()
                     .reduce(1, (a, b) -> a * b);
System.out.println("곱셈 결과: " + product); // 출력: 120

// 최소값 구하기
Optional<Integer> min = numbers.stream()
                               .reduce(Integer::min);
System.out.println("최소값: " + min.orElse(-1)); // 출력: 1

 

💡 reduce()의 동작 방식

  1. 초기값(Identity)을 먼저 설정 (없으면 Optional로 반환)
  2. 첫 번째와 두 번째 요소를 연산하여 결과를 저장
  3. 결과와 세 번째 요소를 연산하여 새로운 결과를 저장
  4. 반복적으로 진행하여 최종 결과 도출

2. 요소 수집 (Collecting)

스트림의 요소들을 다른 컬렉션(List, Set, Map 등)으로 변환하거나, 집계 연산을 수행할 때 사용된다.

🔹 collect() 메소드

<R> R collect(Collector<? super T, A, R> collector)
  • Collector를 사용하여 스트림의 요소를 리스트, 집합, 맵 등으로 변환 가능
  • Collectors 유틸리티 클래스에서 다양한 Collector를 제공

1️⃣ 리스트 또는 셋으로 변환

List<String> names = students.stream()
                             .map(Student::getName)
                             .collect(Collectors.toList());

Set<Integer> scores = students.stream()
                              .map(Student::getScore)
                              .collect(Collectors.toSet());

2️⃣ Map으로 변환

Map<String, Integer> studentMap = students.stream()
                                          .collect(Collectors.toMap(
                                              Student::getName,   // Key: 학생 이름
                                              Student::getScore    // Value: 학생 점수
                                          ));

주의: toMap()을 사용할 때 중복된 키가 발생하면 IllegalStateException이 발생할 수 있다.
해결 방법: toMap()의 세 번째 인자로 (oldValue, newValue) -> newValue 제공.

 


3️⃣ 그룹핑 (GroupBy)

요소들을 특정 그룹으로 묶을 때 Collectors.groupingBy()를 사용한다.

Map<String, List<Student>> studentsByGender = students.stream()
                                                      .collect(Collectors.groupingBy(Student::getGender));

 

결과

{
    "남자": [Student1, Student2],
    "여자": [Student3, Student4]
}

4️⃣ 그룹별 집계

Map<String, Double> avgScoreByGender = students.stream()
                                               .collect(Collectors.groupingBy(
                                                   Student::getGender,
                                                   Collectors.averagingDouble(Student::getScore)
                                               ));

 

{
    "남자": 90.5,
    "여자": 88.0
}

정리

기능 메소드 설명
기본 집계 count(), sum(), average(), max(), min() 요소 개수, 합, 평균, 최댓값, 최솟값
커스텀 집계 reduce() 사용자 정의 방식으로 요소 축소
요소 수집 collect() 리스트, 셋, 맵 등으로 변환
그룹핑 groupingBy() 특정 기준으로 그룹핑
그룹별 집계 groupingBy() + averagingDouble() 그룹별 평균, 합, 개수 구하기

 

💡 스트림을 활용하면 데이터를 더욱 직관적으로 필터링, 변환, 집계할 수 있다!

 

 

 

 

 

 

 

 

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

 

 

 

자바의 스트림(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)가 쉬움
 
 
 
 
 

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

 

+ Recent posts