스트림

현서·2025년 5월 29일
1

자바

목록 보기
20/32
post-thumbnail

1. 스트림(Stream)

  • 컬렉션이나 배열의 데이터를 반복문 없이 함수형 스타일로 처리할 수 있도록 도와주는 도구.
  • 데이터를 흘려보내며 가공하는 처리 흐름
  • filter, map, forEach 같은 연산을 통해 조건에 맞는 데이터를 추출하거나 변형할 수 있다.
  • 내부 반복 방식을 사용하여 코드가 간결하고 가독성이 좋다.
  • 병렬 처리도 손쉽게 할 수 있다.

데이터를 담는 저장소 X
원본 데이터를 변경하지 않고 처리 가능
연속적이고 선언적인 데이터 처리 가능
파이프라인처럼 연결된 연산을 수행함

데이터 → 중간연산 → 중간연산 → ... → 최종연산
List → filter → map → collect

스트림은 데이터를 한 줄씩 처리하며, 중간 연산은 최종 연산이 호출되어야 실행된다. (지연 평가)

1. 스트림의 주요 연산

스트림은 중간 연산과 최종 연산으로 나뉜다.

중간 연산 (결과가 스트림)
filter(Predicate) : 조건에 맞는 요소만 추출
map(Function) : 요소 변환
sorted() : 정렬
distinct() : 중복 제거
limit(n), skip(n) : 자르기/건너뛰기

최종 연산 (스트림 종료)
forEach(Consumer) : 요소 반복 처리
collect(Collectors) : 결과 수집
count(), sum(), max(), min()
anyMatch(), allMatch(), noneMatch()

package lesson08;

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

public class Ex01_Main {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("apple", "banana", "orange", "apple");
        System.out.println(fruits);

        /*
            String::toUpperCase
            - 메서드 참조
            s -> s.toUpperCase()와 같은 람다식을 더 간단하게 표현
            String 클래스의 toUpperCase() 인스턴스 메서드를 현재 스트림의 각 요소에 대해 호출하겠다는 뜻
         */
        fruits.stream()
                .filter(f->f.startsWith("a"))
                .map(String::toUpperCase)
                .distinct()
                .forEach(System.out::println);
    }
}

2. 컬렉션 vs 스트림

2. IO Stream (Input/Output Stream)

  • 자바에서 데이터를 읽고 쓰는 흐름을 추상화한 개념.
  • 다양한 입출력 장치와의 데이터 전송을 처리할 수 있도록 도와준다.

입력 스트림 (Input)
외부 → 프로그램으로 데이터 입력
InputStream, Reader

출력 스트림 (Output)
프로그램 → 외부로 데이터 출력
OutputStream, Writer

바이트 스트림
1바이트 단위로 처리 (이미지, 영상 등 이진 데이터) InputStream, OutputStream
문자 스트림
문자 단위로 처리 (텍스트 데이터)
Reader, Writer

package lesson08;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Ex02_Main {
    public static void main(String[] args) {
        try(
                FileInputStream fis = new FileInputStream("input.txt");
                FileOutputStream fos = new FileOutputStream("output.txt");
        ){
          int data;
          while((data = fis.read())!=-1){
              fos.write(data);
          }
            System.out.println("파일 복사 완료(바이트 스트림)");
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}
package lesson08;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Ex03_Main {
    public static void main(String[] args) {
        try(
                FileReader reader = new FileReader("input.txt");
                FileWriter writer = new FileWriter("output2.txt")
                ){
            int ch;
            while ((ch = reader.read())!=-1){
                writer.write(ch);
            }
            System.out.println("파일 복사 완료(문자 스트림)");
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

3. 직렬화(Serialization)

  • 자바에서 객체를 바이트 형태로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있도록 만드는 과정.
    -> 직렬화된 데이터는 사람이 읽을 수 없는 이진 형식으로 저장된다.
    -> 보통 .ser 확장자를 가진 파일에 저장한다.
    -> 프로그램이 종료되어도 객체의 상태를 저장하거나, 다른 시스템 간에 객체를 전달할 수 있다.

  • 직렬화를 위해 해당 클래스가 Serializable 인터페이스를 구현해야 한다.

  • 직렬화할 클래스는 java.io.Serializable 인터페이스를 구현해야 한다.
    -> java.io.Serializable 인터페이스 : 마커 인터페이스, 구현할 메서드 없음.

  • 클래스의 필드는 모두 직렬화가 가능한 타입이어야 한다.
    -> 직렬화되지 말아야 하는 필드는 transient 키워드 사용

  • 역직렬화(Deserialization) : 바이트 데이터를 다시 객체로 복원하는 과정

package lesson08;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

class Student implements Serializable{
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "학생 이름: " + name + ", 나이: " + age;
    }
}

public class Ex04_Main {
    public static void main(String[] args) {
        Student s = new Student("김사과", 20);
        try(
                FileOutputStream fos = new FileOutputStream("student.ser");
                ObjectOutputStream oos = new ObjectOutputStream(fos);
                ){
            oos.writeObject(s); // 직렬화
            System.out.println("객체를 파일에 저장했습니다.");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
package lesson08;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Ex05_Main {
    public static void main(String[] args) {
        try(
                FileInputStream fis = new FileInputStream("student.ser");
                ObjectInputStream ois = new ObjectInputStream(fis);
                ){
            Student s = (Student) ois.readObject(); // 역직렬화
            System.out.println("복원된 객체: " + s);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

serialVersionUID

  • 직렬화된 객체의 클래스 버전을 식별하는 고유 ID.
  • 직렬화 당시의 클래스 구조를 구분하는 역할을 한다.
private static final long serialVersionUID = 1L;

자바는 직렬화된 객체를 읽어올 때, 원래 클래스와 구조가 같아야 역직렬화가 가능하다. (클래스 구조 바뀌면 문제 생김.)

// 저장 당시
class Student implements Serializable {
    String name;
}
// 변경 후
class Student implements Serializable {
    String name;
    int age; // 필드 추가
}

이때 serialVersionUID가 다르면 역직렬화 시 오류가 발생한다. (InvalidClassException 예외 발생)

// Student.java
import java.io.Serializable;

public class Student implements Serializable {
    String name;
    int age; // 새 필드 추가

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "이름: " + name + ", 나이: " + age;
    }
}

// 복원 코드
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.ser"));
Student s2 = (Student) ois.readObject();  // ❌ InvalidClassException 발생
profile
The light shines in the darkness.

0개의 댓글