최신 Java 기능 (Java 14+)
Record Java 14+
불변 데이터를 간단하게 정의할 수 있는 클래스입니다. 자동으로 생성자, getter, equals, hashCode, toString 메서드를 생성해줍니다.
// 기존 방식
public class PersonOld {
private final String name;
private final int age;
public PersonOld(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
// equals, hashCode, toString 구현 필요...
}
// Record 사용
public record Person(String name, int age) {
// 생성자, getter, equals, hashCode, toString 자동 생성!
// 추가 메서드 정의 가능
public boolean isAdult() {
return age >= 18;
}
}
// 사용 예시
Person person = new Person("홍길동", 25);
System.out.println(person.name()); // 홍길동
System.out.println(person.age()); // 25
System.out.println(person.isAdult()); // true
Sealed Classes Java 17+
상속을 제한하여 허용된 하위 클래스만 만들 수 있게 하는 기능입니다. 패턴 매칭과 함께 사용하면 강력합니다.
// Sealed 클래스 정의
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 getRadius() { return radius; }
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double getWidth() { return width; }
public double getHeight() { return height; }
}
public final class Triangle extends Shape {
private final double base, height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
public double getBase() { return base; }
public double getHeight() { return height; }
}
// 패턴 매칭과 함께 사용
public double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.getRadius() * c.getRadius();
case Rectangle r -> r.getWidth() * r.getHeight();
case Triangle t -> 0.5 * t.getBase() * t.getHeight();
};
}
Text Blocks Java 15+
여러 줄 문자열을 쉽게 작성할 수 있는 기능입니다. JSON, SQL, HTML 등을 작성할 때 매우 유용합니다.
// 기존 방식
String json = "{\n" +
" \"name\": \"홍길동\",\n" +
" \"age\": 25,\n" +
" \"city\": \"서울\"\n" +
"}";
// Text Blocks 사용
String jsonTextBlock = """
{
"name": "홍길동",
"age": 25,
"city": "서울"
}
""";
// SQL 예시
String sql = """
SELECT u.name, u.email, p.title
FROM users u
JOIN posts p ON u.id = p.user_id
WHERE u.active = true
ORDER BY p.created_at DESC
""";
// HTML 예시
String html = """
안녕하세요!
Java Text Blocks를 사용한 HTML입니다.
""";Pattern Matching Java 17+
instanceof와 switch 문에서 패턴 매칭을 사용하여 더 간결하고 안전한 코드를 작성할 수 있습니다.
// instanceof 패턴 매칭
public String formatValue(Object obj) {
// 기존 방식
if (obj instanceof String) {
String s = (String) obj;
return "String: " + s.toUpperCase();
}
// 패턴 매칭 사용
if (obj instanceof String s) {
return "String: " + s.toUpperCase();
} else if (obj instanceof Integer i) {
return "Integer: " + (i * 2);
} else if (obj instanceof Double d) {
return "Double: " + String.format("%.2f", d);
}
return "Unknown type";
}
// Switch 패턴 매칭
public String processValue(Object value) {
return switch (value) {
case String s -> "문자열: " + s.length() + "글자";
case Integer i -> "정수: " + i;
case Double d -> "실수: " + d;
case null -> "null 값";
default -> "알 수 없는 타입";
};
}
함수형 프로그래밍 (Java 8+)
Lambda Expressions Java 8+
익명 함수를 간결하게 표현할 수 있는 방법입니다. 함수형 인터페이스와 함께 사용하여 코드를 더 읽기 쉽게 만듭니다.
import java.util.*;
import java.util.function.*;
// 기존 익명 클래스 방식
List names = Arrays.asList("Alice", "Bob", "Charlie");
names.sort(new Comparator() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// Lambda 표현식 사용
names.sort((a, b) -> a.compareTo(b));
// 또는 더 간단하게
names.sort(String::compareTo);
// 다양한 Lambda 예시
Predicate isEmpty = s -> s.isEmpty();
Function getLength = s -> s.length();
Consumer print = s -> System.out.println(s);
Supplier getHello = () -> "Hello World";
// 사용 예시
List words = Arrays.asList("java", "lambda", "stream");
words.stream()
.filter(word -> word.length() > 4) // 4글자 초과
.map(String::toUpperCase) // 대문자 변환
.forEach(System.out::println); // 출력
Stream API Java 8+
컬렉션 데이터를 함수형 스타일로 처리할 수 있는 API입니다. 필터링, 매핑, 리듀싱 등의 연산을 체이닝으로 수행할 수 있습니다.
import java.util.*;
import java.util.stream.*;
List people = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 35),
new Person("Diana", 28)
);
// 나이가 30 이상인 사람들의 이름을 대문자로 변환하여 리스트로 수집
List result = people.stream()
.filter(person -> person.getAge() >= 30)
.map(person -> person.getName().toUpperCase())
.collect(Collectors.toList());
// 평균 나이 계산
double averageAge = people.stream()
.mapToInt(Person::getAge)
.average()
.orElse(0.0);
// 이름별로 그룹핑
Map> groupedByName = people.stream()
.collect(Collectors.groupingBy(Person::getName));
// 병렬 스트림 사용
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = numbers.parallelStream()
.filter(n -> n % 2 == 0) // 짝수만
.mapToInt(n -> n * n) // 제곱
.sum(); // 합계
System.out.println("짝수 제곱의 합: " + sum); // 220
Optional Java 8+
null 값을 안전하게 처리할 수 있는 컨테이너 클래스입니다. NullPointerException을 방지하고 더 명확한 API를 제공합니다.
import java.util.Optional;
// Optional 생성
Optional optional1 = Optional.of("Hello");
Optional optional2 = Optional.ofNullable(null);
Optional optional3 = Optional.empty();
// 값 확인 및 처리
optional1.ifPresent(System.out::println); // Hello 출력
// 기본값 제공
String value = optional2.orElse("기본값");
String value2 = optional2.orElseGet(() -> "동적 기본값");
// 예외 던지기
try {
String value3 = optional2.orElseThrow(() ->
new IllegalArgumentException("값이 없습니다"));
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
// 체이닝
Optional result = Optional.of(" hello world ")
.filter(s -> !s.trim().isEmpty())
.map(String::trim)
.map(String::toUpperCase);
result.ifPresent(System.out::println); // HELLO WORLD
// 실제 사용 예시 - 사용자 검색
public Optional findUserById(Long id) {
// 데이터베이스에서 사용자 검색
User user = database.findUser(id);
return Optional.ofNullable(user);
}
// 사용
findUserById(1L)
.map(User::getEmail)
.filter(email -> email.contains("@"))
.ifPresentOrElse(
email -> System.out.println("이메일: " + email),
() -> System.out.println("유효한 사용자를 찾을 수 없습니다")
);
Method References Java 8+
기존 메서드를 람다 표현식 대신 참조하여 사용할 수 있는 기능입니다. 코드를 더 간결하고 읽기 쉽게 만듭니다.
import java.util.*;
import java.util.function.*;
List names = Arrays.asList("Alice", "Bob", "Charlie");
// 1. 정적 메서드 참조 (ClassName::staticMethod)
names.sort(String::compareToIgnoreCase);
// 2. 인스턴스 메서드 참조 (instance::method)
String prefix = "Hello ";
Function addPrefix = prefix::concat;
System.out.println(addPrefix.apply("World")); // Hello World
// 3. 특정 타입의 임의 객체의 인스턴스 메서드 참조 (ClassName::instanceMethod)
names.stream()
.map(String::toUpperCase) // s -> s.toUpperCase()와 동일
.forEach(System.out::println);
// 4. 생성자 참조 (ClassName::new)
Supplier
- > listSupplier = ArrayList::new;
Function
컬렉션 프레임워크
List Interface
순서가 있는 데이터의 집합을 다루는 인터페이스입니다. 중복을 허용하고 인덱스로 접근할 수 있습니다.
import java.util.*;
// ArrayList - 동적 배열, 빠른 접근
List arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");
arrayList.add(1, "Blueberry"); // 인덱스 1에 삽입
// LinkedList - 연결 리스트, 빠른 삽입/삭제
List linkedList = new LinkedList<>();
linkedList.addFirst("First");
linkedList.addLast("Last");
// Vector - 동기화된 ArrayList (레거시)
List vector = new Vector<>();
// 공통 메서드들
System.out.println(arrayList.get(0)); // Apple
System.out.println(arrayList.size()); // 4
System.out.println(arrayList.contains("Apple")); // true
// 반복
for (String fruit : arrayList) {
System.out.println(fruit);
}
// Stream과 함께 사용
arrayList.stream()
.filter(fruit -> fruit.startsWith("B"))
.forEach(System.out::println);
// List.of() - 불변 리스트 (Java 9+)
List immutableList = List.of("A", "B", "C");
// immutableList.add("D"); // UnsupportedOperationException
Set Interface
중복을 허용하지 않는 데이터의 집합을 다루는 인터페이스입니다. 수학의 집합 개념과 유사합니다.
import java.util.*;
// HashSet - 해시 테이블 기반, 빠른 검색
Set hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Apple"); // 중복 - 추가되지 않음
System.out.println(hashSet.size()); // 2
// LinkedHashSet - 삽입 순서 보장
Set linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("Third");
linkedHashSet.add("First");
linkedHashSet.add("Second");
// 출력 순서: Third, First, Second
// TreeSet - 정렬된 순서 보장
Set treeSet = new TreeSet<>();
treeSet.add("Zebra");
treeSet.add("Apple");
treeSet.add("Banana");
// 출력 순서: Apple, Banana, Zebra
// 집합 연산
Set set1 = new HashSet<>(Arrays.asList(1, 2, 3, 4));
Set set2 = new HashSet<>(Arrays.asList(3, 4, 5, 6));
// 합집합 (Union)
Set union = new HashSet<>(set1);
union.addAll(set2);
System.out.println(union); // [1, 2, 3, 4, 5, 6]
// 교집합 (Intersection)
Set intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println(intersection); // [3, 4]
// 차집합 (Difference)
Set difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println(difference); // [1, 2]
Map Interface
키-값 쌍으로 데이터를 저장하는 인터페이스입니다. 키는 중복될 수 없지만 값은 중복될 수 있습니다.
import java.util.*;
// HashMap - 해시 테이블 기반, 빠른 검색
Map hashMap = new HashMap<>();
hashMap.put("Apple", 100);
hashMap.put("Banana", 200);
hashMap.put("Cherry", 150);
// LinkedHashMap - 삽입 순서 보장
Map linkedHashMap = new LinkedHashMap<>();
// TreeMap - 키 기준 정렬
Map treeMap = new TreeMap<>();
// 값 접근
System.out.println(hashMap.get("Apple")); // 100
System.out.println(hashMap.getOrDefault("Orange", 0)); // 0
// 키 존재 확인
if (hashMap.containsKey("Apple")) {
System.out.println("Apple이 있습니다");
}
// 반복
// 1. entrySet() 사용
for (Map.Entry entry : hashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 2. keySet() 사용
for (String key : hashMap.keySet()) {
System.out.println(key + ": " + hashMap.get(key));
}
// 3. forEach() 사용 (Java 8+)
hashMap.forEach((key, value) ->
System.out.println(key + ": " + value));
// 고급 메서드들 (Java 8+)
hashMap.putIfAbsent("Orange", 300);
hashMap.computeIfAbsent("Grape", k -> k.length() * 10);
hashMap.merge("Apple", 50, Integer::sum); // 기존 값과 합산
// Map.of() - 불변 맵 (Java 9+)
Map immutableMap = Map.of(
"A", 1,
"B", 2,
"C", 3
);
Collectors
Stream API와 함께 사용하여 데이터를 수집하고 변환하는 유틸리티 클래스입니다. 다양한 수집 연산을 제공합니다.
import java.util.*;
import java.util.stream.*;
List people = Arrays.asList(
new Person("Alice", 25, "Engineering"),
new Person("Bob", 30, "Marketing"),
new Person("Charlie", 35, "Engineering"),
new Person("Diana", 28, "Marketing")
);
// 1. 리스트로 수집
List names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
// 2. 집합으로 수집 (중복 제거)
Set departments = people.stream()
.map(Person::getDepartment)
.collect(Collectors.toSet());
// 3. 맵으로 수집
Map nameToAge = people.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAge
));
// 4. 그룹핑
Map> byDepartment = people.stream()
.collect(Collectors.groupingBy(Person::getDepartment));
// 5. 파티셔닝 (조건에 따라 true/false로 분할)
Map> partitioned = people.stream()
.collect(Collectors.partitioningBy(p -> p.getAge() >= 30));
// 6. 통계 수집
IntSummaryStatistics ageStats = people.stream()
.collect(Collectors.summarizingInt(Person::getAge));
System.out.println("평균 나이: " + ageStats.getAverage());
System.out.println("최대 나이: " + ageStats.getMax());
// 7. 문자열 조인
String allNames = people.stream()
.map(Person::getName)
.collect(Collectors.joining(", ", "[", "]"));
System.out.println(allNames); // [Alice, Bob, Charlie, Diana]
// 8. 다운스트림 컬렉터
Map avgAgeByDept = people.stream()
.collect(Collectors.groupingBy(
Person::getDepartment,
Collectors.averagingInt(Person::getAge)
));
객체지향 프로그래밍
Encapsulation (캡슐화)
데이터와 메서드를 하나로 묶고 외부에서의 직접 접근을 제한하는 원칙입니다. private 필드와 public 메서드를 통해 구현합니다.
public class BankAccount {
// private 필드 - 외부에서 직접 접근 불가
private String accountNumber;
private double balance;
private String ownerName;
// 생성자
public BankAccount(String accountNumber, String ownerName) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0.0;
}
// public 메서드를 통한 안전한 접근
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println(amount + "원이 입금되었습니다.");
} else {
throw new IllegalArgumentException("입금액은 0보다 커야 합니다.");
}
}
public boolean withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println(amount + "원이 출금되었습니다.");
return true;
} else {
System.out.println("출금할 수 없습니다.");
return false;
}
}
// getter 메서드 - 읽기 전용 접근
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
public String getOwnerName() {
return ownerName;
}
// setter 메서드 - 검증 로직 포함
public void setOwnerName(String ownerName) {
if (ownerName != null && !ownerName.trim().isEmpty()) {
this.ownerName = ownerName;
} else {
throw new IllegalArgumentException("소유자 이름은 비어있을 수 없습니다.");
}
}
}
Inheritance (상속)
기존 클래스의 속성과 메서드를 새로운 클래스가 물려받는 기능입니다. 코드 재사용성을 높이고 계층 구조를 만들 수 있습니다.
// 부모 클래스 (슈퍼클래스)
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + "이(가) 먹고 있습니다.");
}
public void sleep() {
System.out.println(name + "이(가) 잠을 자고 있습니다.");
}
// 오버라이드될 수 있는 메서드
public void makeSound() {
System.out.println(name + "이(가) 소리를 냅니다.");
}
}
// 자식 클래스 (서브클래스)
public class Dog extends Animal {
private String breed;
public Dog(String name, int age, String breed) {
super(name, age); // 부모 생성자 호출
this.breed = breed;
}
// 메서드 오버라이딩
@Override
public void makeSound() {
System.out.println(name + "이(가) 멍멍 짖습니다.");
}
// 새로운 메서드 추가
public void wagTail() {
System.out.println(name + "이(가) 꼬리를 흔듭니다.");
}
public String getBreed() {
return breed;
}
}
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age);
}
@Override
public void makeSound() {
System.out.println(name + "이(가) 야옹 웁니다.");
}
public void climb() {
System.out.println(name + "이(가) 나무를 타고 있습니다.");
}
}
// 사용 예시
public class Main {
public static void main(String[] args) {
Dog dog = new Dog("멍멍이", 3, "골든 리트리버");
Cat cat = new Cat("야옹이", 2);
dog.eat(); // 상속받은 메서드
dog.makeSound(); // 오버라이드된 메서드
dog.wagTail(); // 고유 메서드
cat.eat(); // 상속받은 메서드
cat.makeSound(); // 오버라이드된 메서드
cat.climb(); // 고유 메서드
}
}
Polymorphism (다형성)
하나의 인터페이스로 여러 타입의 객체를 다룰 수 있는 능력입니다. 메서드 오버라이딩과 인터페이스를 통해 구현됩니다.
// 인터페이스 정의
interface Shape {
double calculateArea();
double calculatePerimeter();
void draw();
}
// 구현 클래스들
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
@Override
public void draw() {
System.out.println("원을 그립니다. 반지름: " + radius);
}
}
class Rectangle implements Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double calculateArea() {
return width * height;
}
@Override
public double calculatePerimeter() {
return 2 * (width + height);
}
@Override
public void draw() {
System.out.println("사각형을 그립니다. " + width + " x " + height);
}
}
// 다형성 활용
public class ShapeCalculator {
// 다양한 Shape 타입을 받을 수 있음
public void printShapeInfo(Shape shape) {
shape.draw();
System.out.println("넓이: " + shape.calculateArea());
System.out.println("둘레: " + shape.calculatePerimeter());
System.out.println();
}
public double getTotalArea(Shape[] shapes) {
double total = 0;
for (Shape shape : shapes) {
total += shape.calculateArea(); // 런타임에 적절한 메서드 호출
}
return total;
}
public static void main(String[] args) {
ShapeCalculator calculator = new ShapeCalculator();
// 다양한 타입의 객체를 같은 방식으로 처리
Shape[] shapes = {
new Circle(5.0),
new Rectangle(4.0, 6.0),
new Circle(3.0)
};
for (Shape shape : shapes) {
calculator.printShapeInfo(shape);
}
System.out.println("전체 넓이: " + calculator.getTotalArea(shapes));
}
}
Abstract Classes
인스턴스를 생성할 수 없는 클래스로, 공통 기능을 제공하면서 하위 클래스에서 구현해야 할 추상 메서드를 정의합니다.
// 추상 클래스
public abstract class Vehicle {
protected String brand;
protected String model;
protected int year;
public Vehicle(String brand, String model, int year) {
this.brand = brand;
this.model = model;
this.year = year;
}
// 구현된 메서드 (공통 기능)
public void startEngine() {
System.out.println(brand + " " + model + "의 엔진을 시작합니다.");
}
public void stopEngine() {
System.out.println(brand + " " + model + "의 엔진을 정지합니다.");
}
// 추상 메서드 (하위 클래스에서 반드시 구현)
public abstract void accelerate();
public abstract void brake();
public abstract double getFuelEfficiency();
// getter 메서드들
public String getBrand() { return brand; }
public String getModel() { return model; }
public int getYear() { return year; }
}
// 구체 클래스 1
public class Car extends Vehicle {
private int numberOfDoors;
public Car(String brand, String model, int year, int numberOfDoors) {
super(brand, model, year);
this.numberOfDoors = numberOfDoors;
}
@Override
public void accelerate() {
System.out.println("자동차가 가속합니다.");
}
@Override
public void brake() {
System.out.println("자동차가 브레이크를 밟습니다.");
}
@Override
public double getFuelEfficiency() {
return 12.5; // km/l
}
public int getNumberOfDoors() {
return numberOfDoors;
}
}
// 구체 클래스 2
public class Motorcycle extends Vehicle {
private boolean hasSidecar;
public Motorcycle(String brand, String model, int year, boolean hasSidecar) {
super(brand, model, year);
this.hasSidecar = hasSidecar;
}
@Override
public void accelerate() {
System.out.println("오토바이가 가속합니다.");
}
@Override
public void brake() {
System.out.println("오토바이가 브레이크를 잡습니다.");
}
@Override
public double getFuelEfficiency() {
return 25.0; // km/l
}
public boolean hasSidecar() {
return hasSidecar;
}
}
// 사용 예시
public class VehicleTest {
public static void main(String[] args) {
// Vehicle vehicle = new Vehicle(); // 컴파일 에러! 추상 클래스는 인스턴스화 불가
Vehicle car = new Car("현대", "소나타", 2023, 4);
Vehicle motorcycle = new Motorcycle("할리데이비슨", "스포츠터", 2023, false);
Vehicle[] vehicles = {car, motorcycle};
for (Vehicle vehicle : vehicles) {
vehicle.startEngine();
vehicle.accelerate();
System.out.println("연비: " + vehicle.getFuelEfficiency() + " km/l");
vehicle.brake();
vehicle.stopEngine();
System.out.println();
}
}
}
동시성 프로그래밍
Thread
프로그램 내에서 실행되는 독립적인 실행 단위입니다. 멀티스레딩을 통해 동시에 여러 작업을 수행할 수 있습니다.
// 1. Thread 클래스 상속
class MyThread extends Thread {
private String threadName;
public MyThread(String name) {
this.threadName = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + ": " + i);
try {
Thread.sleep(1000); // 1초 대기
} catch (InterruptedException e) {
System.out.println(threadName + " 인터럽트됨");
return;
}
}
System.out.println(threadName + " 완료");
}
}
// 2. Runnable 인터페이스 구현
class MyRunnable implements Runnable {
private String taskName;
public MyRunnable(String name) {
this.taskName = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(taskName + ": " + i);
try {
Thread.sleep(800);
} catch (InterruptedException e) {
System.out.println(taskName + " 인터럽트됨");
return;
}
}
System.out.println(taskName + " 완료");
}
}
// 사용 예시
public class ThreadExample {
public static void main(String[] args) {
// Thread 클래스 사용
MyThread thread1 = new MyThread("스레드-1");
MyThread thread2 = new MyThread("스레드-2");
// Runnable 인터페이스 사용
Thread thread3 = new Thread(new MyRunnable("작업-3"));
// 람다 표현식 사용
Thread thread4 = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("람다 스레드: " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
return;
}
}
});
// 스레드 시작
thread1.start();
thread2.start();
thread3.start();
thread4.start();
// 메인 스레드에서 다른 스레드들이 끝날 때까지 대기
try {
thread1.join();
thread2.join();
thread3.join();
thread4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("모든 스레드 완료");
}
}
Synchronization
여러 스레드가 공유 자원에 동시에 접근할 때 발생할 수 있는 문제를 방지하기 위한 동기화 메커니즘입니다.
// 동기화가 필요한 공유 자원
class Counter {
private int count = 0;
// synchronized 메서드
public synchronized void increment() {
count++;
}
// synchronized 블록
public void decrement() {
synchronized(this) {
count--;
}
}
public synchronized int getCount() {
return count;
}
}
// 은행 계좌 예시
class BankAccount {
private double balance;
private final Object lock = new Object();
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
synchronized(lock) {
double newBalance = balance + amount;
// 시뮬레이션: 처리 시간
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return;
}
balance = newBalance;
System.out.println(Thread.currentThread().getName() +
" 입금: " + amount + ", 잔액: " + balance);
}
}
public boolean withdraw(double amount) {
synchronized(lock) {
if (balance >= amount) {
double newBalance = balance - amount;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
return false;
}
balance = newBalance;
System.out.println(Thread.currentThread().getName() +
" 출금: " + amount + ", 잔액: " + balance);
return true;
} else {
System.out.println(Thread.currentThread().getName() +
" 출금 실패: 잔액 부족");
return false;
}
}
}
public synchronized double getBalance() {
return balance;
}
}
// 사용 예시
public class SynchronizationExample {
public static void main(String[] args) {
BankAccount account = new BankAccount(1000);
// 여러 스레드가 동시에 계좌에 접근
Runnable depositTask = () -> {
for (int i = 0; i < 5; i++) {
account.deposit(100);
}
};
Runnable withdrawTask = () -> {
for (int i = 0; i < 3; i++) {
account.withdraw(150);
}
};
Thread t1 = new Thread(depositTask, "입금자-1");
Thread t2 = new Thread(depositTask, "입금자-2");
Thread t3 = new Thread(withdrawTask, "출금자-1");
Thread t4 = new Thread(withdrawTask, "출금자-2");
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("최종 잔액: " + account.getBalance());
}
}
CompletableFuture Java 8+
비동기 프로그래밍을 위한 고수준 API입니다. Future의 확장된 버전으로 콜백, 조합, 예외 처리 등을 지원합니다.
import java.util.concurrent.*;
import java.util.function.*;
public class CompletableFutureExample {
// 비동기 작업 시뮬레이션
public static CompletableFuture fetchUserData(int userId) {
return CompletableFuture.supplyAsync(() -> {
// 네트워크 요청 시뮬레이션
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "User-" + userId + " 데이터";
});
}
public static CompletableFuture fetchUserProfile(String userData) {
return CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(800);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return userData + "의 프로필";
});
}
public static void main(String[] args) {
System.out.println("비동기 작업 시작");
// 1. 기본 비동기 작업
Completabl
System.out.println("비동기 작업 시작");
// 1. 기본 비동기 작업
CompletableFuture future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "Hello";
});
// 2. 체이닝 (thenApply, thenCompose)
CompletableFuture chainedFuture = fetchUserData(123)
.thenCompose(userData -> fetchUserProfile(userData))
.thenApply(profile -> profile + " (처리 완료)");
// 3. 여러 Future 조합
CompletableFuture future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture combined = future1.thenCombine(future2,
(s1, s2) -> s1 + " " + s2);
// 4. 모든 Future 완료 대기
CompletableFuture allOf = CompletableFuture.allOf(
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
);
// 5. 가장 빠른 Future 결과 사용
CompletableFuture anyOf = CompletableFuture.anyOf(
fetchUserData(1),
fetchUserData(2),
fetchUserData(3)
).thenApply(result -> (String) result);
// 6. 예외 처리
CompletableFuture withExceptionHandling = CompletableFuture
.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("랜덤 에러 발생!");
}
return "성공!";
})
.exceptionally(throwable -> {
System.out.println("예외 처리: " + throwable.getMessage());
return "기본값";
});
// 7. 콜백 등록
chainedFuture.thenAccept(result ->
System.out.println("결과: " + result));
// 결과 대기 및 출력
try {
System.out.println("Combined: " + combined.get());
System.out.println("Exception handling: " + withExceptionHandling.get());
allOf.get(); // 모든 작업 완료 대기
System.out.println("모든 사용자 데이터 로딩 완료");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("메인 스레드 계속 실행 중...");
}
}
제네릭 (Generics)
Generic Classes
타입을 매개변수로 받아 타입 안전성을 보장하는 클래스입니다. 컴파일 타임에 타입 체크를 수행하여 런타임 오류를 방지합니다.
// 제네릭 클래스 정의
public class Box {
private T content;
public Box() {}
public Box(T content) {
this.content = content;
}
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public boolean isEmpty() {
return content == null;
}
}
// 여러 타입 매개변수를 가진 제네릭 클래스
public class Pair {
private T first;
private U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() { return first; }
public U getSecond() { return second; }
public void setFirst(T first) { this.first = first; }
public void setSecond(U second) { this.second = second; }
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}
// 사용 예시
public class GenericExample {
public static void main(String[] args) {
// 타입 안전성 보장
Box stringBox = new Box<>("Hello");
Box intBox = new Box<>(42);
Box
- > listBox = new Box<>(Arrays.asList("a", "b", "c"));
String str = stringBox.getContent(); // 캐스팅 불필요
Integer num = intBox.getContent(); // 캐스팅 불필요
// 컴파일 에러 방지
// stringBox.setContent(123); // 컴파일 에러!
// 여러 타입 매개변수 사용
Pair
Wildcards
제네릭에서 타입의 범위를 제한하거나 확장할 때 사용하는 와일드카드입니다. ?, extends, super 키워드를 사용합니다.
import java.util.*;
public class WildcardExample {
// 1. Unbounded Wildcard (?)
public static void printList(List> list) {
for (Object item : list) {
System.out.println(item);
}
}
// 2. Upper Bounded Wildcard (? extends)
// Number 또는 Number의 하위 타입만 허용
public static double sumNumbers(List extends Number> numbers) {
double sum = 0.0;
for (Number num : numbers) {
sum += num.doubleValue();
}
return sum;
}
// 3. Lower Bounded Wildcard (? super)
// Integer 또는 Integer의 상위 타입만 허용
public static void addIntegers(List super Integer> list) {
list.add(1);
list.add(2);
list.add(3);
}
// 제네릭 메서드
public static void swap(List list, int i, int j) {
T temp = list.get(i);
list.set(i, list.get(j));
list.set(j, temp);
}
// 바운드된 타입 매개변수
public static > T findMax(List list) {
if (list.isEmpty()) {
return null;
}
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}
public static void main(String[] args) {
// Unbounded Wildcard 사용
List stringList = Arrays.asList("a", "b", "c");
List intList = Arrays.asList(1, 2, 3);
printList(stringList); // 어떤 타입의 리스트든 가능
printList(intList);
// Upper Bounded Wildcard 사용
List integers = Arrays.asList(1, 2, 3, 4, 5);
List doubles = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("Integer 합: " + sumNumbers(integers));
System.out.println("Double 합: " + sumNumbers(doubles));
// Lower Bounded Wildcard 사용
List numbers = new ArrayList<>();
List
예외 처리 (Exception Handling)
Try-Catch-Finally
예외가 발생할 수 있는 코드를 안전하게 처리하는 기본적인 방법입니다. try 블록에서 예외가 발생하면 catch 블록에서 처리합니다.
import java.io.*;
import java.util.*;
public class ExceptionHandlingExample {
public static void basicTryCatch() {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // ArrayIndexOutOfBoundsException 발생
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("배열 인덱스 오류: " + e.getMessage());
} catch (Exception e) {
System.out.println("일반적인 예외: " + e.getMessage());
} finally {
System.out.println("finally 블록은 항상 실행됩니다.");
}
}
public static void multiCatchExample() {
try {
String str = null;
int length = str.length(); // NullPointerException
int result = 10 / 0; // ArithmeticException
} catch (NullPointerException | ArithmeticException e) {
// Java 7+ 다중 catch
System.out.println("Null 또는 산술 예외: " + e.getClass().getSimpleName());
} catch (Exception e) {
System.out.println("기타 예외: " + e.getMessage());
}
}
public static void tryWithResources() {
// try-with-resources (Java 7+)
// AutoCloseable 인터페이스를 구현한 리소스 자동 해제
try (Scanner scanner = new Scanner(System.in);
FileWriter writer = new FileWriter("output.txt")) {
System.out.print("입력하세요: ");
String input = scanner.nextLine();
writer.write("사용자 입력: " + input);
} catch (IOException e) {
System.out.println("파일 처리 오류: " + e.getMessage());
}
// scanner와 writer는 자동으로 close() 호출됨
}
public static void nestedTryExample() {
try {
System.out.println("외부 try 블록");
try {
System.out.println("내부 try 블록");
int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
System.out.println("내부 catch: " + e.getMessage());
throw new RuntimeException("내부에서 새로운 예외 발생");
}
} catch (RuntimeException e) {
System.out.println("외부 catch: " + e.getMessage());
} finally {
System.out.println("외부 finally 블록");
}
}
public static void main(String[] args) {
System.out.println("=== 기본 try-catch ===");
basicTryCatch();
System.out.println("\n=== 다중 catch ===");
multiCatchExample();
System.out.println("\n=== 중첩 try ===");
nestedTryExample();
System.out.println("\n=== try-with-resources ===");
tryWithResources();
}
}
Custom Exceptions
애플리케이션의 특정 상황에 맞는 사용자 정의 예외를 만들어 더 명확하고 의미있는 예외 처리를 할 수 있습니다.
// 사용자 정의 Checked Exception
class InsufficientFundsException extends Exception {
private double amount;
private double balance;
public InsufficientFundsException(double amount, double balance) {
super(String.format("잔액 부족: 요청 금액 %.2f, 현재 잔액 %.2f", amount, balance));
this.amount = amount;
this.balance = balance;
}
public double getAmount() { return amount; }
public double getBalance() { return balance; }
public double getShortfall() { return amount - balance; }
}
// 사용자 정의 Unchecked Exception
class InvalidAccountNumberException extends RuntimeException {
private String accountNumber;
public InvalidAccountNumberException(String accountNumber) {
super("유효하지 않은 계좌번호: " + accountNumber);
this.accountNumber = accountNumber;
}
public String getAccountNumber() { return accountNumber; }
}
// 비즈니스 로직 클래스
class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double initialBalance) {
if (accountNumber == null || accountNumber.length() != 10) {
throw new InvalidAccountNumberException(accountNumber);
}
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public void withdraw(double amount) throws InsufficientFundsException {
if (amount <= 0) {
throw new IllegalArgumentException("출금 금액은 0보다 커야 합니다: " + amount);
}
if (amount > balance) {
throw new InsufficientFundsException(amount, balance);
}
balance -= amount;
System.out.println(String.format("%.2f 출금 완료. 잔액: %.2f", amount, balance));
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("입금 금액은 0보다 커야 합니다: " + amount);
}
balance += amount;
System.out.println(String.format("%.2f 입금 완료. 잔액: %.2f", amount, balance));
}
public double getBalance() { return balance; }
public String getAccountNumber() { return accountNumber; }
}
// 사용 예시
public class CustomExceptionExample {
public static void main(String[] args) {
try {
// 정상적인 계좌 생성
BankAccount account = new BankAccount("1234567890", 1000.0);
// 정상적인 거래
account.deposit(500.0);
account.withdraw(200.0);
// 잔액 부족 예외 발생
account.withdraw(2000.0);
} catch (InsufficientFundsException e) {
System.err.println("거래 실패: " + e.getMessage());
System.err.println("부족한 금액: " + e.getShortfall());
// 복구 로직
System.out.println("대출 상품을 안내해드릴까요?");
} catch (InvalidAccountNumberException e) {
System.err.println("계좌 생성 실패: " + e.getMessage());
System.err.println("올바른 계좌번호를 입력해주세요.");
} catch (IllegalArgumentException e) {
System.err.println("입력 오류: " + e.getMessage());
} catch (Exception e) {
System.err.println("예상치 못한 오류: " + e.getMessage());
e.printStackTrace();
}
// 잘못된 계좌번호로 생성 시도
try {
BankAccount invalidAccount = new BankAccount("123", 1000.0);
} catch (InvalidAccountNumberException e) {
System.err.println("계좌 생성 실패: " + e.getMessage());
}
}
}
어노테이션 (Annotations)
Built-in Annotations
Java에서 기본으로 제공하는 어노테이션들입니다. 컴파일러에게 정보를 제공하거나 런타임에 특별한 처리를 할 때 사용됩니다.
// @Override - 메서드 오버라이딩 명시
class Animal {
public void makeSound() {
System.out.println("동물이 소리를 냅니다");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("멍멍!");
}
// @Override
// public void makeSond() { // 컴파일 에러! 오타 발견
// System.out.println("멍멍!");
// }
}
// @Deprecated - 사용 중단 권고
class Calculator {
@Deprecated
public int add(int a, int b) {
return a + b;
}
// 새로운 메서드
public long addLong(long a, long b) {
return a + b;
}
}
// @SuppressWarnings - 경고 억제
class WarningExample {
@SuppressWarnings("unchecked")
public void uncheckedCast() {
List rawList = new ArrayList();
List stringList = (List) rawList; // 경고 억제
}
@SuppressWarnings({"unused", "deprecation"})
public void multipleWarnings() {
Calculator calc = new Calculator();
int result = calc.add(1, 2); // deprecated 경고 억제
String unusedVariable = "사용되지 않음"; // unused 경고 억제
}
}
// @FunctionalInterface - 함수형 인터페이스 명시
@FunctionalInterface
interface MathOperation {
int operate(int a, int b);
// default 메서드는 허용
default void printResult(int result) {
System.out.println("결과: " + result);
}
// static 메서드도 허용
static void printInfo() {
System.out.println("수학 연산 인터페이스");
}
// int anotherMethod(); // 컴파일 에러! 추상 메서드는 하나만 허용
}
// @SafeVarargs - 가변인자 안전성 보장
class VarargsExample {
@SafeVarargs
public static void printAll(T... items) {
for (T item : items) {
System.out.println(item);
}
}
}
// 사용 예시
public class BuiltInAnnotationsExample {
public static void main(String[] args) {
// @Override 사용
Dog dog = new Dog();
dog.makeSound(); // 오버라이드된 메서드 호출
// @Deprecated 사용
Calculator calc = new Calculator();
int result = calc.add(1, 2); // 경고 표시됨
// @FunctionalInterface 사용
MathOperation addition = (a, b) -> a + b;
MathOperation multiplication = (a, b) -> a * b;
addition.printResult(addition.operate(5, 3));
multiplication.printResult(multiplication.operate(5, 3));
// @SafeVarargs 사용
VarargsExample.printAll("Hello", "World", "Java");
VarargsExample.printAll(1, 2, 3, 4, 5);
}
}
Custom Annotations
사용자가 직접 정의하는 어노테이션입니다. 메타데이터를 제공하고 리플렉션을 통해 런타임에 처리할 수 있습니다.
import java.lang.annotation.*;
import java.lang.reflect.*;
// 1. 간단한 마커 어노테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Test {
}
// 2. 값을 가지는 어노테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Benchmark {
String value() default "";
int iterations() default 1;
boolean enabled() default true;
}
// 3. 복합 어노테이션
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Author {
String name();
String email() default "";
String date();
String[] tags() default {};
}
// 4. 검증 어노테이션
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Validate {
int min() default 0;
int max() default Integer.MAX_VALUE;
boolean required() default false;
String pattern() default "";
}
// 어노테이션 사용 예시
@Author(name = "홍길동", date = "2024-01-15", tags = {"utility", "math"})
class Calculator {
@Validate(required = true, min = 0)
private int value;
@Test
public void simpleTest() {
System.out.println("간단한 테스트 실행");
}
@Benchmark(value = "덧셈 성능 테스트", iterations = 1000)
public int add(int a, int b) {
return a + b;
}
@Benchmark(value = "곱셈 성능 테스트", iterations = 500, enabled = false)
public int multiply(int a, int b) {
return a * b;
}
@Author(name = "김개발", date = "2024-01-16")
@Test
public void complexCalculation() {
System.out.println("복잡한 계산 테스트");
}
}
// 어노테이션 처리기
class AnnotationProcessor {
public static void runTests(Class> clazz) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Test.class)) {
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
method.invoke(instance);
System.out.println("✓ " + method.getName() + " 테스트 통과");
} catch (Exception e) {
System.out.println("✗ " + method.getName() + " 테스트 실패: " + e.getMessage());
}
}
}
}
public static void runBenchmarks(Class> clazz) {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Benchmark.class)) {
Benchmark benchmark = method.getAnnotation(Benchmark.class);
if (!benchmark.enabled()) {
System.out.println("⏭ " + method.getName() + " 벤치마크 비활성화됨");
continue;
}
try {
Object instance = clazz.getDeclaredConstructor().newInstance();
long startTime = System.nanoTime();
for (int i = 0; i < benchmark.iterations(); i++) {
method.invoke(instance, 10, 20); // 임의의 매개변수
}
long endTime = System.nanoTime();
double duration = (endTime - startTime) / 1_000_000.0; // ms로 변환
System.out.printf("⏱ %s: %s (%.2f ms, %d iterations)%n",
method.getName(), benchmark.value(), duration, benchmark.iterations());
} catch (Exception e) {
System.out.println("✗ " + method.getName() + " 벤치마크 실패: " + e.getMessage());
}
}
}
}
public static void printAuthorInfo(Class> clazz) {
if (clazz.isAnnotationPresent(Author.class)) {
Author author = clazz.getAnnotation(Author.class);
System.out.println("=== 클래스 작성자 정보 ===");
System.out.println("이름: " + author.name());
System.out.println("이메일: " + author.email());
System.out.println("작성일: " + author.date());
System.out.println("태그: " + String.join(", ", author.tags()));
System.out.println();
}
}
}
// 실행 예시
public class CustomAnnotationExample {
public static void main(String[] args) {
Class calcClass = Calculator.class;
// 작성자 정보 출력
AnnotationProcessor.printAuthorInfo(calcClass);
// 테스트 실행
System.out.println("=== 테스트 실행 ===");
AnnotationProcessor.runTests(calcClass);
System.out.println();
// 벤치마크 실행
System.out.println("=== 벤치마크 실행 ===");
AnnotationProcessor.runBenchmarks(calcClass);
}
}