백엔드/Java

스트림

Ryuzy 2025. 5. 25. 17:32
728x90
반응형

1. 스트림

스트림(Stream)은 자바 8에서 도입된 기능으로, 컬렉션이나 배열의 데이터를 반복문 없이 함수형 스타일로 처리할 수 있도록 도와주는 도구입니다. 스트림은 데이터 저장소가 아니라, 데이터를 흘려보내며 가공하는 처리 흐름이며, filter, map, forEach 같은 연산을 통해 조건에 맞는 데이터를 추출하거나 변형할 수 있습니다. 또한 내부 반복 방식을 사용하여 코드가 간결하고 가독성이 좋으며, 병렬 처리도 손쉽게 할 수 있는 장점이 있습니다.

 

  • 데이터를 담는 저장소가 아님
  • 원본 데이터를 변경하지 않고 처리 가능
  • 연속적이고 선언적인 데이터 처리 가능
  • 파이프라인처럼 연결된 연산을 수행함
데이터 → 중간연산 → 중간연산 → ... → 최종연산
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() 등
import java.util.Arrays;
import java.util.List;

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

        // 1. "a"로 시작하는 과일만 필터링 후 대문자로 변환하고 출력
        fruits.stream()                      // 스트림 생성
              .filter(f -> f.startsWith("a")) // 중간 연산1
              .map(String::toUpperCase)       // 중간 연산2
              .distinct()                     // 중복 제거
              .forEach(System.out::println);  // 최종 연산
    }
}

 

2. 컬렉션 vs 스트림

 

 

2. IO Stream

IO Stream(Input/Output Stream)은 자바에서 데이터를 읽고 쓰는 흐름을 추상화한 개념으로, 파일, 키보드, 네트워크 등 다양한 입출력 장치와의 데이터 전송을 처리할 수 있도록 도와줍니다. 입력 스트림(InputStream)은 외부에서 데이터를 읽어오는 역할, 출력 스트림(OutputStream)은 데이터를 외부로 내보내는 역할을 하며, 기본적으로 바이트 기반 스트림과 문자 기반 스트림(Reader/Writer)으로 나뉩니다. IO 스트림은 계층 구조로 되어 있어, FileInputStream, BufferedReader, PrintWriter 같은 다양한 클래스들이 조합되어 효율적이고 다양한 방식의 입출력을 처리할 수 있도록 설계되어 있습니다.

 

스트림의 분류

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

 

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

 

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

public class ByteStreamExample {
    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();
        }
    }
}

 

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

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

 

 

3. 직렬화

직렬화(Serialization)는 자바에서 객체를 바이트 형태로 변환하여 파일에 저장하거나 네트워크를 통해 전송할 수 있도록 만드는 과정입니다. 이를 통해 프로그램이 종료되더라도 객체의 상태를 저장하거나, 다른 시스템 간에 객체를 전달할 수 있습니다. 직렬화를 위해서는 해당 클래스가 Serializable 인터페이스를 구현해야 하며, 반대로 바이트 데이터를 다시 객체로 복원하는 과정을 역직렬화(Deserialization)라고 합니다. 직렬화된 데이터는 사람이 읽을 수 없는 이진 형식으로 저장되며, 보통 .ser 확장자를 가진 파일에 저장하는 것이 일반적입니다.

  1. 직렬화할 클래스는 java.io.Serializable 인터페이스를 구현해야 합니다.
    • 이 인터페이스는 마커 인터페이스로, 구현할 메서드는 없습니다.
  2. 클래스의 필드는 모두 직렬화가 가능한 타입이어야 합니다.
    • 직렬화되지 말아야 하는 필드는 transient 키워드 사용
import java.io.Serializable;

public 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;
    }

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

 

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

public class SerializeExample {
    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();
        }
    }
}

 

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

public class DeserializeExample {
    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

serialVersionUID는 직렬화된 객체의 클래스 버전을 식별하는 고유 ID입니다. 이 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 발생
728x90
반응형