Java에서의 함수형 인터페이스, Optional 처리 방식, 선언형과 명령형 스타일의 차이를 정리한다.
실무와 이론 모두에서 자주 등장하는 핵심 개념들을 간결하고 명확하게 요약하였다.
T를 받아 출력 R을 반환하는 함수형 인터페이스<?>: 아무 타입이나 가능<? extends T>: T를 상속한 타입 (T 포함 X)<? super T>: T의 부모 타입 (T 포함 O)Function<? super Object, ? extends Object> classifier;
Optional은 Java에서 null을 안전하게 다루기 위해 사용하는 래퍼 클래스이다.
Optional.of(value)
Optional.ofNullable(value)
Optional.empty()
.orElse(default)
.ifPresent(fn)
.map(fn)
OptionalDouble, OptionalInt, OptionalLong 등
Optional.of(null)은 절대 사용하지 말 것OptionalInt 등으로 명확히 구분프로그래밍 스타일에 따라 문제 해결 방식이 달라진다.
| 구분 | 선언형 스타일 | 명령형 스타일 |
|---|---|---|
| 말 그대로 | “무엇을 할지” 선언 | “어떻게 할지” 직접 지시 |
| 특징 | 결과 중심 | 절차 중심 |
| 목적 | 무엇을 원하는지만 말함 | 어떻게 처리할지를 자세히 설명함 |
| 예시 언어 | SQL, HTML, Java Stream, React | C, Java for문, Python 기본 반복문 등 |
자바에서는 FileOutputStream, FileInputStream과 같은 바이트 기반 스트림을 사용하여 파일에 데이터를 저장하고 읽어올 수 있다.
입출력 시에는 반드시 예외 처리를 통해 안정적인 프로그램 흐름을 구성하며, 스트림의 닫기 작업은 finally 블록에서 수행하는 것이 일반적이다.
스트림(Stream) 개념을 기반으로 한다. (파이프, 흐름)FileOutputStream → 파일에 데이터를 바이트 단위로 저장 (바이트 단위로 흘려보낼 수 있게 해주는 통로/흐름)FileInputStream → 파일에서 데이터를 바이트 단위로 읽음 (바이트 단위로 들어올 수 있게 해주는 통로/흐름)문자열을 UTF-8 바이트 배열로 변환 후,
FileOutputStream을 통해 파일로 저장하는 예제이다.
public class Ex07_파일저장 {
public static void main(String[] args) {
// 파일 경로 (새로 만들 파일 포함), 파일에 기록할 내용 지정
String filePath = "./01-JAVA-Basics/text.txt";
String content = "안녕하세요 자바";
// 문자열을 UTF-8 바이트 배열로 인코딩 (파일 저장을 위해)
// FileOutputStream은 바이트 단위 스트림이라서, 흐를 수 있는 건 "바이트"뿐임.
// 따라서 그 안으로 보내려면 무조건 직렬화(객체의 경우)되었거나,
// 혹은 문자열을 바이트 배열로 인코딩한 형태여야 한다.
byte[] buffer = null;
try {
buffer = content.getBytes("utf-8"); //enc-Kr도 있다.(엑셀o, vscode x)
} catch (UnsupportedEncodingException e) {
e.getStackTrace();
}
// 파일쓰기
OutputStream os = null;
try {
os = new FileOutputStream(filePath);
// 자바 프로그램에서 파일을 "열어서" 데이터를 "써주는" 파이프(관) (쓰기모드로 연다)
// 프로그램에서 파일로 데이터를 보냄 (out - 프로그램 기준),
// Stream은 데이터를 한 바이트씩 차례대로 흘려보내는 통로(파이프) 개념
os.write(buffer);
// 파이프를 통해 쓰기 작업 수행
// 이때 이미 존재하는 파일이면 덮어쓰고, 없으면 자동으로 생성됨.
} catch (FileNotFoundException e) {
// 파일을 못찾았을때 발생하는 에러
e.printStackTrace();
} catch (IOException e) {
// 파일 입출력시 하드가 부족하거나 해서 발생하는 에러
e.printStackTrace();
} catch (Exception e) {
// 예상치 못한 에러가 발생할 수 있으므로 항상 catch 마지막은 Exception을 써준다.
e.printStackTrace();
} finally {
if (os!= null) {
// 스트림이 성공적으로 열렸을 경우에만 닫을 수 있다.
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
getBytes("utf-8")을 통해 바이트 배열로 변환FileOutputStream으로 흘려서 바이트 배열을 파일에 저장FileNotFoundException, IOException, Exception)를 통해 안정성 확보finally 블록에서 close() 수행저장된 파일을
FileInputStream으로 읽고, 바이트 배열을 다시 문자열로 복원하는 예제이다.
public class Ex08_파일읽기 {
public static void main(String[] args) {
String filepath = "01-JAVA-Basics/text.txt"; // ./는 생략가능
byte [] buffer = null; // 읽어올 내용이 저장될 임시공간
String content = null; // 임시공간의 바이트들을 문자열로 변환하여 저장할 변수
InputStream is = null; // 꽂을 파이프 정의
try {
is = new FileInputStream(filepath); // 프로그램 기준의 명명이니까 변수로는 파이프가 이어질 대상을 지정 -> 순간 파이프의 크기도 할당
buffer = new byte[is.available()]; // 파이프(흐름)의 크기를 불러옴.
is.read(buffer); // 관 꽂았으니까 이제 읽고 buffer에다가 담음.
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null){ // 통로가 잘 열렸으면 닫아야지
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (buffer != null ){
try {
content = new String(buffer, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
System.out.println(content);
}
}
FileInputStream → 파일 내용을 읽어서 바이트 배열 buffer에 저장buffer를 다시 문자열로 복원 → new String(buffer, "utf-8")available()을 통해 스트림에서 읽을 수 있는 바이트 크기 확보FileOutputStream과 FileInputStream은 모두 바이트 기반이므로, 문자열 처리 시 반드시 인코딩/디코딩 작업이 필요하다.finally 블록에서 닫는 처리를 해야 한다.getBytes() 또는 new String() 시 인코딩명을 명시하지 않으면 시스템 기본값을 사용하므로, 명확하게 "utf-8" 지정해야 한다.Person::name 즉 클래스::필드값으론 접근 안됨p -> p.name이 맞음. p는 지금 스트림에 흐르고 있는 하나의 객체를 의미함.// 답(오답)
people.stream().filter(Person->Person.age>=20).map(Person::name).collect(Collectors.toList());
// 수정코드
people.stream()
.filter(p -> p.age >= 20)
.map(p -> p.name)
.collect(Collectors.toList());
// 문제
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
List<Person> people = List.of(
new Person("철수", 19),
new Person("영희", 22),
new Person("민수", 25)
);
// 답
data.stream().flatMap(list -> list.stream()).distinct().sorted().forEach(s->System.out.println(s));
// 문제
List<List<String>> data = List.of(
List.of("apple", "banana"),
List.of("banana", "carrot"),
List.of("apple", "date")
);
// 답
words.stream().collect(Collectors.groupingBy(String::length));
// 문제
List<String> words = List.of("a", "bee", "ant", "banana", "dog");
min, max, sum은 기본형 스트림에만 제공된다.// 답(오답)
items.stream().filter(Item::price).reduce(0, (a,b) -> a+ b);
// 정답
int sum = items.stream()
.map(item -> item.price)
.reduce(0, Integer::sum); // 또는 (a, b) -> a + b
// 문제
class Item {
String name;
int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
}
List<Item> items = List.of(
new Item("노트북", 1200000),
new Item("마우스", 30000),
new Item("키보드", 45000)
);
// 답
numbers.stream().filter(i -> i <= 50).map(i -> i*i).collect(Collectors.toList());
// 문제
List<Integer> numbers = List.of(5, 8, 60, 7, 9, 100);
// 답
names.stream()
.filter(s -> s.startsWith("김"))
.map(s->s.replace("김","KIM"))
.collect(Collectors.toList());
// 문제
List<String> names = List.of("김철수", "이영희", "김영수", "박민수");
// 답
users.stream()
.map(s -> s.email.split("@")[1])
.distinct()
.sorted()
.forEach(System.out::println);
// 문제
class User {
String name;
String email;
}
List<User> users = List.of(
new User("A", "a@gmail.com"),
new User("B", "b@naver.com"),
new User("C", "c@gmail.com")
);
// 답
books.stream()
.map(s->s.tilte)
.filter(s->s.contains("프로그래밍"))
.sorted()
.forEach(System.out::println);
//문제
List<Book> books = List.of(
new Book("자바 프로그래밍 기초", 30000),
new Book("자료구조", 25000),
new Book("파이썬 프로그래밍", 28000),
new Book("인공지능", 40000)
)
class Book {
String title;
int price;
}
// 답(약간 틀림)
names.stream()
.distinct()
.collect(Collectors.toSet());
// 정답
names.stream().collect(Collectors.toSet()); // 자동으로 중복을 걸러줌.
// 문제
List<String> names = List.of("김철수", "이영희", "김철수", "박민수");
orElse로 기본값을 설정하고 뽑는 방식을 많이 사용한다.getAsDouble()은 optional에 값이 비어있을 경우 에러가 난다.// 답 (애매함.)
Arrays.stream(scores)
.average()
.getAsDouble();
// 정답
Arrays.stream(scores)
.average()
.orElse(0.0); // 기본값 더블타입으로 설정
// 문제 설명
int[] scores = {80, 90, 75, 100, 95};
Collectors.groupingBy(Collectors.mapping(Map<String, List<String>> result = students.stream()
.collect(Collectors.groupingBy(
s -> s.score >= 60 ? "합격" : "불합격", // key: 조건에 따른 문자열
Collectors.mapping(s -> s.name, Collectors.toList()) // value: 이름만 리스트로 수집
));