☕Java — IO, Enum, Lambda, Stream
•
#java
목차
- Chapter 14. 입출력 (IO)
- Chapter 15. 열거형 (Enum)
- Chapter 16. 람다식 (Lambda Expression)
- Chapter 17. 스트림 (Stream API)
Chapter 14. 입출력 (IO)
1. 핵심 개념 및 상세 설명
1-1. 스트림의 분류와 특징
- 방향에 따른 분류:
- InputStream / Reader: 외부에서 프로그램을 데이터를 읽어옴.
- OutputStream / Writer: 프로그램에서 외부로 데이터를 내보냄.
- 단위에 따른 분류:
- 바이트 스트림 (
Stream계열):byte단위 처리. 이미지, 오디오, 비디오 등 모든 바이너리 데이터에 사용. - 문자 스트림 (
Reader/Writer계열):char단위(2byte) 처리. 텍스트 데이터 전용이며 유니코드(UTF-8 등) 인코딩을 자동으로 처리.
- 바이트 스트림 (
1-2. 보조 스트림 (Filter Stream)의 활용
보조 스트림은 성능 향상(Buffered), 기본형 데이터 처리(Data), 객체 직렬화(Object) 등의 기능을 추가합니다.
// 객체 직렬화와 보조 스트림 결합 예시 try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("user.dat")))) { User user = new User("admin", "1234"); oos.writeObject(user); // 객체를 파일에 저장 } catch (IOException e) { e.printStackTrace(); }
2. 실전 예제 보충
파일 복사 예제 (성능 비교 체감)
public void copyFile(String src, String dest) { // Buffered를 사용하면 내부적으로 8KB 크기의 버퍼를 사용하여 속도가 수십 배 빠름 try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src)); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest))) { byte[] buffer = new byte[1024]; int len; while ((len = bis.read(buffer)) != -1) { bos.write(buffer, 0, len); } } catch (IOException e) { e.printStackTrace(); } }
3. 주의사항 및 유의사항
- 자원 반납 (Resource Leak): 스트림을 열었다면 반드시
close()를 호출해야 한다.try-with-resources문법을 사용하는 것이 가장 안전하고 권장된다. - flush()의 중요성: 출력 스트림(특히 Buffered 계열)은 버퍼가 다 차지 않으면 데이터가 실제로 나가지 않을 수 있다. 작업 종료 전이나 중간에
flush()를 호출하여 데이터를 강제로 밀어내야 한다. - 직렬화 (Serializable):
ObjectOutputStream을 사용해 객체를 저장하려면 해당 클래스가java.io.Serializable인터페이스를 구현해야 한다. (구현하지 않으면NotSerializableException발생) - 경로 구분자: Windows(
\)와 Unix/Mac(/)의 경로 구분자가 다르다.File.separator를 사용하면 운영체제에 독립적인 코드를 작성할 수 있다.
Chapter 15. 열거형 (Enum)
1. 핵심 개념 및 상세 설명
1-1. Enum은 클래스다
Java의 Enum은 단순한 상수가 아니라 java.lang.Enum을 상속받는 특수한 클래스입니다. 따라서 메서드, 생성자, 필드를 가질 수 있으며 인터페이스 구현도 가능합니다.
1-2. 싱글톤 패턴과 Enum
Enum은 인스턴스가 JVM 내에 단 하나만 존재함을 보장합니다. 따라서 멀티스레드 환경에서도 안전한 가장 완벽한 싱글톤(Singleton) 구현 방법 중 하나로 꼽힙니다.
2. 실전 예제 보충
상수별 메서드 구현 (전략 패턴)
public enum Operation { PLUS { public double apply(double x, double y) { return x + y; } }, MINUS { public double apply(double x, double y) { return x - y; } }; public abstract double apply(double x, double y); } // 사용 시 double result = Operation.PLUS.apply(10, 20); // 30.0
3. 주의사항 및 유의사항
- ordinal() 사용 지양:
ordinal()은 선언된 순서를 반환하지만, 상수의 순서가 바뀌면 로직이 깨질 위험이 크다. 순서값이 필요하다면 직접 별도의 필드(private final int value)를 선언해 관리해야한다. - switch 문 활용: Enum과
switch문은 궁합이 매우 괜찮다. 가독성을 높이고 모든 케이스를 처리했는지 확인하기 좋다. - 생성자 접근 제한: Enum의 생성자는 암시적으로
private이며, 외부에서new로 생성할 수 없다.
Chapter 16. 람다식 (Lambda Expression)
1. 핵심 개념 및 상세 설명
1-1. 변수 캡처 (Variable Capture)
람다식 내부에서 외부 지역 변수를 사용할 때, 그 변수는 final이거나 effectively final(값이 한 번도 바뀌지 않은 변수)이어야 합니다. 이는 람다가 실행될 때 지역 변수가 사라진 후에도 값을 참조할 수 있도록 복사본을 유지하기 때문입니다.
1-2. 타겟 타입 추론
컴파일러는 람다식이 대입되는 인터페이스의 추상 메서드 시그니처를 보고 파라미터 타입과 반환 타입을 추론합니다.
2. 실전 예제 보충
복합 조건 Predicate 결합
Predicate<String> startWithA = s -> s.startsWith("A"); Predicate<String> lengthFive = s -> s.length() == 5; // 두 조건을 AND 결합 (A로 시작하면서 길이가 5인 문자열) Predicate<String> combined = startWithA.and(lengthFive); System.out.println(combined.test("Apple")); // true System.out.println(combined.test("Banana")); // false
3. 주의사항 및 유의사항
- 가독성 저해: 람다가 너무 길어지면(예: 5줄 이상) 오히려 가독성이 떨어질 수 있다. 이럴 때는 별도의 메서드로 분리하고 메서드 참조를 사용하는 것이 좋다.
- 디버깅의 어려움: 람다는 익명 함수이므로 스택 트레이스에서 위치를 찾기가 일반 메서드보다 어렵다
- 함수형 인터페이스 확인: 람다를 쓰려는 인터페이스에 추상 메서드가 2개 이상이면 안 된다.
@FunctionalInterface어노테이션을 붙여 실수를 방지한다.
Chapter 17. 스트림 (Stream API)
1. 핵심 개념 및 상세 설명
1-1. 중간 연산 vs 최종 연산
- 중간 연산:
filter,map,sorted,peek등. 연산 결과가 다시 스트림이므로 체이닝(Chaining)이 가능합니다. - 최종 연산:
forEach,collect,count,reduce등. 스트림을 소모하며 결과를 반환합니다.
1-2. 병렬 스트림 (Parallel Stream)
parallelStream()을 사용하면 대량의 데이터를 멀티 코어를 활용해 병렬로 처리할 수 있습니다. 하지만 오버헤드가 발생할 수 있으므로 데이터가 아주 많을 때만 신중히 사용해야 합니다.
2. 실전 예제 보충
flatMap을 이용한 리스트 평탄화
List<List<String>> complexList = Arrays.asList( Arrays.asList("A", "B"), Arrays.asList("C", "D") ); // [["A", "B"], ["C", "D"]] -> ["A", "B", "C", "D"] List<String> flatList = complexList.stream() .flatMap(Collection::stream) .collect(Collectors.toList());
3. 주의사항 및 유의사항
- 재사용 불가: 스트림은 한 번 최종 연산을 수행하면 닫힌다. 다시 사용하려면 스트림을 새로 생성해야 한다.
- 지연 연산 (Lazy Evaluation): 최종 연산이 호출되기 전까지는 중간 연산이 아무것도 수행하지 않는다.
peek()같은 메서드로 로그를 찍을 때 최종 연산이 없으면 로그가 남지 않음 - 상태 유지 연산 주의:
sorted(),distinct()같은 연산은 스트림의 모든 요소를 버퍼에 쌓아야 하므로 메모리 사용량이 급증할 수 있다. 무한 스트림에서는 주의가 필요하다. - 사이드 이펙트 지양:
forEach내부에서 외부 변수를 수정하는 행위는 지양해야 한다. 대신collect나reduce를 사용해 결과값을 반환받아서 사용.