자바 함수형 마스터 노트 시리즈 1편. JDK·JRE·JVM 역할 구분, 원시 타입 vs 참조 타입과 Pass-by-Value 본질, 돌연변이(Mutation) 문제와 방어적 복사, 래퍼 클래스 + 오토박싱·언박싱 함정, OOP 4기둥(캡슐화·상속·다형성·추상화), 추상 클래스 vs 인터페이스, 컬렉션 4대 인터페이스(List·Set·Map·Queue)까지 — 람다·스트림에 들어가기 전 단단히 다지는 기초.
이 글은 자바 함수형 마스터 노트 시리즈의 첫 번째 편입니다. 자바 8 람다·스트림으로 본격 입장하기 전에, 그 위에 올라탈 JVM·OOP·컬렉션 기초를 한 흐름으로 정리합니다. 이걸 안 다지면 람다가 왜 효과적이고 스트림이 왜 빠른지가 모래성처럼 무너집니다.
이 시리즈 6편은 자바 기초·람다·함수형 인터페이스·Stream API·Modern Java(9~17)·Java 21 가상 스레드를 다룹니다. 1편의 목표 — JDK·JVM·다형성·컬렉션 4가지 축을 손에 잡히게.
이 시리즈는 Oracle 자바 공식 문서, OpenJDK JEP, Effective Java, 여러 자바 함수형 학습 자료를 참고해 한국어 학습 노트로 풀어쓴 자료입니다.
읽으면서 IntelliJ나 JShell을 띄우고 한두 예제를 직접 쳐 보면 본문이 머리에 훨씬 잘 박혀요. 30분이면 람다 첫 줄까지 손에 들어옵니다.
처음 자바 기초가 어렵게 느껴지는 이유
처음 강의를 들을 때 자바 기초 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, JDK·JRE·JVM 같은 약어가 한 번에 쏟아져서. 셋이 어떻게 다른지 머리에 안 들어옵니다. 둘째, Pass-by-Value 같은 개념이 직관과 충돌해서. 객체를 넘겼는데 왜 원본이 바뀌었지? 같은 의문이 자꾸 생깁니다.
해결법은 한 가지예요. 각 개념을 "회사 비유"로 한 줄에 줄여 두는 것. JVM = "자바 언어가 돌아가는 가상 컴퓨터", JDK = "JVM + 개발자 도구 세트", 컬렉션 = "회사의 다양한 보관함 카탈로그". 비유가 손에 잡히면 나머지 디테일은 채우기만 하면 됩니다.
JDK · JRE · JVM — 셋의 역할 구분
자바 환경의 세 약어. 포함 관계로 이해하면 한 번에 정리됩니다.
┌─────────────────────────────────────────┐
│ JDK (Java Development Kit) │
│ ┌─────────────────────────────────────┐ │
│ │ JRE (Java Runtime Environment) │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ JVM (Java Virtual Machine) │ │ │
│ │ └────────────────────────────────┘ │ │
│ │ + 표준 라이브러리 │ │
│ └─────────────────────────────────────┘ │
│ + 컴파일러(javac), 디버거(jdb), 도구 │
└─────────────────────────────────────────┘
| 약어 | 회사 비유 | 역할 |
|---|---|---|
| JVM | 자바 언어 전용 가상 컴퓨터 | .class 바이트코드 실행 |
| JRE | JVM + 표준 라이브러리 묶음 | 자바 프로그램 실행만 가능 |
| JDK | JRE + 개발자 도구 세트 | 실행 + 개발(컴파일·디버깅) |
여기서 시험 함정이 하나 있어요. 운영 서버에 JDK 까는 게 사실상 표준입니다. 옛날엔 JRE만 있어도 됐는데, Java 11부터 JRE 단독 배포가 사라졌어요. JDK가 모든 걸 포함합니다.
Write Once, Run Anywhere
자바의 슬로건. 같은 .class 파일이 Windows·Mac·Linux 어디서나 그대로 동작.
.java (소스)
↓ javac (컴파일러)
.class (바이트코드 — 플랫폼 중립)
↓ JVM (각 플랫폼별)
실행
JVM이 OS별로 다르고, 그 위 바이트코드는 동일. 이게 자바 인기의 핵심.
원시 타입 vs 참조 타입
자바 변수는 두 종류로 갈립니다.
원시 타입 (Primitive) — 8가지
boolean flag = true;
byte b = 127; // 8 bit
short s = 32767; // 16 bit
int i = 2147483647; // 32 bit
long l = 9223372036854775807L; // 64 bit
float f = 3.14f; // 32 bit
double d = 3.14; // 64 bit
char c = 'A'; // 16 bit (Unicode)
값 자체가 변수에 저장. 메모리 사물함에 숫자가 직접 들어 있는 모습.
참조 타입 (Reference)
String name = "Alice"; // 변수에는 객체 주소만
int[] arr = {1, 2, 3}; // 변수에는 배열 주소만
Person p = new Person();
변수에는 주소만, 실제 객체는 힙(Heap)에. 변수 = 주차장 자리 번호, 객체 = 실제 차.
Pass-by-Value — 자바의 가장 큰 오해
자바 면접 단골. 자바는 항상 Pass-by-Value입니다. Pass-by-Reference 아닙니다.
원시 타입
void increment(int x) {
x = x + 1;
}
int n = 5;
increment(n);
System.out.println(n); // 5 (변경 X)
x는 n의 복사본. 함수 안에서 바꿔도 원본 영향 X.
참조 타입
void changeName(Person p) {
p.setName("Bob"); // 객체 내부 변경
}
Person alice = new Person("Alice");
changeName(alice);
System.out.println(alice.getName()); // "Bob" (변경됨!)
여기서 정말 중요한 시험 함정 — 참조 타입도 Pass-by-Value. 다만 "복사되는 값"이 객체 주소라서, 같은 객체를 가리킵니다. 그래서 객체 내부는 바뀜. 하지만:
void replace(Person p) {
p = new Person("Charlie"); // 새 객체로 재할당
}
Person alice = new Person("Alice");
replace(alice);
System.out.println(alice.getName()); // "Alice" (변경 X)
참조를 다른 객체로 재할당해도 원본 변수는 그대로. 주소를 복사받았기 때문.
Mutation (돌연변이) 문제
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
processList(list);
// list가 어떻게 됐을까? 알 수 없음.
호출자가 보낸 list를 함수 내부에서 마구 변경 가능. 디버깅 지옥.
해결 — 방어적 복사 (Defensive Copy):
void processList(List<Integer> input) {
List<Integer> copy = new ArrayList<>(input);
// copy로 작업
}
또는 불변 컬렉션 사용 (Java 9+):
List<Integer> immutable = List.of(1, 2, 3);
// 수정 시도 → UnsupportedOperationException
여기서 시험 함정이 하나 있어요. 함수형 프로그래밍은 "불변(immutable) 우선". 람다·스트림이 안전한 이유 = 원본 컬렉션을 안 건드리기. 1편의 이 개념이 4편 Stream에서 결정적입니다.
래퍼 클래스와 오토박싱
원시 타입을 객체처럼 다루기 위한 클래스.
| 원시 | 래퍼 |
|---|---|
int |
Integer |
long |
Long |
double |
Double |
boolean |
Boolean |
char |
Character |
오토박싱·언박싱
Integer boxed = 100; // 오토박싱 (int → Integer)
int unboxed = boxed; // 언박싱 (Integer → int)
List<Integer> list = new ArrayList<>();
list.add(5); // 오토박싱
int n = list.get(0); // 언박싱
여기서 시험 함정이 하나 있어요. 컬렉션은 원시 타입 못 받음. List<int> X, List<Integer> O. 람다·스트림에서 IntStream이 따로 있는 이유 — 박싱 비용 때문.
Integer 캐시 함정
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false (!!)
자바는 -128~127 Integer를 캐시. 그 범위 밖은 새 객체 생성. == 대신 .equals() 사용이 답.
객체 지향 4기둥
1. 캡슐화 (Encapsulation)
public class BankAccount {
private double balance; // private
public double getBalance() { return balance; }
public void deposit(double amt) {
if (amt > 0) balance += amt;
}
}
데이터를 private로 숨기고 public 메서드로만 접근. 회사 금고를 직원이 직접 못 열고, 정해진 절차로만.
2. 상속 (Inheritance)
public class Animal {
public void eat() { ... }
}
public class Dog extends Animal {
public void bark() { ... }
}
부모의 기능을 자식이 그대로 사용 + 추가. 상위 부서 권한을 하위 부서가 그대로 + 자기 고유 권한.
3. 다형성 (Polymorphism) — 가장 중요
같은 인터페이스로 다른 구현 사용.
Animal a = new Dog(); // Dog를 Animal 타입으로 받음
a.eat(); // Animal의 eat()? Dog의 eat()? → Dog의 것
오버라이딩 = 부모 메서드 자식이 재정의. 호출 시점에 실제 타입의 메서드 실행 (런타임 다형성).
List<Animal> animals = List.of(new Dog(), new Cat(), new Bird());
for (Animal a : animals) {
a.makeSound(); // 각자의 makeSound() 호출
}
여기서 정말 중요한 시험 함정 — 다형성이 람다의 기반. 람다는 함수형 인터페이스의 다형적 구현이에요. 이거 없으면 람다도 없음.
4. 추상화 (Abstraction)
복잡한 내부를 숨기고 본질만 노출.
public abstract class Shape {
public abstract double area(); // 구현 강제
public void draw() { ... } // 공통 구현
}
public class Circle extends Shape {
public double area() { return Math.PI * r * r; }
}
추상 클래스 vs 인터페이스
| 구분 | 추상 클래스 | 인터페이스 |
|---|---|---|
| 다중 상속 | X (단일) | O |
| 필드 | 가능 | public static final만 |
| 메서드 | 추상 + 구현 | 추상 + default + static |
| 생성자 | O | X |
| 의도 | "is-a" 관계 | "has-a" 능력 |
여기서 시험 함정이 하나 있어요. 자바 8부터 인터페이스에 default·static 메서드 가능. 이전엔 추상 메서드만. 이 변화가 함수형 인터페이스의 전제. 3편에서 깊게 다룹니다.
public interface Walkable {
void walk(); // 추상
default void run() { // Java 8+
System.out.println("running");
}
static Walkable noop() { // Java 8+
return () -> {};
}
}
컬렉션 프레임워크 — 4대 인터페이스
Collection
├── List (순서 O, 중복 O)
├── Set (순서 X, 중복 X)
└── Queue (FIFO·LIFO)
Map (Collection 별개) — 키-값 쌍
List — 순서 있는 목록
| 구현체 | 특징 | 비유 |
|---|---|---|
| ArrayList | 배열 기반, 조회 O(1), 중간 삽입 느림 | 책장(인덱스 빠름) |
| LinkedList | 연결리스트, 조회 O(n), 삽입 O(1) | 줄로 묶은 책들(끼워넣기 쉬움) |
List<String> list = new ArrayList<>();
list.add("Alice");
list.add("Bob");
list.get(0); // "Alice"
Set — 중복 제거
| 구현체 | 특징 |
|---|---|
| HashSet | 해시 기반, 순서 X |
| LinkedHashSet | 삽입 순서 유지 |
| TreeSet | 정렬 (Red-Black Tree) |
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(1); // 중복 무시
set.size(); // 1
Map — 키-값
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 30);
ages.put("Bob", 25);
ages.get("Alice"); // 30
Queue — 대기열
Queue<String> q = new LinkedList<>();
q.offer("first");
q.offer("second");
q.poll(); // "first" (FIFO)
여기서 정말 중요한 시험 함정 — Map은 Collection 인터페이스 상속 X. 키-값 구조라 별개. Collection.stream()에는 Map이 직접 못 들어가서 entrySet().stream() 식으로 우회.
제네릭 — 타입 안전한 컬렉션
List<String> names = new ArrayList<>();
names.add("Alice");
// names.add(42); // 컴파일 에러
String s = names.get(0); // 캐스팅 불필요
자바 5 도입. 제네릭 없던 시절엔 Object로 받아 매번 캐스팅. 지금은 컴파일 타임에 타입 검증.
여기서 시험 함정이 하나 있어요. 자바 제네릭은 타입 소거(Type Erasure). 런타임엔 List<String>도 List<Integer>도 그냥 List. instanceof List<String> 같은 건 X.
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 1편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- JVM = 자바 가상 컴퓨터 / JRE = JVM + 표준 라이브러리 / JDK = JRE + 개발 도구
- Java 11부터 JRE 단독 배포 X — JDK가 표준
- Write Once, Run Anywhere = 같은
.class모든 OS - 원시 8가지 — boolean·byte·short·int·long·float·double·char
- 참조 = 주소만 변수에, 실제 객체는 힙
- 자바는 항상 Pass-by-Value
- 참조 타입은 주소가 복사 → 같은 객체 가리킴 → 내부 변경 가능
- 참조 재할당은 원본 영향 X
- Mutation 방지 = 방어적 복사 또는 불변 컬렉션 (
List.of) - 함수형 프로그래밍 = 불변 우선
- 래퍼 클래스 — Integer·Long·Double·Boolean·Character
- 컬렉션은 원시 타입 X (
List<int>안 됨) - IntStream이 별도 존재 = 박싱 비용 회피
- Integer 캐시 -128~127 —
==대신.equals() - OOP 4기둥 — 캡슐화·상속·다형성·추상화
- 다형성이 람다의 기반
- 추상 클래스 vs 인터페이스 — is-a vs has-a
- 자바 8 인터페이스 default·static 메서드 가능 (함수형 인터페이스 전제)
- 컬렉션 4대 — List / Set / Map / Queue
- ArrayList(조회 O(1)) vs LinkedList(삽입 O(1))
- HashSet·LinkedHashSet·TreeSet
- Map은 Collection 상속 X —
entrySet().stream()우회 - Queue = FIFO (LinkedList·ArrayDeque)
- 제네릭 = 컴파일 타임 타입 안전
- 타입 소거 — 런타임엔 raw type
시리즈 다른 편
- 1편 — JVM·OOP·컬렉션 기초 (현재 글)
- 2편 — 람다 표현식
- 3편 — 함수형 인터페이스
- 4편 — Stream API
- 5편 — Modern Java (9~17)
- 6편 — Java 21 가상 스레드
공식 문서: Oracle Java SE Documentation 에서 더 깊이.
다음 글(2편)에서는 람다 표현식 — 익명 클래스에서 람다로의 변환, 메서드 참조 4종, effectively final까지 풀어 갑니다.