객체지향 프로그래밍

Java 완전 가이드

👥 방문자 수

JVM 기반의 강력한 객체지향 언어 Java를 체계적으로 학습합니다. 설치부터 OOP, 제네릭, 최신 람다와 스트림 API까지 실전 예제 중심으로 정리했습니다.

Java 21 LTS JDK / JVM OOP · 제네릭 람다 · 스트림

1. Java란?

Java는 1995년 Sun Microsystems의 James Gosling이 개발한 범용 객체지향 프로그래밍 언어입니다. "Write Once, Run Anywhere(WORA)" 철학을 바탕으로, 한 번 작성한 코드가 JVM이 설치된 모든 플랫폼에서 동작합니다.

JDK / JRE / JVM 구조

구성 요소역할
JVM바이트코드를 해석해 실행하는 가상 머신. 플랫폼 독립성의 핵심
JREJVM + 표준 라이브러리. Java 프로그램 실행에 필요
JDKJRE + 컴파일러(javac) 등 개발 도구 일체

2. JDK 설치 및 개발 환경 구축

Java 프로그램을 컴파일하고 실행하기 위해서는 Java 개발 키트인 JDK(Java Development Kit)가 필요합니다. 현재 최신 장기 지원(LTS) 버전인 Java 21을 기준으로 각 플랫폼별 설정 방법과 통합 개발 환경(VS Code) 구축을 학습하겠습니다.

2.1 운영체제별 JDK 설치

🪟 Windows 설치 및 환경변수 설정

Windows 환경에서는 Adoptium에서 제공하는 Eclipse Temurin 배포판을 사용하는 것이 신뢰성이 높고 매우 간편합니다.

  1. JDK 다운로드: Adoptium 공식 홈페이지에 접속하여 Windows x64용 .msi 설치 파일을 다운로드합니다.
  2. 설치 옵션 지정: 인스톨러를 실행할 때, 설치 항목 중 "Set JAVA_HOME variable""Associate .jar" 옵션을 "Will be installed on local hard drive"로 체크하여 자동 등록되도록 설정하고 진행합니다.
  3. 수동 환경변수 설정 (옵션): 만약 환경변수가 자동 등록되지 않았거나 빌드 툴을 사용하려면 아래와 같이 등록합니다.
    • JAVA_HOME: 시스템 변수에 JAVA_HOME 이름으로 JDK 설치 폴더(기본값: C:\Program Files\Eclipse Adoptium\jdk-21.x.x\)를 새로 추가합니다.
    • Path: Path 변수를 편집하여 %JAVA_HOME%\bin를 새로 추가합니다.
  4. 설치 확인: 명령 프롬프트(cmd)에서 아래 명령어를 실행하여 올바른 버전이 표시되는지 확인합니다.
    java -version
    javac -versionbash

🍎 macOS 설치 및 환경변수 설정

Mac 환경에서는 Homebrew를 이용한 간단 설치 혹은 패키지 다운로드 형태로 진행 가능합니다.

방법 A: Homebrew 설치 (가장 권장)
# Adoptium Temurin 21 JDK 설치
brew install --cask temurin@21bash
방법 B: 수동 설치 및 쉘 설정

Adoptium 사이트에서 macOS용 .pkg(Apple Silicon 혹은 Intel 아키텍처에 맞게 선택)를 다운로드해 마법사대로 설치합니다. 그 다음 터미널에서 JDK 경로를 인식시키기 위해 쉘 설정 파일(~/.zshrc 또는 ~/.bash_profile) 끝에 아래 구문을 추가합니다.

export JAVA_HOME=$(/usr/libexec/java_home -v 21)
export PATH=$PATH:$JAVA_HOME/binbash

(추가 후 source ~/.zshrc를 실행해 세션에 적용합니다.)

🐧 Linux 설치

Debian/Ubuntu 계열의 리눅스는 패키지 관리 도구(apt)를 통해 아주 간편하게 오픈 JDK 21 버전을 배포받을 수 있습니다.

sudo apt update
sudo apt install openjdk-21-jdk

# 설치 버전 검증
java -versionbash

2.2 VS Code에서 Java 개발 환경 구축

경량 에디터인 VS Code에 Microsoft Java 익스텐션을 설치하여 완벽한 Java 개발 통합 환경(IDE)을 구성할 수 있습니다.

  1. Extension Pack for Java 설치: VS Code 마켓플레이스(Ctrl + Shift + X)에서 Extension Pack for Java (Microsoft 제공)를 검색해 설치합니다. (디버거, 테스트 러너, 코드 완성 엔진 등이 일괄 셋업됩니다.)
  2. 프로젝트 생성 및 실행:
    • 새로운 폴더를 열고 HelloWorld.java 파일을 생성합니다.
    • 아래와 같이 소스코드를 입력합니다. (클래스 이름과 파일 이름은 반드시 일치해야 합니다.)
      public class HelloWorld {
          public static void main(String[] args) {
              System.out.println("VS Code에서 자바가 완벽히 동작합니다!");
          }
      }java
    • 코드 위에 표시되는 [Run] 버튼을 클릭하거나 F5 키를 누르면 통합 터미널 창에 컴파일과 실행이 동시에 수행되어 화면에 출력이 나타납니다.

3. Hello World

Java 프로그램은 반드시 클래스를 선언해야 하며, 파일명과 public class명이 완벽히 동일해야 합니다. 진입점은 항상 public static void main(String[] args) 메서드입니다.

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}java

4. 변수와 자료형 (Variables & Data Types)

Java는 엄격한 타입 시스템을 갖춘 강타입 언어입니다. 메모리 효율을 위한 8가지 기본 자료형(Primitive Types)과 객체 주소를 나타내는 참조 자료형(Reference Types)으로 명확히 나뉩니다.

public class DataTypeExample {
    public static void main(String[] args) {
        // 1. 기본 자료형 (Primitive Types)
        int integerVal = 42;
        long longVal = 9000000000L; // long형 상수는 끝에 L 기재
        double doubleVal = 3.14159;
        boolean booleanVal = true;
        char charVal = 'A';

        // 2. 참조 자료형 (Reference Types)
        String stringVal = "Java Programming";

        System.out.println(integerVal + ", " + longVal + ", " + doubleVal + ", " + booleanVal + ", " + charVal + ", " + stringVal);
    }
}java

5. 연산자와 제어문 (Operators & Control Flow)

조건문 if-else와 반복문 for, while을 지원하며, Java 14부터 공식 표준 탑재된 Switch Expressions(화살표 형태 switch문)를 사용하면 값 반환까지 가독성 높게 코드를 정리할 수 있습니다.

public class ControlFlowExample {
    public static void main(String[] args) {
        // 1. if-else 조건문
        int score = 85;
        if (score >= 90) {
            System.out.println("A 학점");
        } else {
            System.out.println("B 학점 이하");
        }

        // 2. 향상된 for문 (for-each)
        String[] fruits = {"Apple", "Banana", "Orange"};
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // 3. Modern Switch Expression (Java 14+)
        String grade = "A";
        String result = switch (grade) {
            case "A", "B" -> "우수";
            case "C" -> "보통";
            default -> "노력 요함";
        };
        System.out.println("결과: " + result);
    }
}java

6. 배열과 컬렉션 (Arrays & Collections)

고정 크기의 기본 배열 외에도 실무에서는 가변 크기의 자료구조를 지원하는 **Java Collection Framework(List, Set, Map)**를 99% 이상 채택해 작업합니다.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CollectionExample {
    public static void main(String[] args) {
        // 1. 고정 배열
        int[] numbers = {1, 2, 3, 4, 5};

        // 2. 동적 리스트 (List)
        List<String> list = new ArrayList<>();
        list.add("홍길동");
        list.add("이순신");

        // 3. 해시 맵 (Map - 키와 값 쌍)
        Map<String, Integer> map = new HashMap<>();
        map.put("Apple", 1500);
        map.put("Banana", 3000);

        System.out.println("리스트 크기: " + list.size() + ", 사과 가격: " + map.get("Apple"));
    }
}java

7. 메서드 (Methods)

메서드는 특정 작업을 수행하기 위한 독립적인 함수 블록입니다. 매개변수 선언 시 강타입 매핑을 정의하며, 이름은 같지만 매개변수 개수나 타입을 다르게 정의하는 **메서드 오버로딩(Overloading)**을 완벽히 지원합니다.

public class Calculator {
    // 두 정수를 더하는 메서드
    public int add(int a, int b) {
        return a + b;
    }

    // 메서드 오버로딩 (실수 덧셈 지원)
    public double add(double a, double b) {
        return a + b;
    }

    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println("정수 덧셈: " + calc.add(5, 3));
        System.out.println("실수 덧셈: " + calc.add(2.5, 3.5));
    }
}java

8. 클래스와 객체 (Classes & Objects)

Java는 순수 객체지향 언어입니다. 캡슐화, 상속, 다형성, 추상화 4가지 OOP 원칙을 기반으로 설계합니다.

public class BankAccount {
    private final String owner;  // final: 불변 필드
    private double balance;

    public BankAccount(String owner, double initialBalance) {
        this.owner   = owner;
        this.balance = initialBalance;
    }

    // Getter (setter는 의도적으로 제공하지 않음 — 캡슐화)
    public String getOwner()   { return owner; }
    public double getBalance() { return balance; }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("입금액은 양수여야 합니다.");
        balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount > balance) return false;
        balance -= amount;
        return true;
    }

    @Override
    public String toString() {
        return String.format("BankAccount[owner=%s, balance=%.2f]", owner, balance);
    }
}java

Record (Java 16+) — 불변 데이터 클래스

// 전통적인 DTO 클래스 → Record로 대체
record Point(double x, double y) {
    // 컴팩트 생성자 (유효성 검사)
    Point {
        if (Double.isNaN(x) || Double.isNaN(y))
            throw new IllegalArgumentException("좌표는 NaN일 수 없습니다.");
    }

    // 인스턴스 메서드 추가 가능
    double distanceTo(Point other) {
        return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2));
    }
}

// Record는 equals, hashCode, toString 자동 생성
Point p1 = new Point(0, 0);
Point p2 = new Point(3, 4);
System.out.println(p1.distanceTo(p2)); // 5.0
System.out.println(p1);               // Point[x=0.0, y=0.0]java

Sealed Class (Java 17+) — 제한된 상속

// 도형 계층을 sealed로 완전히 통제
sealed interface Shape permits Circle, Rectangle, Triangle {}

record Circle   (double radius)          implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle (double base, double height) implements Shape {}

// Pattern Matching switch (Java 21)
double area(Shape s) {
    return switch (s) {
        case Circle    c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        case Triangle  t -> 0.5 * t.base() * t.height();
    }; // Shape의 모든 케이스가 처리됨 → default 불필요
}java

9. 상속과 다형성 (Inheritance & Polymorphism)

abstract class Animal {
    protected final String name;

    Animal(String name) { this.name = name; }

    abstract String sound();  // 자식이 반드시 구현

    String describe() {       // 공통 구현 (재사용)
        return name + "은(는) '" + sound() + "' 소리를 냅니다.";
    }
}

class Dog extends Animal {
    Dog(String name) { super(name); }

    @Override String sound() { return "멍멍"; }

    void fetch() { System.out.println(name + "이 공을 물어옵니다!"); }
}

class Cat extends Animal {
    Cat(String name) { super(name); }

    @Override String sound() { return "야옹"; }
}

// 다형성: 부모 타입으로 다양한 자식 처리
List<Animal> animals = List.of(new Dog("바둑이"), new Cat("나비"), new Dog("흰둥이"));
animals.forEach(a -> System.out.println(a.describe()));

// instanceof Pattern Matching (Java 16+)
for (Animal a : animals) {
    if (a instanceof Dog dog) {  // 타입 체크 + 캐스팅 동시에
        dog.fetch();
    }
}java

10. 인터페이스와 추상 클래스 (Interfaces & Abstract Classes)

// 함수형 인터페이스 (@FunctionalInterface) — 람다식과 연계
@FunctionalInterface
interface Transformer<T, R> {
    R transform(T input);
}

// default 메서드 — 인터페이스에 구현 제공 (Java 8+)
interface Printable {
    String format();
    default void print() { System.out.println(format()); }
    default void printUpper() { System.out.println(format().toUpperCase()); }
}

// 다중 인터페이스 구현
interface Serializable { byte[] serialize(); }

class User implements Printable, Serializable {
    private final String name;
    private final int age;

    User(String name, int age) { this.name = name; this.age = age; }

    @Override public String format() { return name + " (" + age + "세)"; }
    @Override public byte[] serialize() { return format().getBytes(); }
}

// 람다로 함수형 인터페이스 구현
Transformer<String, Integer> lengthOf = String::length;  // 메서드 참조
System.out.println(lengthOf.transform("TestForge"));       // 9java

11. 예외 처리 (Exception Handling)

// ── 커스텀 예외 ─────────────────────────────────
class InsufficientFundsException extends RuntimeException {
    private final double shortage;

    InsufficientFundsException(double shortage) {
        super("잔액 부족: " + shortage + "원 부족합니다.");
        this.shortage = shortage;
    }

    public double getShortage() { return shortage; }
}

// ── try-with-resources (AutoCloseable 자동 close) ──
try (var reader = new BufferedReader(new FileReader("data.txt"));
     var writer = new BufferedWriter(new FileWriter("out.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line.toUpperCase());
        writer.newLine();
    }
} // reader.close(), writer.close() 자동 호출 (예외 발생 시에도)

// ── Multi-catch & 예외 체이닝 ──────────────────────
try {
    process();
} catch (NullPointerException | IllegalArgumentException e) {
    // 두 예외 타입을 하나의 catch로 처리
    log.error("입력 오류", e);
} catch (IOException e) {
    // 원인 예외를 래핑하여 상위로 전파
    throw new ServiceException("파일 처리 실패", e);
} finally {
    cleanup(); // 예외 여부 무관 실행
}

// ── Optional — null 안전 처리 ──────────────────────
Optional<User> findUser(int id) {
    return Optional.ofNullable(userRepository.findById(id));
}

String email = findUser(1)
    .map(User::getEmail)
    .filter(e -> e.contains("@"))
    .orElse("unknown@example.com");java

12. 제네릭 (Generics)

// ── 제네릭 클래스 ────────────────────────────────
class Result<T> {
    private final T value;
    private final String error;

    private Result(T value, String error) {
        this.value = value; this.error = error;
    }

    public static <T> Result<T> ok(T value)      { return new Result<>(value, null);  }
    public static <T> Result<T> fail(String err)  { return new Result<>(null, err); }

    public boolean isOk()     { return error == null; }
    public T getValue()       { return value; }
    public String getError()  { return error; }
}

Result<Integer> result = Result.ok(42);
if (result.isOk()) System.out.println(result.getValue()); // 42

// ── 와일드카드 (?) ────────────────────────────────
// ? extends T: T 또는 T의 하위 타입 (읽기 전용)
double sumList(List<? extends Number> list) {
    return list.stream().mapToDouble(Number::doubleValue).sum();
}

// ? super T: T 또는 T의 상위 타입 (쓰기 가능)
void addNumbers(List<? super Integer> list) {
    list.add(1); list.add(2); list.add(3);
}

// ── 제네릭 메서드 ─────────────────────────────────
<T extends Comparable<T>> T max(List<T> list) {
    return list.stream().max(Comparator.naturalOrder())
               .orElseThrow(NoSuchElementException::new);
}

System.out.println(max(List.of(3, 1, 4, 1, 5, 9))); // 9
System.out.println(max(List.of("banana", "apple", "cherry"))); // cherryjava

13. 람다와 스트림 API (Lambda & Stream API)

import java.util.*;
import java.util.stream.*;

record Employee(String name, String dept, double salary) {}

List<Employee> employees = List.of(
    new Employee("김철수", "개발", 5000000),
    new Employee("이영희", "개발", 6000000),
    new Employee("박지민", "기획", 4500000),
    new Employee("최민준", "개발", 5500000),
    new Employee("정수연", "기획", 4800000)
);

// ── 기본 스트림 파이프라인 ──────────────────────────
List<String> devNames = employees.stream()
    .filter(e -> e.dept().equals("개발"))     // 개발팀만
    .sorted(Comparator.comparing(Employee::salary).reversed()) // 연봉 내림차순
    .map(Employee::name)                       // 이름만 추출
    .collect(Collectors.toList());
// [이영희, 최민준, 김철수]

// ── Collectors 심화 ───────────────────────────────
// groupingBy: 부서별 그룹화
Map<String, List<Employee>> byDept =
    employees.stream().collect(Collectors.groupingBy(Employee::dept));

// partitioningBy: 조건으로 둘로 분리
Map<Boolean, List<Employee>> highEarners =
    employees.stream().collect(Collectors.partitioningBy(e -> e.salary() > 5000000));

// summarizingDouble: 통계
DoubleSummaryStatistics stats = employees.stream()
    .collect(Collectors.summarizingDouble(Employee::salary));
System.out.printf("평균: %.0f, 최대: %.0f%n", stats.getAverage(), stats.getMax());
// 평균: 5160000, 최대: 6000000

// 부서별 평균 연봉
Map<String, Double> avgSalaryByDept = employees.stream().collect(
    Collectors.groupingBy(Employee::dept, Collectors.averagingDouble(Employee::salary)));

// ── 메서드 참조 4가지 형태 ─────────────────────────
employees.stream().map(Employee::name)          // 인스턴스 메서드 참조
         .map(String::toUpperCase)              // 임의 인스턴스 메서드
         .forEach(System.out::println);         // 인스턴스 메서드 참조
List<Employee> copy = employees.stream()
         .collect(Collectors.toCollection(ArrayList::new)); // 생성자 참조java

14. 다음 단계 (Next Steps)

Java의 기본 OOP, 제네릭, 스트림을 숙지했다면, 본격적으로 국내 최고의 시장 지배력을 보유한 엔터프라이즈 통합 백엔드 프레임워크인 **Spring Boot** 학습으로 도약하세요!

🚀
추천 자바 백엔드 로드맵:
Java 기초 → Spring Core(IoC/DI) → Spring Boot 웹 MVC 개발 → JPA/Hibernate ORM → 데이터베이스 통합 설계

연계 가이드: 🍃 Spring Boot 완전 가이드 · Docker 가이드 · Kubernetes 가이드