Java는 1995년 James Gosling이 설계한 객체지향 언어다. "Write Once, Run Anywhere"라는 철학 아래 JVM(Java Virtual Machine) 위에서 동작하며, 플랫폼 독립성을 보장한다. 백엔드 개발, 안드로이드 앱, 대규모 엔터프라이즈 시스템까지 폭넓게 사용된다.
이 글은 Java를 다시 처음부터 훑으면서 기본기를 단단하게 다지는 데 목적이 있다.
1. 데이터 타입
1-1. 기본 타입 (Primitive Type)
Java는 8가지 기본 타입을 제공한다. 기본 타입은 스택 메모리에 값 자체가 저장된다.
타입 | 크기 | 범위 | 기본값 |
byte | 1 byte | -128 ~ 127 | 0 |
short | 2 byte | -32,768 ~ 32,767 | 0 |
int | 4 byte | -2,147,483,648 ~ 2,147,483,647 | 0 |
long | 8 byte | -9.2 × 10¹⁸ ~ 9.2 × 10¹⁸ | 0L |
float | 4 byte | 소수점 약 7자리 | 0.0f |
double | 8 byte | 소수점 약 15자리 | 0.0d |
char | 2 byte | 유니코드 문자 (0 ~ 65,535) | 'u0000' |
boolean | 1 bit | true / false | false |
int age = 25;
long population = 8_000_000_000L; // 언더스코어로 가독성 향상
double pi = 3.14159;
boolean isActive = true;
char grade = 'A';
Java
복사
1-2. 참조 타입 (Reference Type)
기본 타입을 제외한 모든 타입은 참조 타입이다. 힙 메모리에 객체가 생성되고, 스택에는 그 주소(참조값)가 저장된다.
String name = "홍길동"; // 문자열
int[] scores = {90, 85, 70}; // 배열
List<String> list = new ArrayList<>(); // 컬렉션
Java
복사
1-3. 형 변환 (Type Casting)
// 자동 형 변환 (묵시적): 작은 타입 → 큰 타입, 데이터 손실 없음
int i = 100;
long l = i; // int → long 자동 변환
double d = l; // long → double 자동 변환
// 강제 형 변환 (명시적): 큰 타입 → 작은 타입, 데이터 손실 가능
double pi = 3.14;
int intPi = (int) pi; // 3 (소수점 버림)
// 문자열 변환
String numStr = "42";
int parsed = Integer.parseInt(numStr); // String → int
String back = String.valueOf(parsed); // int → String
Java
복사
2. 변수와 상수
// 변수: 값을 변경할 수 있음
int count = 0;
count = 10; // 재할당 가능
// 상수: final 키워드로 선언, 한 번 할당 후 변경 불가
final int MAX_SIZE = 100;
// MAX_SIZE = 200; // 컴파일 에러
// static final: 클래스 레벨 상수 (관례상 대문자 스네이크 케이스)
public static final double TAX_RATE = 0.1;
Java
복사
3. 연산자
3-1. 산술 연산자
int a = 10, b = 3;
System.out.println(a + b); // 13
System.out.println(a - b); // 7
System.out.println(a * b); // 30
System.out.println(a / b); // 3 (정수 나눗셈: 소수점 버림)
System.out.println(a % b); // 1 (나머지)
// 증감 연산자
int x = 5;
System.out.println(x++); // 5 출력 후 x = 6
System.out.println(++x); // x = 7 된 후 7 출력
Java
복사
3-2. 비교 / 논리 연산자
int n = 10;
boolean result1 = n > 5 && n < 20; // true (AND)
boolean result2 = n < 0 || n > 5; // true (OR)
boolean result3 = !(n == 10); // false (NOT)
// 삼항 연산자
String label = (n % 2 == 0) ? "짝수" : "홀수";
Java
복사
3-3. 비트 연산자
int flags = 0b1010; // 10
int mask = 0b1100; // 12
System.out.println(flags & mask); // 8 (AND: 공통 비트)
System.out.println(flags | mask); // 14 (OR: 합집합 비트)
System.out.println(flags ^ mask); // 6 (XOR: 다른 비트)
System.out.println(~flags); // -11 (NOT: 비트 반전)
System.out.println(flags << 1); // 20 (왼쪽 시프트: ×2)
System.out.println(flags >> 1); // 5 (오른쪽 시프트: ÷2)
Java
복사
4. 제어문
4-1. if-else
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B"); // 이 블록 실행
} else if (score >= 70) {
System.out.println("C");
} else {
System.out.println("F");
}
Java
복사
4-2. switch-case
// 전통적 switch
String day = "MON";
switch (day) {
case "MON":
case "TUE":
case "WED":
case "THU":
case "FRI":
System.out.println("평일");
break;
case "SAT":
case "SUN":
System.out.println("주말");
break;
default:
System.out.println("알 수 없음");
}
// Java 14+ switch 표현식 (Arrow 문법)
String result = switch (day) {
case "SAT", "SUN" -> "주말";
default -> "평일";
};
Java
복사
4-3. 반복문
// for 루프
for (int i = 0; i < 5; i++) {
System.out.print(i + " "); // 0 1 2 3 4
}
// while 루프
int n = 1;
while (n <= 5) {
System.out.print(n + " "); // 1 2 3 4 5
n++;
}
// do-while: 조건과 무관하게 최소 1회 실행
int count = 0;
do {
System.out.println("실행: " + count);
count++;
} while (count < 3);
// 향상된 for (enhanced for): 배열·컬렉션 순회
int[] arr = {10, 20, 30, 40};
for (int val : arr) {
System.out.print(val + " "); // 10 20 30 40
}
// break / continue
for (int i = 0; i < 10; i++) {
if (i == 3) continue; // 3 건너뜀
if (i == 7) break; // 7에서 루프 종료
System.out.print(i + " "); // 0 1 2 4 5 6
}
Java
복사
5. 배열
// 1차원 배열 선언 및 초기화
int[] nums = new int[5]; // 기본값(0)으로 초기화
int[] scores = {95, 88, 76, 92}; // 리터럴 초기화
System.out.println(scores.length); // 4
System.out.println(scores[0]); // 95
// 2차원 배열
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
System.out.println(matrix[1][2]); // 6
// 배열 유틸리티
import java.util.Arrays;
int[] arr = {5, 3, 1, 4, 2};
Arrays.sort(arr); // 정렬: [1, 2, 3, 4, 5]
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5]
int[] copy = Arrays.copyOf(arr, arr.length); // 배열 복사
Java
복사
6. 메서드
public class Calculator {
// 기본 메서드: 반환값 있음
public static int add(int a, int b) {
return a + b;
}
// void 메서드: 반환값 없음
public static void printResult(String label, int value) {
System.out.println(label + ": " + value);
}
// 오버로딩: 같은 이름, 다른 매개변수
public static double add(double a, double b) {
return a + b;
}
// 가변인자 (varargs): 매개변수 개수 유동적
public static int sum(int... numbers) {
int total = 0;
for (int n : numbers) total += n;
return total;
}
public static void main(String[] args) {
System.out.println(add(3, 4)); // 7
System.out.println(add(1.5, 2.5)); // 4.0
System.out.println(sum(1, 2, 3, 4, 5)); // 15
}
}
Java
복사
7. 객체지향 프로그래밍 (OOP)
7-1. 클래스와 객체
public class Person {
// 필드 (멤버 변수)
private String name;
private int age;
// 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 기본 생성자 (명시적 정의)
public Person() {
this("이름 없음", 0); // 생성자 체이닝
}
// Getter / Setter
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age >= 0) this.age = age; // 유효성 검사 가능
}
// toString 오버라이드
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
// 사용
Person p1 = new Person("홍길동", 30);
Person p2 = new Person(); // 기본 생성자
System.out.println(p1); // Person{name='홍길동', age=30}
Java
복사
7-2. 상속 (Inheritance)
// 부모 클래스
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void speak() {
System.out.println(name + "이(가) 소리를 냅니다.");
}
}
// 자식 클래스: extends로 상속
public class Dog extends Animal {
private String breed;
public Dog(String name, String breed) {
super(name); // 부모 생성자 호출
this.breed = breed;
}
// 메서드 오버라이딩
@Override
public void speak() {
System.out.println(name + "이(가) 멍멍 짖습니다.");
}
public void fetch() {
System.out.println(name + "이(가) 공을 가져옵니다.");
}
}
// 다형성
Animal a = new Dog("바둑이", "진돗개"); // 업캐스팅
a.speak(); // Dog의 speak() 호출 (동적 바인딩)
// a.fetch(); // 컴파일 에러: Animal 타입에는 fetch()가 없음
if (a instanceof Dog dog) { // Java 16+ 패턴 매칭
dog.fetch(); // 다운캐스팅 없이 바로 사용
}
Java
복사
7-3. 추상 클래스 vs 인터페이스
// 추상 클래스: 공통 구현을 공유하면서 일부를 강제
public abstract class Shape {
protected String color;
public Shape(String color) {
this.color = color;
}
// 추상 메서드: 반드시 자식이 구현해야 함
public abstract double area();
// 일반 메서드: 공통 구현 제공
public void describe() {
System.out.println(color + " 도형, 넓이: " + area());
}
}
// 인터페이스: 규약(계약) 정의, 다중 구현 가능
public interface Drawable {
void draw(); // public abstract 생략 가능
// default 메서드 (Java 8+): 기본 구현 제공
default void drawWithBorder() {
System.out.println("테두리 그리기");
draw();
}
// static 메서드 (Java 8+)
static Drawable createDefault() {
return () -> System.out.println("기본 도형");
}
}
// 구현
public class Circle extends Shape implements Drawable {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
@Override
public void draw() {
System.out.println("원 그리기 (반지름: " + radius + ")");
}
}
Java
복사
구분 | 추상 클래스 | 인터페이스 |
상속 | 단일 상속 (extends) | 다중 구현 (implements) |
필드 | 인스턴스 변수 가능 | public static final 상수만 |
생성자 | 있음 | 없음 |
접근 제어자 | 제한 없음 | public (메서드) |
주 용도 | 공통 구현 공유 | 타입 규약 정의 |
8. 접근 제어자
제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스 | 외부 |
public | ||||
protected | ||||
(default) | ||||
private |
public class BankAccount {
private double balance; // 외부 직접 접근 불가
public void deposit(double amount) {
if (amount > 0) balance += amount;
}
public double getBalance() {
return balance;
}
}
Java
복사
9. 제네릭 (Generics)
타입을 매개변수화해서 컴파일 시점에 타입 안전성을 보장한다.
// 제네릭 클래스
public class Box<T> {
private T value;
public Box(T value) { this.value = value; }
public T getValue() { return value; }
}
Box<String> strBox = new Box<>("안녕");
Box<Integer> intBox = new Box<>(42);
// String strBox.getValue()를 Integer 변수에 넣으면 컴파일 에러
// → 런타임 ClassCastException 방지
// 제네릭 메서드
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) >= 0 ? a : b;
}
System.out.println(max(3, 7)); // 7
System.out.println(max("apple", "banana")); // banana
// 와일드카드
public static double sumList(List<? extends Number> list) {
return list.stream().mapToDouble(Number::doubleValue).sum();
}
List<Integer> ints = List.of(1, 2, 3);
List<Double> doubles = List.of(1.1, 2.2, 3.3);
System.out.println(sumList(ints)); // 6.0
System.out.println(sumList(doubles)); // 6.6
Java
복사
10. 예외 처리
10-1. 예외 계층 구조
10-2. try-catch-finally
public static int divide(int a, int b) {
try {
return a / b;
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없음: " + e.getMessage());
return -1;
} finally {
System.out.println("finally는 항상 실행"); // 리소스 정리 등
}
}
// 멀티 catch
try {
String s = null;
s.length(); // NullPointerException
} catch (NullPointerException | IllegalArgumentException e) {
System.out.println("처리: " + e.getClass().getSimpleName());
}
// try-with-resources (Java 7+): AutoCloseable 자동 close
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} // finally 없이도 br.close() 자동 호출
Java
복사
10-3. 커스텀 예외
// 체크 예외: Exception 상속
public class InsufficientBalanceException extends Exception {
private final double amount;
public InsufficientBalanceException(double amount) {
super("잔액 부족. 부족액: " + amount);
this.amount = amount;
}
public double getAmount() { return amount; }
}
// 언체크 예외: RuntimeException 상속 (Spring에서 주로 사용)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(Long id) {
super("사용자를 찾을 수 없음. ID: " + id);
}
}
// 사용
public void withdraw(double amount) throws InsufficientBalanceException {
if (balance < amount) {
throw new InsufficientBalanceException(amount - balance);
}
balance -= amount;
}
Java
복사
11. 컬렉션 프레임워크
11-1. List
import java.util.*;
// ArrayList: 동적 배열, 인덱스 접근 O(1), 중간 삽입/삭제 O(n)
List<String> arrayList = new ArrayList<>();
arrayList.add("Java");
arrayList.add("Python");
arrayList.add(0, "C++"); // 인덱스 0에 삽입
arrayList.remove("Python"); // 값으로 삭제
System.out.println(arrayList.get(0)); // C++
System.out.println(arrayList.size()); // 2
// LinkedList: 이중 연결 리스트, 중간 삽입/삭제 O(1), 인덱스 접근 O(n)
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.addFirst(1);
linkedList.addLast(3);
linkedList.add(1, 2); // [1, 2, 3]
Java
복사
11-2. Map
// HashMap: 해시 테이블, 순서 없음, O(1) 평균
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("apple", 3);
hashMap.put("banana", 5);
hashMap.put("apple", 7); // 덮어쓰기
System.out.println(hashMap.get("apple")); // 7
System.out.println(hashMap.getOrDefault("cherry", 0)); // 0
hashMap.putIfAbsent("banana", 10); // 이미 존재하면 무시
// 순회
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
// LinkedHashMap: 삽입 순서 유지
// TreeMap: 키 기준 정렬
// computeIfAbsent 패턴 (그룹핑)
Map<Integer, List<String>> grouped = new HashMap<>();
for (String word : List.of("apple", "ant", "ball", "bear")) {
grouped.computeIfAbsent(word.length(), k -> new ArrayList<>()).add(word);
}
// {3=[ant], 4=[ball, bear], 5=[apple]}
Java
복사
11-3. Set
// HashSet: 중복 없음, 순서 없음
Set<String> set = new HashSet<>(Arrays.asList("a", "b", "c", "a"));
System.out.println(set.size()); // 3 (중복 제거)
// TreeSet: 정렬된 Set
Set<Integer> sorted = new TreeSet<>(List.of(5, 3, 1, 4, 2));
System.out.println(sorted); // [1, 2, 3, 4, 5]
// 집합 연산
Set<Integer> s1 = new HashSet<>(Set.of(1, 2, 3, 4));
Set<Integer> s2 = new HashSet<>(Set.of(3, 4, 5, 6));
s1.retainAll(s2); // 교집합: [3, 4]
Java
복사
12. 람다와 함수형 인터페이스
Java 8에서 도입. 메서드를 일급 객체처럼 다룰 수 있게 됐다.
import java.util.function.*;
// 함수형 인터페이스: 추상 메서드가 하나인 인터페이스
// Predicate<T>: T → boolean
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
// Function<T, R>: T → R
Function<String, Integer> strLen = String::length; // 메서드 참조
System.out.println(strLen.apply("hello")); // 5
// Consumer<T>: T → void
Consumer<String> printer = System.out::println;
printer.accept("출력");
// Supplier<T>: () → T
Supplier<List<String>> listFactory = ArrayList::new;
List<String> newList = listFactory.get();
// BiFunction<T, U, R>: (T, U) → R
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println(multiply.apply(3, 4)); // 12
// 함수 합성
Function<Integer, Integer> times2 = x -> x * 2;
Function<Integer, Integer> plus3 = x -> x + 3;
Function<Integer, Integer> times2ThenPlus3 = times2.andThen(plus3);
System.out.println(times2ThenPlus3.apply(5)); // 13
Java
복사
13. Stream API
Java 8에서 도입. 컬렉션 데이터를 선언적으로 처리한다.
import java.util.stream.*;
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 기본 파이프라인: source → intermediate → terminal
int result = numbers.stream()
.filter(n -> n % 2 == 0) // 짝수 필터: [2, 4, 6, 8, 10]
.map(n -> n * n) // 제곱: [4, 16, 36, 64, 100]
.reduce(0, Integer::sum); // 합산: 220
// collect
List<String> words = List.of("apple", "banana", "cherry", "apricot");
List<String> filtered = words.stream()
.filter(w -> w.startsWith("a"))
.sorted()
.collect(Collectors.toList()); // [apple, apricot]
// groupingBy
Map<Integer, List<String>> byLength = words.stream()
.collect(Collectors.groupingBy(String::length));
// {5=[apple], 6=[banana, cherry], 7=[apricot]}
// flatMap: 중첩 구조 평탄화
List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4), List.of(5));
List<Integer> flat = nested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList()); // [1, 2, 3, 4, 5]
// 통계
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
IntSummaryStatistics stats = numbers.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("평균: " + stats.getAverage()); // 5.5
System.out.println("합계: " + stats.getSum()); // 55
Java
복사
14. Optional
NullPointerException 방지를 위한 컨테이너 클래스 (Java 8+).
import java.util.Optional;
// 생성
Optional<String> opt1 = Optional.of("값 있음"); // null 불가
Optional<String> opt2 = Optional.ofNullable(null); // null 허용
Optional<String> opt3 = Optional.empty();
// 조회
opt1.isPresent(); // true
opt2.isEmpty(); // true (Java 11+)
// 꺼내기
String val1 = opt1.get(); // 있으면 꺼냄 (없으면 예외)
String val2 = opt2.orElse("기본값"); // 없으면 기본값
String val3 = opt2.orElseGet(() -> computeDefault()); // 없으면 Supplier 실행
String val4 = opt2.orElseThrow(() -> new RuntimeException("없음"));
// 변환 체이닝 — NullPointerException 없이 안전하게
Optional<String> upper = Optional.ofNullable(getUserName())
.map(String::toUpperCase)
.filter(s -> s.length() > 3);
upper.ifPresent(System.out::println);
// 실무에서는 메서드 반환 타입에만 사용 (필드 타입에는 비권장)
public Optional<User> findById(Long id) {
return Optional.ofNullable(userRepository.get(id));
}
Java
복사
15. 문자열 처리
String s = "Hello, World!";
// 기본 메서드
System.out.println(s.length()); // 13
System.out.println(s.charAt(0)); // H
System.out.println(s.indexOf("World")); // 7
System.out.println(s.contains("Hello")); // true
System.out.println(s.toUpperCase()); // HELLO, WORLD!
System.out.println(s.toLowerCase()); // hello, world!
System.out.println(s.trim()); // 앞뒤 공백 제거
System.out.println(s.replace("World", "Java")); // Hello, Java!
System.out.println(s.substring(7, 12)); // World
String[] parts = s.split(", "); // ["Hello", "World!"]
// 문자열 비교 — == 는 참조 비교, equals()가 맞음
String a = new String("hello");
String b = new String("hello");
System.out.println(a == b); // false (참조 다름)
System.out.println(a.equals(b)); // true (값 비교)
// StringBuilder: 반복 문자열 조작 시 성능 우위
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
sb.append(i).append(", ");
}
sb.deleteCharAt(sb.length() - 1); // 마지막 공백 제거
sb.deleteCharAt(sb.length() - 1); // 마지막 쉼표 제거
System.out.println(sb.toString()); // 1, 2, 3, 4, 5
// String.format / formatted (Java 15+)
String msg = String.format("이름: %s, 나이: %d세", "홍길동", 30);
String msg2 = "이름: %s, 나이: %d세".formatted("홍길동", 30);
// 텍스트 블록 (Java 15+)
String json = """
{
"name": "홍길동",
"age": 30
}
""";
Java
복사
16. record (Java 16+)
불변 데이터 클래스를 간결하게 선언한다. equals(), hashCode(), toString()이 자동 생성된다.
// 전통적인 방식
public final class PointOld {
private final int x;
private final int y;
public PointOld(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
// equals, hashCode, toString 직접 구현 필요...
}
// record: 위와 동일한 의미
public record Point(int x, int y) {}
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]
System.out.println(p.equals(new Point(3, 4))); // true
// 커스텀 생성자 추가 가능
public record Range(int min, int max) {
public Range {
if (min > max) throw new IllegalArgumentException("min > max");
}
}
Java
복사
17. sealed class (Java 17+)
상속을 허용할 클래스를 명시적으로 제한한다.
public sealed class Shape permits Circle, Rectangle, Triangle {}
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) { this.radius = radius; }
public double area() { return Math.PI * radius * radius; }
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double w, double h) { width = w; height = h; }
public double area() { return width * height; }
}
public final class Triangle extends Shape {
private final double base, height;
public Triangle(double b, double h) { base = b; height = h; }
public double area() { return 0.5 * base * height; }
}
// switch 패턴 매칭 (Java 21+)과 결합하면 강력
double area = switch (shape) {
case Circle c -> c.area();
case Rectangle r -> r.area();
case Triangle t -> t.area();
};
// 컴파일러가 모든 케이스를 망라했는지 검증해줌 (default 불필요)
Java
복사
핵심 요약
주제 | 핵심 포인트 |
데이터 타입 | 기본 타입은 스택, 참조 타입은 힙에 저장 |
OOP | 캡슐화→상속→다형성→추상화 4대 원칙 |
추상 클래스 vs 인터페이스 | 공통 구현 공유 vs 타입 규약 정의 |
제네릭 | 컴파일 타임 타입 안전성 보장 |
예외 | 체크 예외(컴파일 강제) vs 언체크 예외(런타임) |
컬렉션 | List(순서·중복O), Set(중복X), Map(키-값) |
람다·Stream | 선언적 데이터 처리, 함수형 프로그래밍 패러다임 |
Optional | NPE 방지, 반환 타입에서만 사용 권장 |
record | 불변 DTO에 적합, boilerplate 제거 |
sealed class | 타입 계층 명시적 제한, 패턴 매칭과 시너지 |

