자바 함수형 마스터 — JVM·OOP·컬렉션 기초

2026-05-03확률과 통계 마스터 노트

자바 함수형 마스터 노트 시리즈 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)

xn복사본. 함수 안에서 바꿔도 원본 영향 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 상속 XentrySet().stream() 우회
  • Queue = FIFO (LinkedList·ArrayDeque)
  • 제네릭 = 컴파일 타임 타입 안전
  • 타입 소거 — 런타임엔 raw type

시리즈 다른 편

공식 문서: Oracle Java SE Documentation 에서 더 깊이.

다음 글(2편)에서는 람다 표현식 — 익명 클래스에서 람다로의 변환, 메서드 참조 4종, effectively final까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!