1. Java란?
Java는 1995년 Sun Microsystems의 James Gosling이 개발한 범용 객체지향 프로그래밍 언어입니다. "Write Once, Run Anywhere(WORA)" 철학을 바탕으로, 한 번 작성한 코드가 JVM이 설치된 모든 플랫폼에서 동작합니다.
JDK / JRE / JVM 구조
| 구성 요소 | 역할 |
|---|---|
| JVM | 바이트코드를 해석해 실행하는 가상 머신. 플랫폼 독립성의 핵심 |
| JRE | JVM + 표준 라이브러리. Java 프로그램 실행에 필요 |
| JDK | JRE + 컴파일러(javac) 등 개발 도구 일체 |
2. JDK 설치 및 개발 환경 구축
Java 프로그램을 컴파일하고 실행하기 위해서는 Java 개발 키트인 JDK(Java Development Kit)가 필요합니다. 현재 최신 장기 지원(LTS) 버전인 Java 21을 기준으로 각 플랫폼별 설정 방법과 통합 개발 환경(VS Code) 구축을 학습하겠습니다.
2.1 운영체제별 JDK 설치
🪟 Windows 설치 및 환경변수 설정
Windows 환경에서는 Adoptium에서 제공하는 Eclipse Temurin 배포판을 사용하는 것이 신뢰성이 높고 매우 간편합니다.
- JDK 다운로드: Adoptium 공식 홈페이지에 접속하여 Windows x64용
.msi설치 파일을 다운로드합니다. - 설치 옵션 지정: 인스톨러를 실행할 때, 설치 항목 중 "Set JAVA_HOME variable"과 "Associate .jar" 옵션을 "Will be installed on local hard drive"로 체크하여 자동 등록되도록 설정하고 진행합니다.
- 수동 환경변수 설정 (옵션): 만약 환경변수가 자동 등록되지 않았거나 빌드 툴을 사용하려면 아래와 같이 등록합니다.
- JAVA_HOME: 시스템 변수에
JAVA_HOME이름으로 JDK 설치 폴더(기본값:C:\Program Files\Eclipse Adoptium\jdk-21.x.x\)를 새로 추가합니다. - Path:
Path변수를 편집하여%JAVA_HOME%\bin를 새로 추가합니다.
- JAVA_HOME: 시스템 변수에
- 설치 확인: 명령 프롬프트(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)을 구성할 수 있습니다.
- Extension Pack for Java 설치: VS Code 마켓플레이스(
Ctrl + Shift + X)에서 Extension Pack for Java (Microsoft 제공)를 검색해 설치합니다. (디버거, 테스트 러너, 코드 완성 엔진 등이 일괄 셋업됩니다.) - 프로젝트 생성 및 실행:
- 새로운 폴더를 열고
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 가이드