Backend
home

Java 기본 문법 완전 정복

생성 일시
2026/06/02 10:37
태그
Java
게시일
2026/06/02
최종 편집 일시
2026/06/02 10:37
GitHub 링크
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
타입 계층 명시적 제한, 패턴 매칭과 시너지