1. 입출력 스트림 (I/O Stream)
스트림이란?
- 스트림(Stream)은 데이터가 한 방향으로 흐르는 통로
- 프로그램 기준으로
- 데이터가 들어오면 입력 스트림
- 데이터가 나가면 출력 스트림
스트림의 종류
자바에서는 다루는 데이터의 단위에 따라 스트림을 구분한다.
- 바이트 스트림
- 그림, 영상, 파일, 문자 등 모든 데이터
InputStream/OutputStream
- 문자 스트림
- 문자 데이터 전용
Reader/Writer
자바의 입출력 관련 클래스는 대부분 java.io 패키지에 포함되어 있다.
2. 보조 스트림 (Decorator Stream)
보조 스트림이란?
- 다른 스트림에 기능을 덧붙이는 스트림
- 스스로 입출력은 하지 못하고 기존 스트림을 감싸서 사용
보조 스트림으로 할 수 있는 것
- 바이트 → 문자 변환
- 입출력 성능 향상 (버퍼링)
- 객체 단위 입출력 (직렬화)
InputStream is = new FileInputStream("data.txt");
InputStreamReader reader = new InputStreamReader(is);
👉 실제 파일 입출력은 FileInputStream이 담당
👉 문자 변환 기능은 InputStreamReader가 담당
즉, 역할 분리가 핵심이다.
3. 직렬화 (Serialization)
직렬화란?
메모리에 존재하는 객체를 바이트 스트림 형태로 변환하는 과정
- 파일 저장
- 네트워크 전송
- DB 저장
같은 외부 시스템과 객체를 주고받기 위해 필요
역직렬화란?
직렬화된 바이트 데이터를 다시 객체로 복원하는 과정
Object
↓ (직렬화)
Byte Stream
↓
File / DB / Network
↑
Byte Stream
↑ (역직렬화)
Object
Serializable 인터페이스
“이 클래스는 직렬화가 가능하다”는 표시용(Marker) 인터페이스
public class User implements Serializable { ... }
- 메서드 없음
- 구현 여부 자체가 의미
특징
Serializable을 구현한 클래스만 직렬화 가능static필드 → 직렬화 제외transient필드 → 직렬화 제외- 상속 구조에서
- 부모 클래스가 Serializable이 아니면
- 부모의 인스턴스 필드는 직렬화 대상 ❌
serialVersionUID
직렬화된 객체의 버전을 식별하기 위한 고유 ID
private static final long serialVersionUID = 1L;
- 클래스 구조가 바뀌었는지 판단하는 기준
- 값이 다르면 → 역직렬화 시
InvalidClassException
👉 명시적으로 선언하는 것이 권장
(자동 생성 값은 컴파일러마다 달라질 수 있음)
flush()
- 내부 버퍼에 쌓인 데이터를 강제로 출력
- 데이터가 많거나 네트워크 스트림일 때 중요
oos.writeObject(obj);
oos.flush();
- 실습 코드처럼 데이터가 적으면 체감 안 됨
- Spring 같은 프레임워크에서는 내부적으로 사용됨
4. Properties
Map<String, String>전용 컬렉션- 애플리케이션 환경 설정 저장에 특화
Properties props = new Properties();
props.setProperty("url", "localhost");
props.setProperty("port", "8080");
.properties파일과 함께 자주 사용- 타입 안정성은 낮지만 설정용으로는 충분
5. try-with-resources
리소스를 자동으로 close 해주는 try 문법
AutoCloseable또는Closeable구현 객체 대상- 파일, 스트림, DB 커넥션 등에 매우 유용
try (FileInputStream fis = new FileInputStream("a.txt")) {
// 사용
} catch (IOException e) {
// 예외 처리
}
장점
finally에서close()호출할 필요 없음- 예외 발생 여부와 관계없이 자동 자원 해제
- 코드 간결 + 안정성 향상
6. 스레드 / 멀티 스레드
스레드란?
- 코드의 실행 흐름
- 하나의 프로세스 안에 여러 스레드가 존재 가능
멀티 스레드 = 하나의 프로세스에서 여러 작업 흐름
메인 스레드
- 모든 자바 프로그램은 main 스레드로 시작
- 싱글 스레드 프로그램
- main 종료 → 프로세스 종료
- 멀티 스레드 프로그램
- 하나라도 살아 있으면 프로세스 유지
스레드 생성 방법
1) Runnable 구현
Thread thread = new Thread(() -> {
// 실행 코드
});
thread.start();
2) Thread 상속
Thread thread = new Thread() {
@Override
public void run() {
// 실행 코드
}
};
thread.start();
실무에서는 Runnable 방식이 더 일반적
스레드 상태 요약
- NEW → RUNNABLE → RUNNING → TERMINATED
- RUNNING ↔ RUNNABLE 은 스케줄링에 의해 반복
7. 열거형 (Enum)
Enum이란?
한정된 값만 가질 수 있는 타입
enum Day {
MON, TUE, WED, THU, FRI, SAT, SUN
}
- 상수 집합을 타입으로 표현
- 문자열/정수 상수보다 안전하고 명확
주요 메서드
name()
- 열거 상수의 이름을 문자열로 반환
Day.MON.name(); // "MON"
values()
- 모든 열거 상수를 배열로 반환
Day[] days = Day.values();
valueOf(String)
- 문자열과 일치하는 열거 상수 반환
- 일치하지 않으면
IllegalArgumentException
Day day = Day.valueOf("MON");
8. Enum 활용 패턴 — 조건문을 없애는 설계
Enum은 단순히 상수를 묶는 문법이 아니라,
“상태나 전략을 가진 타입”으로 활용할 수 있다.
기존 방식의 한계
보통 연산을 처리할 때는 이런 코드가 많다.
if (op.equals("+")) {
return a + b;
} else if (op.equals("-")) {
return a - b;
}
위 방식의 문제점은...
- 문자열 비교 (타입 안정성 ❌)
- 연산 추가 시 if/switch 계속 증가
- 로직이 여기저기 흩어짐
Enum으로 연산을 표현하기
public enum Operation {
PLUS("+", (a, b) -> a + b),
MINUS("-", (a, b) -> a - b);
private final String operator;
private final IntBinaryOperator operation;
Operation(String operator, IntBinaryOperator operation) {
this.operator = operator;
this.operation = operation;
}
public int calculate(int a, int b) {
return operation.applyAsInt(a, b);
}
}
여기서 낯설었던 부분은 바로 이거다.
PLUS("+", (a, b) -> a + b)
Enum 상수는 사실 “객체 생성”이다
Enum의 각 상수는 내부적으로 하나의 객체다.
PLUS("+", 람다식)
이 코드는 다음과 같은 의미다.
PLUS라는 이름의 Operation 객체 생성- 생성자에
"+"와(a, b) -> a + b전달
즉, Enum은 이런 구조를 가진다.
Operation plus = new Operation("+", (a, b) -> a + b);
단지 new를 직접 못 쓰게 막아둔 것뿐이다.
IntBinaryOperator를 사용하는 이유
private final IntBinaryOperator operation;
IntBinaryOperator는 자바에서 제공하는 표준 함수형 인터페이스다.
@FunctionalInterface
public interface IntBinaryOperator {
int applyAsInt(int left, int right);
}
의미는 간단하다.
- int 두 개를 받아서
- int 하나를 반환하는 연산
즉,
(a, b) -> a + b
calculate() 메서드의 역할
public int calculate(int a, int b) {
return operation.applyAsInt(a, b);
}
- 실제 계산 로직은 람다에 있음
- Enum은 “어떤 연산을 쓸지”만 결정
이 구조 덕분에:
- if / switch 필요 없음
- 연산 추가 시 Enum 상수만 추가하면 됨
사용 코드
System.out.println(Operation.PLUS.calculate(10, 20));
또는 Enum 전체를 순회하면서도 가능하다.
for (Operation op : Operation.values()) {
System.out.println(op + " = " + op.calculate(10, 20));
}
이 구조의 정체 — 전략 패턴
이 코드는 전략(Strategy) 패턴을 따른다.
| 역할 | 담당 |
|---|---|
| 전략 인터페이스 | IntBinaryOperator |
| 전략 구현 | 각 Enum 상수의 람다 |
| 전략 선택 | Operation.PLUS, Operation.MINUS |
연산을 선택하는 책임을 Enum이 가진다
이 방식의 장점?
- 문자열, 숫자 상수 제거 → 타입 안정성
- 조건문 제거 → 가독성 개선
- 기능 추가 시 기존 코드 수정 최소화
📚 Reference
- 『이것이 자바다』, 신용권, 임경균 저
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 10 - 객체 다루기, DBMS 기초(1) (0) | 2026.02.04 |
|---|---|
| [멋사 클라우드 5기] Day 9 - Enum 활용, BigNumber, Lombok, 객체 설계 (1) | 2026.02.03 |
| [멋사 클라우드 5기] Day 7 - Lambda · Stream 심화 + Optional (0) | 2026.01.30 |
| [멋사 클라우드 5기] Day 6 - 자료구조, Lambda, Stream (0) | 2026.01.29 |
| [멋사 클라우드 5기] Day 5 - Java 기본 라이브러리 & 컬렉션 정리 (0) | 2026.01.28 |
