상속은 '~IS A' 관계일 때, 개념이 나뉘어 분류가 필요할 때 쓴다.
Coffee → Americano, Latte 식으로 구성하면 커피 종류가 너무 많아지는 Class Explosion이 발생할 수 있다. 이럴 땐 속성으로 해결하는 게 낫다.
public class Coffee {
private String name;
private int shot;
private int temp;
private boolean hasMilk;
}
기준 정리:
무기 → 검, 활 ✅ 좋은 상속 관계아메리카노 → 아이스 아메리카노 ❌ 속성으로 처리하는 게 나음절차지향은 덩어리가 커진 다음에 고치기 너무 힘들다. 객체지향은 실생활의 모든 것을 '객체'로 보고, 잘 만든 부품을 조합해서 사용한다.
핵심 장점:
1. 재사용성 — 잘 만든 부품을 다른 곳에서도 쓸 수 있다
2. 유지보수성 — 오류 범위가 특정되기 때문에 수정이 간편하다
3. 대규모 협업 — 역할 분리가 가능하다
하나의 타입으로 여러 객체를 다룰 수 있도록 하는 것. 같은 메서드인데 다른 결과가 나온다. 코드 수정을 최소화하면서 다양한 기능을 유연하게 확장할 수 있다.
오버로딩(Overloading)
하나의 클래스 안에 같은 이름의 메서드를 매개변수의 개수나 타입을 다르게 정의하는 것. 코드 일관성과 가독성이 올라가고, 협업 시 재사용성도 높아진다. 주로 생성자에 많이 사용한다.
오버라이딩(Overriding)
부모 클래스의 메서드를 자식 클래스에서 재정의해서 동작을 바꾸는 것. 리턴 타입은 유지해야 한다.
업캐스팅(Upcasting)
클래스가 구현한 상위 인터페이스나 부모 클래스 타입으로 참조하는 것. 다양한 객체를 동일한 타입으로 다룰 수 있게 된다.
Vehicle vehicle = new Bus(); // Bus → Vehicle로 자동 형변환
vehicle.excel(); // Bus의 오버라이딩된 excel() 실행
코드의 일관성을 유지하면서 다양한 구현체를 유연하게 교체하는 설계가 가능해진다.
다운캐스팅(Downcasting)
부모 클래스 타입으로 참조된 객체를 다시 자식 클래스 타입으로 변환하는 것. 자식 클래스에만 존재하는 메서드나 필드에 접근하고 싶을 때 쓴다.
Vehicle vehicle = new Bus(); // 업캐스팅
Bus bus = (Bus) vehicle; // 다운캐스팅
bus.excel(); // Bus 고유 기능 호출 가능
instanceof로 검사 후 사용하는 걸 권장한다.
객체의 내부 상태를 숨기고, 검증된 통로(메서드)로만 접근하도록 하는 것.
없으면?
구현:
private으로 감추고getter로 외부에서 읽고setter로 외부에서 값을 설정한다구현 세부사항 없이 클래스가 구현해야 할 메서드의 집합만을 정의한 설계 명세서다. implements 키워드를 사용한다.
장점:
| 구분 | Interface | 상속 |
|---|---|---|
| 의미 | CAN DO (~를 할 수 있다) | IS A (~이다) |
| 구현 | 다중 구현 가능 | 단일 상속만 가능 |
| 구조 | 수평적, 기능적 확장 | 수직적, 계층적 구조 |
공통점은 상속으로 묶고, 차이점은 인터페이스로 분리한다.
데이터 타입을 클래스나 메서드가 사용할 때 나중에 지정할 수 있도록 하는 타입이다. 자바는 타입에 엄격하기 때문에, 타입 간 오류를 줄이고자 등장했다.
public class Garage<T> {
private T car;
public Garage(T car) { this.car = car; }
public T getCar() { return this.car; }
}
Garage<String> garage1 = new Garage<>("전기차");
Garage<Integer> garage2 = new Garage<>(2025);
언제 사용?
List<T>, Map<K,V>, Set 등<T extends 부모클래스>
// T에 들어올 수 있는 클래스는 반드시 부모클래스이거나 그 자식이어야 함
그냥 Generic 그대로 쓰는 경우는 많지 않다.
여러 개의 데이터를 쉽고 효과적으로 처리할 수 있도록 자바에서 미리 만들어준 표준화된 자료구조 세트다.
순서가 있는 배열. 데이터 넣은 순서가 유지되고 중복이 허용된다. List 자체는 인터페이스라 직접 객체 생성은 불가하고, ArrayList, LinkedList, Vector 같은 구현 클래스를 활용한다.
실무 예시: 게시판 게시물 목록, 채팅 메시지 이력
중복 없는 집합. 순서는 보장되지 않는다.
실무 예시: 방문자 IP, 특정 글의 좋아요 누른 유저 ID
HashSet은 내부적으로 Hash Table을 사용해 빠른 검색과 조작을 지원한다. Hashcode 결과값을 기반으로 한 위치(slot)에 데이터를 저장한다.
key와 value가 한 쌍인 자료구조.
셋 다 인터페이스인 이유 → 다형성
List<String> list = new ArrayList<>();
List<String> list2 = new LinkedList<>();
// ArrayList로 선언했다가도 LinkedList로 갈아끼울 수 있다
throw — 예외를 발생시킨다. 조건이 맞으면 그 자리에서 바로 던진다.throws — '예외를 던질 수 있다'는 약속. 호출하는 쪽에서 처리해야 한다.public void setSpeed(int speed) throws IllegalArgumentException {
if (speed < 0) {
throw new IllegalArgumentException("음수 불가");
}
}
try 괄호 안에 자원을 선언하면, 블록이 끝날 때 자동으로 close()를 호출해주는 문법이다. 실제로 'with' 키워드를 쓰는 건 아니다.
try (Scanner sc = new Scanner(System.in)) {
// 사용
} // 예외가 터져도 sc.close() 자동 호출
파일, DB 연결, 스트림 같은 자원은 안 닫으면 메모리 릭이 발생한다. Scanner, BufferedReader, FileWriter 등 자바 표준 입출력 클래스는 모두 AutoCloseable 인터페이스를 구현하고 있다. 자원을 여러 개 선언하면 역순으로 close() 된다.
자바의 표준 예외로 표현하기 어려운 상황을 개발자가 직접 이름 붙여 만든 예외 클래스다. 이름 자체가 의미를 담으면 로그 분석도, 예외 처리 분기도 명확해진다.
// Unchecked (주로 이걸 씀) — 처리 강제 안 함
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
// Checked — 호출하는 쪽에서 반드시 처리해야 함
public class FileFormatInvalidException extends Exception {
public FileFormatInvalidException(String message) {
super(message);
}
}
하나의 프로그램 내에서 동시에 실행될 수 있는 독립적 실행 흐름 단위. Thread는 클래스다.

"빠른 실행"이 목적이 아니라 "동시에 수행하기 위해서" 쓴다.
ex. 고화질 영상을 다운받으면서 동시에 채팅 보내기
class MyThread extends Thread {
@Override
public void run() { ... }
}
new MyThread().start(); // start()로 실행 — run() 직접 호출하면 멀티스레드 안 됨
| 메서드 | 역할 |
|---|---|
start() | 새 스레드 생성 후 실행 |
run() | 실행 코드 정의 (직접 호출 금지) |
sleep(ms) | 현재 스레드 일시 정지 |
join() | 해당 스레드 끝날 때까지 대기 |
interrupt() | 스레드에 중단 요청 |
주의사항:
run() 내부에 try-catch 필수.volatile 키워드로 해결.private static volatile boolean stop = false; // 모든 스레드에 즉시 반영
스레드가 실행할 작업을 정의하기 위한 표준 인터페이스. '실행 흐름'과 '제어'를 분리할 수 있다.
// Thread 상속 — 작업과 실행 주체가 한 곳에 묶임
class MyThread extends Thread {
@Override
public void run() { ... }
}
// Runnable 구현 — 작업과 실행 주체가 분리됨 (실무에서 선호)
class MyTask implements Runnable {
@Override
public void run() { ... }
}
new Thread(new MyTask()).start();
Runnable을 쓰는 이유:
ExecutorService 같은 스레드 풀 도구들이 전부 Runnable 기반으로 동작한다new Thread(() -> System.out.println("작업 실행")).start();
| 구분 | Runnable | Thread |
|---|---|---|
| 역할 | 무엇을 할지 (작업 정의) | 누가 실행할지 (실행 주체) |
| 형태 | 인터페이스 | 클래스 |
| 상속 제약 | 없음 | 다른 클래스 상속 불가 |
실무에서는 직접 new Thread를 잘 안 하고 ExecutorService를 사용한다.
자바에서 스레드를 직접 만들지 않고, 스레드 풀을 통해 작업을 안전하게 병렬 처리하는 실행 관리 인터페이스다.
Thread를 계속 생성하지 못하는 이유:
new Thread()는 생성 비용이 높다. 스레드는 OS가 관리하는 무거운 자원이다.// Thread 직접 생성 — 대량 작업 시 위험
new Thread(() -> System.out.println("작업")).start();
// ExecutorService — 스레드 풀에서 재사용. 안정적
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(() -> System.out.println("작업"));
executor.shutdown();
풀 생성 방법 4가지:
| 메서드 | 특징 | 쓰는 시점 |
|---|---|---|
newFixedThreadPool(n) | 스레드 수 고정 | 처리량이 일정할 때 |
newCachedThreadPool() | 필요한 만큼 생성, 유휴 시 제거 | 작업량이 불규칙할 때 |
newSingleThreadExecutor() | 스레드 1개, 순차 실행 | 순서가 중요할 때 |
newScheduledThreadPool(n) | 지연·주기 실행 | 예약 작업, 배치 |
구성:
// execute — 결과 없음. 단순 작업(로그, 알림 등)에 사용
executor.execute(() -> System.out.println("실행"));
// submit — Future 반환, 결과·예외 추적 가능
Future<String> future = executor.submit(() -> "완료");
String result = future.get(); // 작업 끝날 때까지 대기 후 결과 반환
Callable은 작업 후 결과를 반환받고 싶을 때 사용한다.
장점: 자원 보호 / 재사용으로 인한 성능 향상 / 관리 편함
현업에서는 @Async 어노테이션을 쓰면 자동으로 ExecutorService가 켜진다.
종료는 반드시 해야 한다. 안 하면 JVM이 꺼지지 않는다.
executor.shutdown(); // 남은 작업 처리 후 종료 (일반적)
executor.shutdownNow(); // 대기 작업 취소, 즉시 강제 종료
주요 설정값:
| 설정 | 의미 |
|---|---|
corePoolSize | 항상 유지되는 최소 스레드 수 |
maximumPoolSize | 생성 가능한 최대 스레드 수 |
keepAliveTime | core 초과 스레드가 유휴 상태일 때 유지되는 시간 |
여러 스레드가 동시에 공유 자원에 접근하지 못하도록 일시적으로 접근을 하나로 제한하는 동기화 키워드다.
임계 영역(Critical Section): 공유 자원에 접근하는 코드의 일부분. 여러 스레드가 동시에 실행되어서는 안 되는 영역이다.
임계 영역의 조건:
1. 공유 자원 — 여러 스레드가 동시에 접근할 수 있는 변수여야 한다. 힙 영역의 변수여야 한다.
2. 수정 가능성 — 단순히 읽는 게 아니라 누군가 값을 바꿔야 한다.
3. 동시 접근 가능성 — 실제로 여러 스레드가 그 코드를 동시에 실행할 가능성이 있어야 한다.
스레드가 임계 영역에 진입하면 Lock을 획득해 다른 스레드는 접근하지 못하도록 막는다. Lock을 가진 스레드만 진입 가능하고, 나머지는 대기한다. 대기 조건도 걸 수 있다.
사용 형태:
| 형태 | 락 기준 | 동기화 범위 |
|---|---|---|
synchronized 인스턴스 메서드 | this | 메서드 전체 |
synchronized static 메서드 | 클래스 객체 | 메서드 전체 |
synchronized (this) | this | 블록 일부 |
synchronized (lock) | 지정 객체 | 블록 일부 |
문제점: