티스토리 뷰
Stack Overflow 2024 설문조사에 따르면, Java는 여전히 세계에서 가장 많이 사용되는 프로그래밍 언어 중 하나다.
하지만 놀라운 건 Java 개발자들의 40% 이상이 아직도 Java 8을 사용하고 있다는 사실이다.
즉, 많은 개발자들이 Java의 최신 기능들을 제대로 활용하지 못하고 있다는 뜻이다.
만약 당신이 Java 8에 머물러 있다면, 이런 코드들을 놓치고 있는 것이다.
Java 8
1. 람다 표현식
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Before java 8
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
// Java 8
Collections.sort(names, (a, b) -> a.compareTo(b));
//또는
Collections.sort(names, String::compareTo);
2. Stream API
// Before java 8
List<String> result = new ArrayList<>();
for(String name : names) {
if(name.length() > 3) {
result.add(name.toUpperCase());
}
}
// Java 8
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
3. Optional
// Before Java 8 - null 체크 지옥
public String getUserEmail(Long userId) {
User user = userRepository.findById(userId);
if(user != null) {
Profile profile = user.getProfile();
if (profile != null) {
return profile.getEmail();
}
}
return "default@email.com";
}
// Java 8
public String getUserEmail(Long userId) {
return userRepository.findById(userId)
.map(User::getProfile)
.map(Profile::getEmail)
.orElse("default@email.com");
}
4. 새로운 날짜/시간 API
// Before Java 8 - Date는 mutable하고 thread-safe 하지 않음
Date date = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, 30);
Date futureDate = cal.getTime();
// Java 8 - immutable하고 thread-safe
LocalDate today = LocalDate.now();
LocalDate futureDate = today.plusDays(30);
LocalDateTime meetingTime = LocalDateTime.of(2024, 12, 25, 14, 30);
Java 9(2017)
1. 모듈 시스템
module com.example.myapp {
requires java.base;
requires java.logging;
exports com.example.myapp.api;
}
2. 컬렉션 팩토리 메서드
// Before Java 9
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);
// Java 9
List<String> list = List.of("apple", "banana", "cherry");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("one", 1, "two", 2);
3. 인터페이스 private 메서드
// Java 9
public interface Calculator {
default int addAndMultiply(int a, int b, int multiplier) {
return multiply(add(a, b), multiplier);
}
default int subtractAndMultiply(int a, int b, int multiplier) {
return multiply(subtract(a, b), multiplier);
}
// private 메서드로 중복 제거
private int multiply(int a, int b) {
return a * b;
}
private int add(int a, int b) {
return a + b;
}
private int subtract(int a, int b) {
return a - b;
}
}
Java 10(2018)
var 키워드
// Before Java 10
Map<String, List<Integer>> complexMap = new HashMap<String, List<Integer>>();
List<String> names = Arrays.asList("Alice", "Bob");
// Java 10
var complexMap = new HashMap<String, List<Integer>>();
var names = List.of("Alice", "Bob");
var stream = names.stream().filter(name -> name.startsWith("A"));
// 주의 : 지역 변수에만 사용 가능
public class Example {
// var field; // 컴파일 에러!
public void method() {
var localVar = "This is OK"; // 가능
}
}
Java 11(2018) - LTS
1. String 메서드 추가
String text = " Hello World ";
// isBlank() 공백 문자만 있거나 비어 있으면 true
System.out.println(" ".isBlank()); //true
System.out.println("".isBlank()); //true
System.out.println("a".isBlank()); //false
// strip() 앞뒤 공백 제거(유니 코드 지원)
System.out.println(text.strip()); //"Hello World"
System.out.println(text.stripLeading()); //"Hello World "
System.out.println(text.stripTrailing()); //" Hello World"
// lines() 줄 단위로 나누어 Stream 반환
String multiline = "Line1\nLine2\nLine3";
multiline.lines().forEach(System.out::println);
2. Files 메서드
// Java 11
Path path = Paths.get("example.txt");
// 파일 읽기/쓰기가 간단해짐
String content = Files.readString(path);
Files.writeString(path, "Hello World");
// 이전에는 복잡했음
List<String> lines = Files.readAllLines(path);
String content = String.join("\n", lines);
3. HTTP Client API
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// 비동기 처리
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
Java 14(2020)
1. Records(Preview)
// Before Java 14
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
@Override
public boolean equals(Object o) {
// ... 복잡한 equals 구현
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
// Java 14 Record
public record Person(String name, int age) {
// 생성자, getter, equals, hashCode, toString 자동 생성!
// 추가 검증이 필요하면
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
}
// 사용
Person person = new Person("Alice", 30);
System.out.println(person.name()); // "Alice"
System.out.println(person.age()); // 30
2. Switch 표현식
// Before Java 14
String result;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
result = "6";
break;
case TUESDAY:
result = "7";
break;
case THURSDAY:
case SATURDAY:
result = "8";
break;
case WEDNESDAY:
result = "9";
break;
default:
throw new IllegalStateException("Invalid day: " + day);
}
// Java 14
String result = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> "6";
case TUESDAY -> "7";
case THURSDAY, SATURDAY -> "8";
case WEDNESDAY -> "9";
};
// 복잡한 로직도 가능
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> {
System.out.println("Processing: " + day);
yield 6;
}
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
Java 15 (2020)
Text Blocks
// Before Java 15
String html = "<html>\n" +
" <body>\n" +
" <h1>Hello World</h1>\n" +
" <p>This is a paragraph with \"quotes\"</p>\n" +
" </body>\n" +
"</html>";
String json = "{\n" +
" \"name\": \"John\",\n" +
" \"age\": 30,\n" +
" \"city\": \"New York\"\n" +
"}";
// Java 15
String html = """
<html>
<body>
<h1>Hello World</h1>
<p>This is a paragraph with "quotes"</p>
</body>
</html>
""";
String json = """
{
"name": "John",
"age": 30,
"city": "New York"
}
""";
// SQL 쿼리도 깔끔하게
String query = """
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
""";
Java 16 (2021)
Pattern Matching for instanceof
타입 체크 + 캐스팅 + 값 비교를 모두 한 번에
// Before Java 16
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.toUpperCase());
}
// Java 16
if (obj instanceof String str) {
System.out.println(str.toUpperCase());
}
// 더 복잡한 예시
public String formatValue(Object obj) {
if (obj instanceof Integer i && i > 10) {
return "Large number: " + i;
} else if (obj instanceof String s && !s.isEmpty()) {
return "String: " + s.toUpperCase();
} else if (obj instanceof Double d) {
return String.format("%.2f", d);
}
return "Unknown: " + obj;
}
Java 17 (2021) - LTS
Sealed Classes
// Java 17
public abstract sealed class Shape
permits Circle, Rectangle, Triangle {
// 오직 Circle, Rectangle, Triangle만 이 클래스를 상속할 수 있음!
public abstract double area();
}
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
public non-sealed class Triangle extends Shape {
// non-sealed이므로 다른 클래스가 이를 상속할 수 있음
private final double base, height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double area() {
return 0.5 * base * height;
}
}
// 사용 - 컴파일러가 모든 경우를 체크
public String describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle with area " + c.area();
case Rectangle r -> "Rectangle with area " + r.area();
case Triangle t -> "Triangle with area " + t.area();
// default 케이스 불필요 - 모든 경우를 다룸
};
}
Java 21 (2023) - LTS
1. Virtual Threads
// 기존 방식 - 플랫폼 스레드 (무겁고 제한적)
Thread thread = new Thread(() -> {
// 작업 수행
System.out.println("Platform thread: " + Thread.currentThread());
});
thread.start();
// Java 21 - Virtual Threads (가볍고 많이 생성 가능)
Thread virtualThread = Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread: " + Thread.currentThread());
});
// ExecutorService와 함께 사용
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 수백만 개의 virtual thread도 가능!
for (int i = 0; i < 1_000_000; i++) {
executor.submit(() -> {
// I/O 집약적인 작업
try {
Thread.sleep(1000);
System.out.println("Task completed on: " + Thread.currentThread());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
// HTTP 클라이언트에서 활용
HttpClient client = HttpClient.newBuilder()
.executor(Executors.newVirtualThreadPerTaskExecutor())
.build();
2. Pattern Matching for switch(정식)
// Java 21
public String processValue(Object obj) {
return switch (obj) {
case null -> "null value";
case Integer i when i > 0 -> "Positive integer: " + i;
case Integer i when i < 0 -> "Negative integer: " + i;
case Integer i -> "Zero";
case String s when s.length() > 10 -> "Long string: " + s.substring(0, 10) + "...";
case String s -> "Short string: " + s;
case Double d -> String.format("Double: %.2f", d);
case List<?> list when list.isEmpty() -> "Empty list";
case List<?> list -> "List with " + list.size() + " elements";
default -> "Unknown type: " + obj.getClass().getSimpleName();
};
}
3. Record Patterns
// Record 정의
public record Point(int x, int y) {}
public record ColoredPoint(Point point, String color) {}
// Java 21 - Record Patterns
public String describe(Object obj) {
return switch (obj) {
case Point(int x, int y) -> "Point at (" + x + ", " + y + ")";
case ColoredPoint(Point(int x, int y), String color) ->
"Colored point at (" + x + ", " + y + ") in " + color;
default -> "Unknown object";
};
}
// 네스티드 패턴도 가능
public boolean isOriginPoint(Object obj) {
return switch (obj) {
case Point(0, 0) -> true;
case ColoredPoint(Point(0, 0), String color) -> true;
default -> false;
};
}
Java 22-23 (2024-2025)
String Templates(Preview)
// Java 22+ (Preview)
String name = "World";
int value = 42;
// 기존 방식
String message1 = String.format("Hello %s! Value is %d", name, value);
String message2 = "Hello " + name + "! Value is " + value;
// String Templates
String message = STR."Hello \{name}! Value is \{value}";
// 포맷팅도 가능
double pi = Math.PI;
String formatted = STR."Pi is approximately \{pi %.2f}";
// HTML 생성
String html = STR."""
<html>
<body>
<h1>Hello \{name}</h1>
<p>Your score is \{value}/100</p>
</body>
</html>
""";
// JSON 생성
String json = STR."""
{
"name": "\{name}",
"score": \{value},
"passed": \{value >= 60}
}
""";
Unnamed Variables and Patterns
// Java 22+
// 사용하지 않는 변수를 _ 로 표시
try {
processData();
} catch (IOException _) {
// 예외 객체를 사용하지 않음
log.error("IO error occurred");
}
// 튜플에서 일부만 사용
var (name, _, age) = getPersonData(); // 중간 값은 무시
// for-each에서
for (var (key, _) : map.entrySet()) {
// value는 사용하지 않고 key만 사용
processKey(key);
}
정리 및 전망
Java의 진화 과정을 살펴보면 몇 가지 뚜렷한 트렌드를 발견할 수 있다.
코드 간소화
- Records: 50줄 데이터 클래스 → 1줄
- Text Blocks: 복잡한 문자열 연결 → 깔끔한 멀티라인
- var 키워드: 장황한 타입 선언 → 간결한 타입 추론
- Switch 표현식: 복잡한 분기 처리 → 표현력 있는 패턴 매칭
타입 안전성 강화
- Optional: null 체크 지옥 → 안전한 null 처리
- Sealed Classes: 예측 불가능한 상속 → 제한된 상속 계층
- Pattern Matching: 불안전한 캐스팅 → 컴파일 타임 안전성
성능 혁신
- Stream API: 명령형 → 선언적 데이터 처리
- Virtual Threads: 플랫폼 스레드의 한계 → 수백만 경량 스레드
- 새로운 GC: 더 나은 메모리 관리와 응답성
함수형 프로그래밍 도입
- Lambda: 익명 클래스 → 간결한 함수 표현
- Stream API: 반복문 → 함수형 데이터 파이프라인
- Pattern Matching: 조건문 → 선언적 패턴 기반 처리
새로운 기능들이 추가될수록 기존 Java 8에 머물러 있는 것은 더 큰 기술 부채가 될 수 있다.
하지만 변화가 두렵다면 무리할 필요는 없다.
새 프로젝트에서 Records 하나만 써보는 것부터 시작해도 된다.
Optional로 null 체크를 개선해보는 것도 좋은 출발점이다.
두려워 하지 말고 어서 빨리 시작하자!!
'언어 > Java' 카테고리의 다른 글
| Optional 바르게 사용하기 (0) | 2025.08.26 |
|---|---|
| JVM 메모리 구조 정리 (0) | 2025.08.16 |
| String, StringBuilder, StringBuffer 성능 차이 (0) | 2025.05.13 |
| Jackson의 @JsonProperty로 JSON 키와 Java 필드 매핑하기 (0) | 2025.04.09 |
| JDBC란? (0) | 2025.03.22 |
