Timeline
09:00 ~ 09:30 : 데일리 스크럼
09:30 ~ 12:00 : 개인 학습
12:00 ~ 13:00 : 점심 시간
13:00 ~ 18:00 : 개인 학습
18:00 ~ 19:00 : 저녁 시간
19:00 ~ 21:00 : 개인 학습
예외(Exception)란?
- 예외는 프로그램 실행 중 예상하지 못한 상황이 발생하는 것을 의미합니다.
- → 대표적인 산술 예외: 10 / 0 (0 으로 나누기)
- 의도적으로 예외를 발생시킬 때는 throw 키워드를 통해 발생시킵니다.
- 예외를 처리하지 않으면 프로그램이 중단될 수 있습니다.
- 그래서 예외처리(try-catch)를 통해 프로그램이 안정적으로 실행되게 할 수 있습니다.
의도하지 않은 예외
public class Main {
public static void main(String[] args) {
System.out.println("프로그램 시작");
int result = 10 / 0; // ❌ 예외 발생 (ArithmeticException)
System.out.println("이 문장은 실행되지 않음");
}
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
at chapter3.exception.Main.main(Main.java:8)
Process finished with exit code 1
의도적인 예외 - throw
public class Main {
public static void main(String[] args) {
int age = 10;
if (age < 18) {
// ✅ 의도적으로 예외를 발생시키는 부분
throw new IllegalArgumentException("미성년자는 접근할 수 없습니다!");
}
System.out.println("프로그램 종료");
}
}
IllegalArgumentException
예외 구조와 종류
✔ Checked Exception → 컴파일 시점에 체크됨 (반드시 try-catch로 처리해야 함)
✔ Unchecked Exception → 실행(Runtime) 중에 발생 (강제 처리 X, 개발자가 신경 써야 함)
📌 즉, Checked Exception은 “필수 처리”, Unchecked Exception은 “개발자가 처리 선택 가능” 🚀
구분 | Checked Exception | Unchecked Exception |
발생 시점 | 컴파일 시 체크됨 | 실행(Runtime) 중 발생 |
처리 강제 여부 | try-catch 또는 throws 필수 | 선택 사항(예외 발생 시 런타임 오류) |
예제 | IOException, SQLException, InterruptedException | NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException |
주로 발생하는 상황 | 파일, 네트워크, DB 오류 | 코드 논리 오류 (잘못된 배열 인덱스, null 참조 등) |
📌 즉, Checked Exception은 예상 가능한 외부 오류(파일, 네트워크), Unchecked Exception은 논리적 버그! 🚀
🔹 Checked Exception 예제 (IOException)
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class CheckedExceptionExample {
public static void main(String[] args) {
try {
File file = new File("nonexistent.txt");
Scanner scanner = new Scanner(file); // ✅ 파일이 없으면 FileNotFoundException 발생
} catch (FileNotFoundException e) {
System.out.println("⚠️ 파일을 찾을 수 없습니다: " + e.getMessage());
}
}
}
📌 Checked Exception은 try-catch 없이 사용할 수 없음!
🔹 Unchecked Exception 예제 (NullPointerException)
public class UncheckedExceptionExample {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // ❌ NullPointerException 발생
}
}
Exception in thread "main" java.lang.NullPointerException
📌 Unchecked Exception은 try-catch 없이도 실행되지만, 실행 중 오류가 발생할 수 있음!
throws를 사용하여 Checked Exception 처리
✔ Checked Exception은 try-catch 외에도 throws 키워드로 예외를 넘길 수 있음
✔ 즉, 예외 처리를 “이 메서드를 호출하는 쪽”에서 하도록 미룰 수 있음
🔹 throws를 사용한 예제
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ThrowsExample {
public static void main(String[] args) throws FileNotFoundException {
File file = new File("nonexistent.txt");
Scanner scanner = new Scanner(file); // ✅ `throws` 덕분에 여기서 예외 처리를 강제하지 않음
}
}
✔ 하지만 main()에서 예외 처리를 안 하면 실행 중 오류 발생 가능
📌 즉, throws는 “예외를 넘기는 역할”을 하지만, 결국 어디선가 try-catch 해야 함!
Checked Exception을 Unchecked Exception으로 변환 가능
✔ Checked Exception이지만, RuntimeException으로 감싸서 Unchecked Exception처럼 만들 수도 있음!
🔹 Checked → Unchecked 변환 예제
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class ConvertToUnchecked {
public static void main(String[] args) {
try {
readFile();
} catch (RuntimeException e) {
System.out.println("⚠️ 처리되지 않은 예외 발생: " + e.getMessage());
}
}
public static void readFile() {
try {
File file = new File("nonexistent.txt");
Scanner scanner = new Scanner(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e); // ✅ Checked Exception을 Unchecked Exception으로 변환
}
}
}
✔ 즉, Checked Exception을 RuntimeException으로 감싸면 Unchecked Exception처럼 동작함! 🚀
✅ Checked Exception vs Unchecked Exception을 코드에서 어떻게 알 수 있을까? 🚀
✔ 처음에는 구분하기 어려울 수 있지만, 익숙해지면 어떤 예외가 필수 처리(Checked)인지, 개발자가 신경 써야 하는 예외(Unchecked)인지 쉽게 알 수 있음
✔ 하지만, 코드 작성 중에도 “이 예외가 Checked인지 Unchecked인지” 바로 확인하는 방법이 있음
📌 1. 예외가 Checked인지 Unchecked인지 확인하는 방법
✅ 예외가 Exception을 상속하지만 RuntimeException을 상속하지 않으면 Checked Exception!
✅ 예외가 RuntimeException을 상속하면 Unchecked Exception!
📌 2. 예외의 부모 클래스를 확인하는 방법
✔ 자바 공식 문서(Java API)에서 예외 클래스의 부모 클래스를 보면 바로 구분할 수 있음.
🔹 Checked Exception 예제 (FileNotFoundException)
// Checked Exception (반드시 처리해야 함)
public class FileNotFoundException extends IOException { }
✔ IOException은 RuntimeException을 상속하지 않음 → Checked Exception (필수 처리 필요)
🔹 Unchecked Exception 예제 (NullPointerException)
// Unchecked Exception (개발자가 신경 써야 함)
public class NullPointerException extends RuntimeException { }
✔ RuntimeException을 상속 → Unchecked Exception (필수 처리 X, 런타임 오류 발생 가능)
📌 3. 코딩 중 필수 처리 여부를 확인하는 방법
✔ 실제 코드를 작성할 때 Checked Exception은 “컴파일 오류”가 발생하므로 바로 알 수 있음!
🔹 Checked Exception 예제 (컴파일 오류 발생!)
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class CheckedExceptionExample {
public static void main(String[] args) {
File file = new File("test.txt");
Scanner scanner = new Scanner(file); // ❌ 컴파일 오류 발생 (Unhandled exception: FileNotFoundException)
}
}
📌 즉, try-catch를 쓰지 않으면 컴파일 오류가 나서 “반드시 처리해야 하는 예외”라는 걸 바로 알 수 있음.
🔹 Unchecked Exception 예제 (컴파일 오류 없음)
public class UncheckedExceptionExample {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // ❌ 실행 중 NullPointerException 발생
}
}
📌 Unchecked Exception은 컴파일 오류가 발생하지 않아서, 실행(Runtime) 중에 오류가 터질 수 있음!
📌 4. 결국 예외를 필수로 처리해야 하는지 직접 알아야 할까?
✔ ✅ YES → Checked Exception (try-catch 또는 throws 필수)
• 컴파일 오류가 발생하면 Checked Exception (필수 처리)
• 대표 예외: IOException, SQLException, InterruptedException
• 파일, 네트워크, 데이터베이스 작업을 할 때 발생
✔ ❌ NO → Unchecked Exception (개발자가 논리적으로 신경 써야 함)
• 컴파일 오류가 발생하지 않음 (예외 처리는 개발자의 선택)
• 대표 예외: NullPointerException, IndexOutOfBoundsException
• null을 참조하거나 배열 범위를 벗어날 때 발생
📌 즉, 코드 작성 중에 컴파일러가 “예외 처리를 하라고 하면” Checked Exception이고, 그냥 실행하다가 오류가 발생하면 Unchecked Exception!
- RuntimeException - UncheckedException
- RuntimeException 을 상속받는 모든 예외를 UncheckedException 이라고 합니다.
- 예외처리를 컴파일러가 확인하지 않습니다.
- Exception - CheckedException
- Exception 클래스를 직접 상속받는 모든 예외를 CheckedException 이라고합니다. RuntimeException과 RuntimeException 을 상속받은 예외는 제외합니다.
- 예외처리를 컴파일러가 확인해 줍니다.
예외 전파
- 예외 전파는 메서드에서 발생한 예외가 해당 메서드 내에서 처리되지 않았을 때 메서드를 호출한 상위 메서드로 전달되는 과정을 말합니다.
- 예외가 프로그램 시작 지점(main()) 까지 전파되고 끝내 처리되지 않으면 프로그램이 비정상 종료 됩니다.
RuntimeException - UncheckedException
- 컴파일러가 예외 처리를 강제하지 않는 예외입니다.
- 예외 처리를 하지 않아도 컴파일 오류(빨간 줄) 가 발생하지 않습니다.
- 처리되지 않은 예외는 계속 프로그램 시작 지점까지 전파됩니다.
- 끝내 예외가 처리되지 않으면 프로그램이 비정상적으로 종료됩니다.
- RuntimeException 을 상속받는 모든 예외를 UncheckedException 이라고 합니다.
try-catch 활용
public class ExceptionPractice {
public void callUncheckedException() {
if (true) {
System.out.println("언체크 예외 발생");
throw new RuntimeException(); // ✅ 예외발생
}
}
}
public class Main {
public static void main(String[] args) {
ExceptionPractice exceptionPractice = new ExceptionPractice();
// ✅ 상위로 전파된 예외처리
try {
exceptionPractice.callUncheckedException();
} catch (RuntimeException e) { // ✅ 예외처리
System.out.println("언체크 예외 처리");
} catch (Exception e) {
System.out.println("체크 예외 처리");
}
System.out.println("프로그램 종료");
}
}
Exception - CheckedException
- Exception 클래스를 직접 상속받는 모든 예외를 CheckedException 이라고 합니다.
- RuntimeException 을 상속받는 예외는 제외.
- 컴파일러가 예외 처리를 강제하는 예외입니다.
- 예외 처리를 하지 않으면 “컴파일 오류가 발생한다(코드에 빨간줄)” 라고 이해하시면 쉽습니다.
- 반드시 try-catch로 예외를 처리하거나 throws 키워드를 사용해야 합니다. → throws 로 예외 처리의 책임을 호출자에게 전가할 수 있습니다.
try-catch 활용
public class ExceptionPractice {
public void callCheckedException() {
// ✅ try-catch 로 예외 처리
try {
if (true) {
System.out.println("체크예외 발생");
throw new Exception();
}
} catch (Exception e) {
System.out.println("예외 처리");
}
}
}
public class Main {
public static void main(String[] args) {
// 예외 실습 객체 인스턴스화
ExceptionPractice exceptionPractice = new ExceptionPractice();
// ✅ 체크예외 호출
exceptionPractice.callCheckedException();
}
}
throws 활용
- throws 키워드를 사용하여 예외를 호출한 곳에서 처리하도록 강제하는 방식입니다. → (책임 전가)
public class ExceptionPractice {
public void callCheckedException() throws Exception { // ✅ throws 예외를 상위로 전파
if (true) {
System.out.println("체크예외 발생");
throw new Exception();
}
}
}
package chapter3.exception;
public class Main {
public static void main(String[] args) {
// 예외 실습 객체 인스턴스화
ExceptionPractice exceptionPractice = new ExceptionPractice();
// 체크 예외 사용
// ✅ 반드시 상위 메서드에서 try-catch 를 활용해 주어야합니다.
try {
exceptionPractice.callCheckedException();
} catch (Exception e) {
System.out.println("예외처리");
}
}
}
Optional 이란?
Optional 객체는 null 을 안전하게 다루게 해주는 객체입니다.
null 이란?
- null은 프로그래밍에서 값이 없음 또는 참조하지 않음 을 나타내는 키워드
null 을 직접 다루는 대신 Optional 을 사용하면 NullPoinerException 을 방지할 수 있습니다.
Optional 이 왜 필요한가?
✔ Optional<T>는 “null 가능성”이 있는 값을 감싸는 래퍼 클래스.
✔ 즉, null 반환을 방지하고, NullPointerException(NPE)을 예방하는 데 사용.
✔ 자바 8부터 도입되었으며, 주로 “값이 없을 수도 있는 경우” 사용.
NullPointerException 을 방지해야 하는 이유
- NPE 예외는 런타임 예외이고 컴파일러가 잡아주지 못합니다.
- 예외가 발생했을 때 처리해 주지 않으면 프로그램이 종료됩니다.
- camp.getStudent() 는 null 을 반환할 수 있는 메서드입니다.
- 학생이 없는 경우 null을 반환하면 NPE(NullPointerException)가 발생합니다.
- null인 객체에서 student.getName()을 호출하는 것은 존재하지 않는 객체의 메서드를 실행하려는 것입니다.
public class Student {
// 속성
private String name;
// 생성자
// 기능
public String getName() {
return this.name;
}
}
public class Camp {
// 속성
private Student student;
// 생성자
// 기능: ⚠️ null 을 반환할 수 있는 메서드
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
Student student = camp.getStudent(); // ⚠️ student 에는 null 이 담김
// ⚠️ 아래 코드에서 NPE 발생! 컴파일러가 잡아주지 않음
String studentName = student.getName(); // 🔥 NPE 발생 -> 프로그램 종료
System.out.println("studentName = " + studentName);
}
}
NULL 직접 처리의 한계
if문을 활용해서 null 처리를 할 수 있지만 모든 코드에서 null 이 발생할 가능성을 미리 예측하고 처리하는 것은 현실적으로 어렵습니다.
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
Student student = camp.getStudent();
String studentName;
if (student != null) { // ⚠️ 가능은하지만 현실적으로 어려움
studentName = student.getName();
} else {
studentName = "등록된 학생 없음"; // 기본값 제공
}
System.out.println("studentName = " + studentName);
}
}
Optional 활용하기
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
- Optional 객체는 값이 있을 수도 있고 없을 수도 있는 컨테이너라고 생각하시면 됩니다.
- Optional 객체를 메서드 반환 자료형에 선언해서 해당 메서드가 null 이 반환될 가능성을 명확하게 전달할 수 있습니다.
- Optional.ofNullable() 을 사용하여 null 이 반환될 수 있는 객체를 감쌉니다.
- 활용할 때는 isPresent() 와 같은 Optional API 를 통해 안전하게 null 처리를 할 수 있습니다.
메서드 | 설명 |
Optional.of(value) | null이 아닌 값을 감싸는 Optional 객체 생성 (null이면 NullPointerException 발생) |
Optional.ofNullable(value) | null 여부와 상관없이 안전하게 감쌈 (null이면 빈 Optional 반환) |
Optional.empty() | 비어 있는 Optional 객체 반환 |
Optional.isPresent() | 내부 값이 null 일 경우 false 를 반환 |
public class OptionalExample {
public static void main(String[] args) {
// ✅ 값이 있는 경우
Optional<String> optionalValue = Optional.of("Hello");
System.out.println(optionalValue.get()); // Hello
// ✅ 값이 null일 가능성이 있는 경우
Optional<String> nullableValue = Optional.ofNullable(null);
System.out.println(nullableValue.isPresent()); // false (null이면 비어있는 Optional 반환)
// ✅ 비어있는 Optional
Optional<String> emptyValue = Optional.empty();
System.out.println(emptyValue.isPresent()); // false
}
}
isPresent() 활용 방법
- Optional 내부의 값이 존재할 경우에 true 반환합니다.
- 내부 값이 null 일 경우 false 를 반환합니다.
import java.util.Optional;
public class Camp {
// 속성
private Student student;
// 생성자
// 기능
// ✅ null 이 반환될 수 있음을 명확하게 표시
public Optional<Student> getStudent() {
return Optional.ofNullable(student); //null 가능성 대비
}
public void setStudent(Student student) {
this.student = student;
}
}
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
// isPresent() 활용시 true 를 반환하고 싶을때 활용
// Student newStudent = new Student();
// camp.setStudent(newStudent);
// Optional 객체 반환받음
Optional<Student> studentOptional = camp.getStudent();
// Optional 객체의 기능 활용
boolean flag = studentOptional.isPresent(); // false 반환
if (flag) {
// 존재할 경우
Student student = studentOptional.get(); // ✅ 안전하게 Student 객체 가져오기
String studentName = student.getName();
System.out.println("studentName = " + studentName);
} else {
// null 일 경우
System.out.println("학생이 없습니다.");
}
}
}
Optional 값 가져오기 (orElse, orElseGet, orElseThrow)
메서드 | 설명 |
get() | 값이 없으면 NoSuchElementException 발생 (주의 필요) |
orElse(defaultValue) | 값이 있으면 반환, 없으면 defaultValue 반환 |
orElseGet(supplier) | 값이 있으면 반환, 없으면 람다 실행 (defaultValue 대신 동적 처리 가능) |
orElseThrow() | 값이 없으면 NoSuchElementException 발생 |
orElseThrow(Supplier) | 값이 없으면 커스텀 예외 발생 |
🔹 orElse와 orElseGet 차이
public class OptionalExample {
public static void main(String[] args) {
Optional<String> optionalValue = Optional.ofNullable(null);
// ✅ 기본값 반환
String value1 = optionalValue.orElse("기본값");
System.out.println(value1); // 기본값
// ✅ 동적 기본값 처리
String value2 = optionalValue.orElseGet(() -> "동적 기본값");
System.out.println(value2); // 동적 기본값
// ✅ 예외 발생
try {
String value3 = optionalValue.orElseThrow(() -> new RuntimeException("값이 없습니다!"));
} catch (RuntimeException e) {
System.out.println(e.getMessage()); // 값이 없습니다!
}
}
}
📌 즉, orElse()는 기본값을 직접 제공하고, orElseGet()은 람다로 동적 처리가 가능
orElseGet() 활용 방법
- orElseGet()은 값이 없을 때만 기본값을 제공하는 로직을 실행하는 메서드입니다.
- orElseGet()을 제대로 활용하려면 람다 표현식을 이해해야 합니다.
- 하지만 이 부분은 이후 수업에서 다룰 예정이므로 지금은 메서드를 매개변수로 전달한다 정도로 이해하셔도 충분합니다
import java.util.Optional;
public class Camp {
// 속성
private Student student;
// 생성자
// 기능
// ✅ null 이 반환될 수 있음을 명확하게 표시
public Optional<Student> getStudent() {
return Optional.ofNullable(student);
}
}
import java.util.Optional;
public class Main {
public static void main(String[] args) {
Camp camp = new Camp();
// ✅ Optional 객체의 기능 활용 (orElseGet 사용)
Student student = camp.getStudent()
.orElseGet(() -> new Student("미등록 학생"));
System.out.println("studentName = " + student.getName());
}
}
Optional을 사용하면 모든 객체에 붙여야 할까?
❌ No! 모든 객체에 Optional을 사용할 필요는 없음.
✔ Optional은 “값이 없을 수도 있는 경우”에만 사용하는 게 좋음.
✔ “항상 값이 존재하는 필드”에는 Optional을 쓰면 오히려 불필요한 객체 할당이 늘어나 성능이 나빠질 수 있음
✅ 언제 Optional을 사용해야 할까?
1. null을 반환할 가능성이 있는 메서드 결과값
public Optional<String> getUsername(User user) {
return Optional.ofNullable(user.getName());
}
2. 데이터베이스 조회 결과 (findById() 같은 경우)
Optional<User> user = userRepository.findById(1L);
3. API 응답 값 (null이 올 수도 있는 경우)
Optional<Response> response = apiClient.getResponse();
🚨 하지만, 필드 변수에는 Optional을 사용하지 않는 게 좋음 🚨
// ❌ Optional 필드를 사용하면 메모리 낭비가 발생할 수 있음!
public class User {
private Optional<String> name; // ❌ 필드에 Optional 사용 X
}
✅ Unchecked Exception vs Checked Exception vs Optional 비교 🚀
개념 | Checked Exception ✅ | Unchecked Exception ❌ | Optional 🤔 |
발생 시점 | 컴파일 시 체크됨 (필수 처리) | 런타임(Runtime) 중 발생 | 컴파일 시 체크 X (개발자가 선택적으로 사용) |
처리 강제 여부 | try-catch 또는 throws 필수 | 예외 처리 강제 X, 실행 중 오류 발생 가능 | 예외 처리 강제 X, orElse(), ifPresent() 등으로 안전하게 다룰 수 있음 |
사용 목적 | 외부적인 오류 (파일, 네트워크, DB 등) | 개발자의 논리적 오류 (null 참조, 배열 범위 초과 등) | null을 안전하게 다루기 위한 대체 수단 |
대표 예외 | IOException, SQLException, InterruptedException | NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException | Optional<User> findById(Long id) |
컴파일 시 오류 여부 | 예외 처리를 안 하면 컴파일 오류 발생 | 예외 처리를 안 해도 컴파일 오류 없음 (하지만 실행 중 오류 발생 가능) | 컴파일 오류 없음 (null 대신 안전하게 값 관리 가능) |
예외 처리 방식 | try-catch 또는 throws | try-catch (필수 아님) | orElse(), orElseThrow(), ifPresent() |
📌 즉, Checked Exception은 예외 처리를 강제하지만, Unchecked Exception과 Optional은 개발자가 알아서 처리해야 함.
📌 언제 각각을 사용해야 할까?
사용 상황 | Checked Exception ✅ | Unchecked Exception ❌ | Optional 🤔 |
파일 입출력 (파일이 없을 수도 있음) | ✅ IOException (파일이 없을 가능성이 있음) | ❌ | ❌ |
네트워크 오류 (서버 다운 가능성) | ✅ SQLException (DB 접속 오류) | ❌ | ❌ |
배열의 잘못된 인덱스 접근 | ❌ | ✅ ArrayIndexOutOfBoundsException | ❌ |
null 체크 없이 안전한 코드 작성 | ❌ | ❌ | ✅ Optional<User> |
DB 조회 (findById() 결과가 없을 수도 있음) | ❌ | ❌ | ✅ Optional<User> |
예시
✅ 1️⃣ Checked Exception 예제 (IOException - 파일 읽기 오류)
✔ 파일이 없을 경우 예외가 발생할 가능성이 있으므로 try-catch 필수!
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class CheckedExceptionExample {
public static void main(String[] args) {
try {
File file = new File("nonexistent.txt");
Scanner scanner = new Scanner(file); // ❌ FileNotFoundException 발생 가능 → `try-catch` 필수
} catch (FileNotFoundException e) {
System.out.println("⚠️ 파일을 찾을 수 없습니다: " + e.getMessage());
}
}
}
✔ 출력 (파일이 없을 경우)
⚠️ 파일을 찾을 수 없습니다: nonexistent.txt (지정된 파일을 찾을 수 없습니다)
✅ 2️⃣ Unchecked Exception 예제 (NullPointerException)
✔ 개발자의 논리적 실수로 인해 실행 중 예외 발생 (컴파일 시에는 오류 없음)
public class UncheckedExceptionExample {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // ❌ NullPointerException 발생
}
}
✔ 출력 (실행 중 오류 발생)
Exception in thread "main" java.lang.NullPointerException
✅ 3️⃣ Optional을 활용한 안전한 null 처리 (Optional<String>)
✔ 기존에는 null을 반환했지만, Optional을 사용하면 null 체크가 필요 없음!
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
Optional<String> name = getUserName(false); // ✅ null 대신 Optional 사용 → 안전하게 처리 가능
System.out.println(name.orElse("기본 이름")); // 값이 없으면 "기본 이름" 반환
}
public static Optional<String> getUserName(boolean exists) {
if (exists) {
return Optional.of("홍길동");
} else {
return Optional.empty(); // ✅ null 대신 빈 Optional 반환
}
}
}
✔ 출력 결과 (exists = false일 경우)
기본 이름
컬렉션(Collection)이란?
✔ 컬렉션(Collection)은 데이터를 효율적으로 저장하고 관리하는 자료 구조(데이터 컨테이너)
✔ 배열(Array)처럼 여러 개의 데이터를 저장할 수 있지만, 크기가 동적으로 변경되고 다양한 기능을 제공함.
✔ 자바의 Collection Framework를 사용하면, 데이터를 쉽고 효율적으로 추가/삭제/검색할 수 있음.
https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html
- 자바 컬렉션 프레임워크는 이러한 자료구조들을 쉽게 사용할 수 있도록 인터페이스와 구현체(ArrayList, HashSet, HashMap 등)를 제공하는 집합입니다.
- 컬렉션을 통해 데이터 저장, 조회, 삭제, 정렬 등 다양한 기능을 간편하게 구현할 수 있습니다.
- 배열과 다르게 컬렉션은 길이를 동적으로 변경할 수 있습니다.(추가 삭제 시 유연하게 길이가 변경됩니다.)

배열의 한계
- 배열은 크기가 고정되어 있어서 한 번 설정하면 길이를 변경할 수 없습니다. → 배열의 길이 초과 시 에러가 발생합니다.
- 자바에서는 다양한 컬렉션 클래스(ArrayList, HashSet, HashMap 등)를 제공합니다.
- 컬렉션 객체를 활용해 데이터들을 저장하고 관리할 수 있습니다.
- 배열과 다르게 컬렉션은 길이를 동적으로 변경할 수 있습니다.(추가 삭제 시 유연하게 길이가 변경됩니다.)
컬렉션 종류와 특징
인터페이스 | 특징 | 구현체 |
List(리스트) | 순서 유지, 중복 허용 | ArrayList, LinkedList, Vector, Stack |
Set(집합) | 순서 없음, 중복 불가 | HashSet, LinkedHashSet, TreeSet |
Map(맵) | 키-값 구조, 키 중복 불가 | HashMap, LinkedHashMap, TreeMap, Hashtable |
2차 보강
✔ 자바에서 Collection은 여러 개의 요소(데이터)를 담을 수 있는 인터페이스(설계도)를 의미
✔ Class처럼 객체를 만들 수는 없지만, 메서드의 구조(형태)를 정의 할 수 있음.
✔ 인터페이스를 implements(구현)하면 해당 인터페이스에 정의된 메서드를 반드시 구현해야 함.
Collection
- List
- ArrayList
- LinkedList
- Vector
- Set
- HashSet
- LinkedHashSet
- TreeSet
- Queue
- PriorityQueue
- LinkedList
Map은 Collecion의 하위 인터페이스가 아님
- Map
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable
📌 Collection 인터페이스의 주요 메서드
메서드 | 설명 | 사용 가능한 컬렉션 |
add(E e) | 요소 추가 | ✅ List, Set, Queue |
remove(Object o) | 요소 삭제 | ✅ List, Set, Queue |
contains(Object o) | 해당 요소가 있는지 확인 | ✅ List, Set, Queue |
size() | 요소 개수 반환 | ✅ List, Set, Queue |
isEmpty() | 비어있는지 확인 | ✅ List, Set, Queue |
clear() | 모든 요소 제거 | ✅ List, Set, Queue |
iterator() | 요소를 순회할 Iterator 반환 | ✅ List, Set, Queue |
toArray() | 컬렉션을 배열로 반환 | ✅ List, Set, Queue |
🍒 List 인터페이스
- List는 “순서 보장, 중복 허용”을 지원하는 컬렉션
- 배열과 비슷하지만 크기가 자동으로 조절됨
구현체 | 특징 | 차이점 |
ArrayList | 배열 기반, 검색 속도 빠름 | 삽입/삭제 느림 |
LinkedList | 노드 기반, 삽입/삭제 빠름 | 검색 느림 |
Vector | ArrayList와 유사, 동기화 지원 | 멀티스레드 환경에서 사용 |
🔹 List 인터페이스의 주요 메서드
메서드 | 설명 |
add(int index, E element) | 특정 위치에 요소 추가 |
get(int index) | 특정 위치에 요소 가져오기 |
set(int index, E element) | 특정 위치의 요소 변경 |
remove(int index) | 특정 위치의 요소 삭제 |
indexOf(Object o) | 요소의 인덱스 반환 |
subList(int from, int to) | 특정 범위의 요소 리스트 반환 |
🍒 Set 인터페이스
- Set은 중복 없는 데이터를 저장할 때 사용
구현체 | 특징 | 차이점 |
HashSet | 순서 없음, 빠른 검색 | 순서 보장 안 됨 |
LinkedHashSet | 입력 순서 유지 | 정렬 없음 |
TreeSet | 자동 정렬(오름차순) | 속도 느림 |
🔹 Set 인터페이스의 주요 메서드
메서드 | 설명 |
add(E e) | 요소 추가 (중복 허용 X) |
remove(Object o) | 요소 삭제 |
contains(Object o) | 요소 포함 여부 확인 |
size() | 요소 개수 반환 |
iterator() | Iterator 반환 |
🍒 Queue 인터페이스
- FIFO(선입선출)
구현체 | 특징 | 차이점 |
PriorityQueue | 우선순위 큐(기본 오름차순) | 정렬된 순서로 요소 반환 |
LinkedList | FIFO 방식 큐 지원 | 일반 리스트 기능도 포함 |
🔹 Queue 인터페이스의 주요 메서드
메서드 | 설명 |
offer(E e) | 요소 추가(add()와 유사) |
poll() | 첫 번째 요소 제거 및 반환 |
peek() | 첫 번째 요소 반환(삭제 X) |
🍒 Map 인터페이스
- “Key-Value” 형태
구현체 | 특징 | 차이점 |
HashMap | 순서 없음, 빠른 검색 | 키 순서 보장 안됨 |
LinkedHashMap | 입력 순서 유지 | 느림 |
TreeMap | 키 정렬(오름차순) | 속도 느림 |
🔹 Map 인터페이스의 주요 메서드
메서드 | 설명 |
put(K ket, V value) | 키-값 추가 |
get(Object key) | 키에 해당하는 값 반환 |
remove(Object Key) | 키-값 삭제 |
containsKey(Object key) | 특정 키 존재 여부 확인 |
containsValue(Object value) | 특정 값 존재 여부 확인 |
List 인터페이스를 구현한 ArrayList
- ArrayList 는 요소의 순서를 유지하고 중복된 값을 저장할 수 있는 자료구조입니다.
- 요소 추가 → add("값")
- 요소 조회 → get(인덱스)
- 요소 제거 → remove("값")
- 대표적인 구현체로는 ArrayList , LinkedList가 있습니다.
// List 를 구현한 ArrayList
ArrayList<String> names = new ArrayList<>();
names.add("Spartan"); // 1 번째 요소 추가
names.add("Steve"); // 2 번째 요소 추가
names.add("Isac"); // 3 번째 요소 추가
names.add("1");
names.add("2");
// ✅ 순서 보장
System.out.println("names = " + names);
// ✅ 중복 데이터 허용
names.add("Spartan");
System.out.println("names = " + names);
// ✅ 단건 조회
System.out.println("1 번째 요소 조회: " + names.get(0)); // 조회 Spartan
// ✅ 데이터 삭제
names.remove("Steve");
System.out.println("names = " + names);
Set 인터페이스를 구현한 HashSet
- HashSet 은 순서를 유지하지 않고 중복을 허용하지 않습니다.
- → 순서를 보장하지 않기 때문에 get() 지원을 하지 않습니다.
- 요소 추가 → add("값")
- 요소 제거 → remove("값")
- 대표적인 구현체로는 HashSet , TreeSet 이 있습니다.
// Set 을 구현한 HashSet
HashSet<String> uniqueNames = new HashSet<>();
// ✅ 추가
uniqueNames.add("Spartan");
uniqueNames.add("Steve");
uniqueNames.add("Isac");
uniqueNames.add("1");
uniqueNames.add("2");
// ⚠️ 순서를 보장 안함
System.out.println("uniqueNames = " + uniqueNames);
uniqueNames.get(0); // ❌ get 사용 불가
// ⚠️ 중복 불가
uniqueNames.add("Spartan");
System.out.println("uniqueNames = " + uniqueNames);
// ✅ 제거
uniqueNames.remove("Spartan");
System.out.println("uniqueNames = " + uniqueNames);
Map 인터페이스를 구현한 HashMap
- HashMap 은 키(Key) - 값(Value) 구조로 데이터를 저장합니다.(키: 값)
- 키(Key) 는 중복될 수 없지만 값(Value) 은 중복 가능합니다.
- 순서를 보장하지 않습니다.
- 요소 추가 → put(”키”, 값)
- 요소 조회 → get(”키”)
- 요소 제거 → remove("Steve")
- 키 확인 → keySet()
- 값 확인 → values()
- 대표적인 구현체로는 HashMap, TreeMap 이 있습니다.
// Map 을 구현한 HashMap
HashMap<String, Integer> memberMap = new HashMap<>();
// ✅ 추가
memberMap.put("Spartan", 15);
memberMap.put("Steve", 15); // ✅ 값은 중복 가능
memberMap.put("Isac", 1);
memberMap.put("John", 2);
memberMap.put("Alice", 3);
// ⚠️ 순서 보장 안함
System.out.println("memberMap = " + memberMap);
// ⚠️ 키 중복 불가: 값 덮어쓰기 발생
memberMap.put("Alice", 5);
System.out.println("memberMap = " + memberMap);
// ✅ 조회: 15
System.out.println(memberMap.get("Steve"));
// ✅ 삭제 가능
memberMap.remove("Spartan");
System.out.println("memberMap = " + memberMap);
// ✅ 키 확인
Set<String> keys = memberMap.keySet();
System.out.println("keys = " + keys);
// ✅ 값 확인
Collection<Integer> values = memberMap.values();
System.out.println("values = " + values);
전체 코드
package chapter2.collection;
import java.util.*;
public class Main {
public static void main(String[] args) {
// 배열의 한계
// 선언과 동시에 길이를 설정 필요
int[] numbers = new int[3];
numbers[0] = 10;
numbers[1] = 20;
numbers[2] = 30;
// 배열의 정적인 특징의 한계
// numbers[3] = 40;
//컬렉션
ArrayList<Integer> arrayList = new ArrayList<Integer>();
// <> : 제너릭 : 어떤 객체를 다룰건지
// 두번째 <> 내용 생략가능
arrayList.add(10);
arrayList.add(20);
arrayList.add(30);
arrayList.add(40);
// ArrayList 활용
ArrayList<String> names = new ArrayList<>();
// 데이터 추가
names.add("sun");
names.add("steve");
names.add("Isac");
names.add("1");
System.out.println("names = " + names); // names = [sun, steve, Isac, 1]
// 중복 데이터 허용
names.add("sun");
System.out.println("names = " + names); // names = [sun, steve, Isac, 1, sun]
// 데이터 단건 조회
String name1 = names.get(0);
System.out.println("name1 = " + name1); // name1 = sun
// 데이터 삭제
names.remove("steve");
System.out.println("name1 = " + names); // name1 = [sun, Isac, 1, sun]
names.remove("sun");
System.out.println("name1 = " + names); // name1 = [Isac, 1, sun]
// HashSet 활용
HashSet<String> uniqueNames = new HashSet<>();
// 데이터 추가
uniqueNames.add("sunn");
uniqueNames.add("steve");
uniqueNames.add("joo");
uniqueNames.add("1");
// 순서 보장 안됨 -> get() 활용 불가
System.out.println("uniqueNames = " + uniqueNames); // uniqueNames = [1, sunn, steve, joo]
// 중복 데이터 불가
uniqueNames.add("sunn");
System.out.println("uniqueNames = " + uniqueNames); // uniqueNames = [1, sunn, steve, joo]
// 데이터 제거
uniqueNames.remove("sunn");
System.out.println("uniqueNames = " + uniqueNames); // uniqueNames = [1, steve, joo]
// HashMap 활용
HashMap<String, Integer> memberMap = new HashMap<>();
// <키, 값> 구조로 저장
// 데이터 저장
memberMap.put("sun", 15);
memberMap.put("joo", 13);
memberMap.put("steve", 20);
memberMap.put("alice", 3);
// 순서 보장이 안됨
System.out.println("memberMap = " + memberMap); // memberMap = {steve=20, alice=3, joo=13, sun=15}
// 키 중복 불가
memberMap.put("sun", 15);
System.out.println("memberMap = " + memberMap); // memberMap = {steve=20, alice=3, joo=13, sun=15}
// 단건 조회
Integer Num = memberMap.get("sun");
System.out.println("Num = " + Num); // Num = 15
// 데이터 삭제
memberMap.remove("alice");
System.out.println("memberMap = " + memberMap); // memberMap = {steve=20, joo=13, sun=15}
// 키 확인
Set<String> keySet = memberMap.keySet();
System.out.println("keySet = " + keySet); // keySet = [steve, joo, sun]
// 값 확인
Collection<Integer> values = memberMap.values();
System.out.println("values = " + values); // values = [20, 13, 15]
}
}
제네릭(Generic)이란?
- 제네릭은 클래스, 메서드 등에 사용되는 <T>**타입 매개변수**를 의미합니다.
- “데이터 타입을 미리 지정하지 않고, 나중에 사용할 때 타입을 결정하는 기능”
- 제네릭을 활용하면 코드 재사용성과 타입 안정성을 보장받을 수 있습니다.
- 하지만 과도하게 사용하면 오히려 복잡해질 수 있으므로 주의해야 합니다.
제네릭 <T>(타입매개변수)
- <T>(타입매개변수) 는 제네릭에서 타입을 의미하는 자리입니다.
- 실제 데이터 타입으로 대체되어 활용 됩니다.
타입소거(**Erasure**)
- 타입 소거는 컴파일 시점에 제네릭 타입 정보를 제거하는 과정입니다.
- <T> 타입 매개변수 부분은 Object 로 대체됩니다.
- 필요한 경우 컴파일러가 자동으로 강제 다운 캐스팅(cast) 코드를 삽입하여 타입 안전성을 보장합니다.
제너릭 미사용 (형 변환 필요, 오류 발생 가능)
import java.util.ArrayList;
public class NoGenericsExample {
public static void main(String[] args) {
ArrayList list = new ArrayList(); // ❌ 제너릭 미사용 → Object 타입으로 저장됨
list.add("Hello"); // 문자열 저장
list.add(123); // 정수 저장 (다른 타입 가능)
String str = (String) list.get(0); // ✅ 형 변환 필요
System.out.println(str);
// ❌ 형 변환 오류 (Integer → String)
String num = (String) list.get(1); // ClassCastException 발생
}
}
제너릭 사용
import java.util.ArrayList;
public class GenericsExample {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(); // ✅ 제너릭 사용 → String 타입만 저장 가능
list.add("Hello"); // ✅ 정상
// list.add(123); // ❌ 컴파일 오류 발생! (Integer는 저장 불가능)
String str = list.get(0); // ✅ 형 변환 없이 안전하게 가져오기 가능
System.out.println(str);
}
}
제너릭 클래스
- 제네릭 클래스는 클래스 선언부에 <T> 가 선언된 클래스입니다.
- 제네릭 클래스는 클래스 선언 시 타입 매개변수를 사용해 다양한 데이터 타입을 안전하게 처리할 수 있는 구조입니다.
- GenericBox<T> 를 활용해서 String, Integer, Double 등 다양한 타입 저장 가능 합니다.
// ✅ 제너릭 클래스 정의 (T는 타입 변수)
class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
public class GenericsClassExample {
public static void main(String[] args) {
Box<String> stringBox = new Box<>(); // ✅ String 타입 지정
stringBox.set("Hello");
System.out.println(stringBox.get()); // Hello
Box<Integer> intBox = new Box<>(); // ✅ Integer 타입 지정
intBox.set(123);
System.out.println(intBox.get()); // 123
}
}
제네릭 메서드(Generic Method)
- 제네릭 메서드는 메서드 선언부에 <T> 가 선언된 메서드입니다.
- 제네릭 메서드는 클래스 제네릭 타입과 별개로 독립적인 타입 매개변수를 가집니다.
public class GenericMethodExample {
// ✅ 제너릭 메서드 (T는 메서드에서만 사용)
public static <T> void print(T value) {
System.out.println(value);
}
public static void main(String[] args) {
print("Hello"); // ✅ String 사용
print(123); // ✅ Integer 사용
print(3.14); // ✅ Double 사용
}
}
자바에서 <T> 제네릭이 활용된 곳
- Optional<T>와 ArrayList<T> 컬렉션 클래스 등은 제네릭 클래스입니다.
- 여기서 <T>는 실제 데이터 타입으로 대체되어 활용됩니다.
전체 코드
package chapter2.generic;
public class GenericBox<T> {
private T item;
public GenericBox(T item){
this.item = item;
}
public T getItem() {
return item;
}
// 일반 메서드
public void printItem(T item){
System.out.println("item = " + item);
}
// 제너릭 메서드
// 클래스의 타입 매개변수와, 메서드의 타입 매개변수는 별도로 작동 함
public <S> void printBoxItem(S item){
System.out.println("item = " + item);
}
}
package chapter2.generic;
public class Main {
public static void main(String[] args) {
// 1. 재사용 불가
Box box1 = new Box(100);
//다른 데이터 타입 생성 불가
// Box box2 = new Box("String");
// 2. 낮은 타입 안정성
ObjectBox strBox = new ObjectBox("ABC");
ObjectBox intBox = new ObjectBox(100);
// item을 활용하기 위해서는 다운캐스팅 필요
String item = (String) strBox.getItem();
// Object형을 반환하는데 해당 데이터 타입을 바로 출력할 수 없어서 변환해줌
System.out.println("item = " + item); // item = ABC
// 다운캐스팅은 잘못 사용하면 컴파일러가 미리 오류를 잡아주지 못함
// String item2 = (String) intBox.getItem();
// Generic 활용
// 1. 재사용성 보장(타입소거 T -> Object)
GenericBox<String> strGBox = new GenericBox<>("ABC");
GenericBox<Integer> intGBox = new GenericBox<>(100);
GenericBox<Double> doubleGBox = new GenericBox<>(1.1);
// 2. 타입 안정성 보장(타입소거 : 자동으로 다운캐스팅 삽입)
// 컴파일러가 타입소거 과정에서 알아서 형변환 해줌
String strGBoxItem = strGBox.getItem();
System.out.println("strGBoxItem = " + strGBoxItem); // strGBoxItem = ABC
Integer intGBoxItem = intGBox.getItem();
System.out.println("intGBoxItem = " + intGBoxItem); // intGBoxItem = 100
Double doubleGBoxItem = doubleGBox.getItem();
System.out.println("doubleGBoxItem = " + doubleGBoxItem); // doubleGBoxItem = 1.1
// 일반 메서드
strGBox.printItem("ABC");
// strGBox.printItem(100); // 인스턴스 생성 시 타입 매개변수가 선언됐기 때문에 사용 불가
// 제너릭 메서드
strGBox.printBoxItem(100);
strGBox.printBoxItem("ABC");
strGBox.printBoxItem(5.5);
// 클래스의 타입 매개변수와 별도로 작동함
}
}
익명 클래스란(Anonymous Class)?
✔ 익명 클래스: 이름이 없는 “일회성 클래스” (한 번만 사용할 클래스)
람다(Lambda)
✔ 람다(Lambda): 익명 클래스를 더 간결하게 표현한 것 (주로 함수형 인터페이스(메서드가 1개만 있는 인터페이스)에서 사용)
익명 클래스
- 별도의 클래스 파일을 만들지 않고 코드 내에서 일회성으로 정의해 사용하기 때문에 이름이 없다고 부릅니다.
- 인터페이스, 클래스(일반, 추상)의 구현과 상속을 활용해 익명 클래스를 구현할 수 있습니다.
- → 람다에서는 인터페이스를 사용한 익명 클래스가 활용됩니다.
람다
- 함수형 인터페이스 를 통해서 구현하는 것을 권장합니다.
- → 하나의 추상 메서드만 가져야하기 때문입니다.
- → 하지만, 하나의 추상 메서드를 가진 일반 인터페이스를 통해서도 사용 가능합니다.
람다식을 활용한 익명 클래스 변환 방법
- 컴파일 시점에 컴파일러가 (a, b) -> a + b 람다 표현식을 보고 sum() 메서드를 가진 익명 클래스를 구현합니다.
- Calculator 인터페이스에 추상 메서드가 하나뿐이기 때문에 컴파일러는 (a, b) -> a + b 람다 표현식이sum() 메서드라고 추론 가능하기 때문입니다.
// 람다 표현식
Calculator calculator1 = (a, b) -> a + b;
// 익명클래스
Calculator calculator1 = new Calculator() {
@Override
public int sum(int a, int b) {
return a + b;
}
};
@FunctionalInterface // ✅ 함수형 인터페이스 선언
public interface Calculator {
int sum(int a, int b); // ✅ 오직 하나의 추상 메서드만 선언해야합니다.
}
public class Main {
public static void main(String[] args) {
...
// ✅ 람다식 활용
Calculator calculator2 = (a, b) -> a + b;
int ret2 = calculator2.sum(2, 2);
System.out.println("ret2 = " + ret2);
}
}
람다 사용시 주의사항
람다식을 활용할때는 꼭 함수형 인터페이스를 활용합시다.
- 함수형 인터페이스는 단 하나의 추상 메서드만 가지도록 강제하는 어노테이션입니다.
- 람다식에서는 함수형 인터페이스가 활용됩니다.
- 인터페이스에 두 개 이상의 추상 메서드가 존재하면 컴파일러가 어떤 메서드를 구현하는지 모호해지기 때문입니다.
- 예를 들어 오버로딩(Overloading) 기능을 통해 같은 이름의 sum() 메서드를 여러 형태로 정의한다면 람다 표현식이 어떤 메서드를 구현하는 것인지 명확하지 않아 모호성이 발생할 수 있습니다.
✅ 1️⃣ 익명 클래스로 작성한 코드
interface MyInterface {
void show();
}
public class AnonymousClassExample {
public static void main(String[] args) {
MyInterface obj = new MyInterface() {
@Override
public void show() {
System.out.println("익명 클래스 실행!");
}
};
obj.show();
}
}
✅ 2️⃣ 람다 표현식으로 변환 (()-> {} 사용)
interface MyInterface {
void show();
}
public class LambdaExample {
public static void main(String[] args) {
// ✅ 익명 클래스를 람다로 변환
MyInterface obj = () -> System.out.println("람다 실행!");
obj.show();
}
}
📌 함수형 인터페이스(@FunctionalInterface)와 람다
✔ 람다는 “함수형 인터페이스”에서만 사용 가능.
✔ “함수형 인터페이스”는 “메서드가 1개만 있는 인터페이스”를 의미.
✔ @FunctionalInterface를 붙여서 “람다에서 사용할 인터페이스”라고 표시
🔹 @FunctionalInterface 예제
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
public class LambdaExample {
public static void main(String[] args) {
// ✅ 람다 표현식으로 함수형 인터페이스 구현
Calculator calc = (a, b) -> a + b;
System.out.println(calc.add(5, 3)); // 8
}
}
오버로딩(Overloading) vs 오버라이딩(Overriding)
- **오버로딩**은 같은 클래스나 인터페이스 내에서 동일한 메서드 이름을 사용해서 선언하는 기능입니다. → 매개변수의 개수나 타입, 순서는 다르게 선언해야 합니다.
- **오버라이딩**은 부모 클래스에 정의된 메서드를 자식 클래스에서 재정의하는 것을 의미합니다.
람다식을 매개변수로 전달하는 방법
익명 클래스를 변수에 담아 전달
- 람다식 없이 직접 객체를 생성해서 전달하는 방식입니다.
- 클래스의 익명 객체를 만든 다음에 매개변수로 전달합니다.
public class Main {
public static int calculate(int a, int b, Calculator calculator) {
return calculator.sum(a, b);
}
public static void main(String[] args) {
Calculator cal1 = new Calculator() {
@Override
public int sum(int a, int b) {
return a + b;
}
};
// ✅ 익명 클래스를 변수에 담아 전달
int ret3 = calculate(3, 3, cal1);
System.out.println("ret3 = " + ret3); // 출력: ret3 = 6
}
}
람다식을 변수에 담아 전달
- 람다식을 변수에 담아 매개변수로 전달하는 방식입니다.
- 람다식을 전달하면 calculate() 메서드의 매개변수의 타입으로 Calculator 인터페이스를 구현했는지 추론되기 때문에 람다식을 전달 가능합니다.
public class Main {
public static int calculate(int a, int b, Calculator calculator) {
return calculator.sum(a, b);
}
public static void main(String[] args) {
Calculator cal2 = (a, b) -> a + b;
// ✅ 람다식을 변수에 담아 전달
int ret4 = calculate(4, 4, cal2);
System.out.println("ret4 = " + ret4); // 출력: ret4 = 8
}
}
람다식을 직접 전달
- 람다식을 직접 전달합니다.
- 마찬가지로 calculate() 메서드의 매개변수의 타입으로 Calculator 인터페이스를 구현했는지 추론됩니다.
public class Main {
public static int calculate(int a, int b, Calculator calculator) {
return calculator.sum(a, b);
}
public static void main(String[] args) {
// ✅ 람다식을 직접 매개변수로 전달
int ret5 = calculate(5, 5, (a, b) -> a + b);
System.out.println("ret5 = " + ret5); // 출력: ret5 = 10
}
}
스트림(stream) 이란?
✔ 스트림(Stream)은 “데이터 처리 기능”을 제공하는 자바의 기능.
✔ 배열이나 컬렉션(List, Set, Map)의 요소를 쉽게 “필터링, 변환, 정렬, 집계”할 수 있음.
✔ 반복문(for)을 사용하지 않고, 더 간결하고 효율적인 코드로 데이터를 다룰 수 있음.
✔ filter(), map(), sorted(), collect() 같은 “체이닝 방식”으로 가독성이 좋아짐.
- 스트림은 데이터를 효율적으로 처리할 수 있는 흐름입니다.
- 선언형 스타일로 가독성이 굉장히 뛰어납니다.
- 데이터 준비 → 중간 연산 → 최종 연산 순으로 처리됩니다.
- 스트림은 컬렉션(List, Set 등)과 함께 자주 활용됩니다.
스트림 처리 단계
- 스트림은 데이터 처리를 위해 여러 API를 제공합니다.
- 관련 문서 → https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html
- 아래는 대표적인 API 예시입니다.
단계 | 설명 | 주요 API |
1. 데이터 준비 | 컬렉션을 스트림으로 변환 | stream(), parallelStream() |
2. 중간 연산 등록 (즉시 실행되지 않음) |
데이터 변환 및 필터링 | map(), filter(), sorted() |
3. 최종 연산 | 최종 처리 및 데이터 변환 | collect(), forEach(), coumt() |
스트림 예제 전체 코드
- stream() → map() → collect() 순으로 데이터 흐름을 처리합니다.
- stream(): 데이터 준비 - 데이터를 스트림으로 변환하여 연산 흐름을 만들 준비합니다.
- map(): 중간 연산 등록 - 각 요소를 주어진 함수에 적용해서 변환합니다.
- collect(): 최종 연산 - 결과를 원하는 형태(List, Set)로 수집합니다.
- 반복문 없이 간결하게 데이터 변환이 가능합니다.
package chapter2.stream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> arrayList = new ArrayList<Integer>(List.of(1,2,3,4,5));
// for 명령형 스타일 : 각 요소 * 10 처리
List<Integer> ret1 = new ArrayList<>();
for(Integer num : arrayList){
Integer multipliedNum = num * 10;
ret1.add(multipliedNum);
}
System.out.println("ret1 = " + ret1);
// stream 선언형 스타일: 각 요소 * 10 처리
List<Integer> ret2 = arrayList.stream().map(num -> num *10).collect(Collectors.toList());
System.out.println("ret2 = " + ret2);
// steam > map > collect
// steam : 데이터를 스트림으로 변환해서 데이터 준비
// map : 중간 연산 등록 단계, 데이터를 어떻게 처리 할 것인지 명시
// collect : 최종 연산, 결과를 어떤 형태로 받을지 명시 해주는 것
// ArrayList를 List로 받는 이유
// 코드 수정을 최소화 하기 위해
// ArrayList는 List 중 한 종류다. 다른 타입으로 변경하기 위해
//스트림과 람다식을 활용
// 1. 익명 클래스를 만들어서 변수에 담아 매개변수로 전달
Function<Integer, Integer> function = new Function<Integer, Integer>(){
@Override
public Integer apply(Integer integer){
return integer * 10;
}
};
List<Integer> ret3 = arrayList.stream().map(function).collect(Collectors.toList());
System.out.println("ret3 = " + ret3);
// 2. 람다식을 만들어서 변수에 담아 매개변수로 전달
Function<Integer, Integer> functionLambda = (integer -> integer*10);
List<Integer> ret4 = arrayList.stream()
.map(functionLambda)
.collect(Collectors.toList());
System.out.println("ret4 = " + ret4);
// 3. 람다식을 직접 매개변수로 전달
List<Integer> ret5 = arrayList.stream()
.map(num -> num*10)
.collect(Collectors.toList());
System.out.println("ret5 = " + ret5);
// 4. 중간 연산 함께 사용 방법
// filter(), map()
// 리스트에서 짝수를 찾아서 * 10
List<Integer> ret6 = arrayList.stream()
.filter(num -> num % 2 == 0)
.map(num -> num * 10)
.collect(Collectors.toList());
System.out.println("ret6 = " + ret6);
}
}
'코딩 > sparta TIL' 카테고리의 다른 글
TIL 10 : 나태지옥, 백준 4단계(1차원 배열), 컬렉션 공부 (0) | 2025.02.28 |
---|---|
TIL 9 : BufferedReader, BufferedWriter (0) | 2025.02.27 |
CH2 계산기 과제 (0) | 2025.02.25 |
TIL 7 : Java 문법 종합반 2주차 (0) | 2025.02.24 |
TIL 6 : Java 문법 종합반 1주차 (1) | 2025.02.24 |