각 자바 버전들 릴리스 이후 언어와 플랫폼에 추가된 새로운 기능들을 정리해보고자 한다.
각 버전에 모든 문법들은 정리하는 것을 목적으로 하는 문서는 아니고, 알아두면 좋을 것들을 기록해보고자 한다.
Java 8
함수형 프로그래밍
Java 8에서는 함수형 프로그래밍을 지원하기 위해 여러 기능이 추가되었다. 이로 인해 더 간결하고 효율적인 코드를 작성할 수 있다.
특히 아래에서 소개할 람다식과 함께 사용해서 더 이상 익명 클래스를 사용하지 않아도 되게 되었다.
import java.util.Arrays;
import java.util.List;
public class FunctionalProgrammingExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names
.forEach(name -> System.out.println(name));
}
}
람다식
람다식은 익명 함수로, 코드를 간결하게 작성할 수 있도록 돕는다. 주로 컬렉션 API와 함께 사용된다. 과거에는 익명 클래스를 통해서 메서드 혹은 함수를 일급으로 전달했었다. 람다식이 등장한 이후 코드가 훨씬 간결하게 처리되었다.
import java.util.Arrays;
import java.util.List;
//과거
public class TraditionalLoopExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 전통적인 for 루프 사용
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
System.out.println(name);
}
}
}
// 현재
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
}
}
Stream API
Stream API는 컬렉션에 대한 함수형 스타일의 연산을 지원한다. 데이터의 연속적인 처리에 유용하다. 과거에 비해서 Imperative 했던 코드들이 Declaritive 하게 변경되어 가시성이 매우 좋게 확보되었다. Java 8 이상의 스펙이 선호되는 이유는 여러가지 이유가 있겠지만 Stream 의 등장이 주가 되었다고 해도 무방하다고 개인적으로 생각한다.
장점은 다음과 같다.
- 가독성 향상: 코드가 간결하고 명확해서 데이터 처리의 의도를 쉽게 이해할 수 있다
- 함수형 프로그래밍: 람다식을 사용하여 데이터 처리 로직을 함수형 프로그래밍 스타일로 작성할 수 있어서 코드 재사용성과 유지보수성이 높아진다
- 지연 실행: Stream은 필요할 때만 데이터를 처리하기 때문에 성능을 최적화할 수 있다
- 병렬 처리: Stream API를 활용하면 데이터의 병렬 처리를 쉽게 적용할 수 있어 성능 향상이 가능하다
- 다양한 연산 지원: 필터, 변환, 정렬 등 다양한 연산을 체계적으로 연결하여 복잡한 데이터 처리를 쉽게 구현할 수 있다
import java.util.Arrays;
import java.util.List;
// 과거 버전
public class TraditionalLoopExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 전통적인 for 루프와 if 조건문 사용
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (name.startsWith("A")) {
System.out.println(name);
}
}
}
}
// stream 의 등장
public class StreamExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
}
}
Optional
Optional 클래스는 null값 처리를 위한 컨테이너이다. 이로 인해 Null Pointer Exception을 방지할 수 있다. 많은 개발자들이 과거에는 null 값 체크하는 것을 validation 의 기본으로 생각하곤 했는데, 덕분에 많이 개선되었다.
Optional 에서 제공하는 유명한 API 들은 아래 예제 코드에 추가해두었다.
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
// 1. of(T value)
Optional<String> name1 = Optional.of("Alice"); // null이 아닌 값으로 Optional 생성
// 2. ofNullable(T value)
Optional<String> name2 = Optional.ofNullable(null); // null인 값으로 빈 Optional 생성
// 3. isPresent()
boolean hasValue = name1.isPresent(); // name1이 존재하는지 확인
// 4. ifPresent(Consumer<? super T> action)
name1.ifPresent(n -> System.out.println(n)); // 값이 존재하면 출력, "Alice" 출력
// 5. orElse(T other)
String result1 = name2.orElse("Default Name"); // name2가 빈 Optional일때 "Default Name" 반환
// 6. orElseGet(Supplier<? extends T> other)
String result2 = name2.orElseGet(() -> "Default Name"); // 공급자를 사용하여 기본값 반환
// 7. orElseThrow(Supplier<? extends X> exceptionSupplier)
try {
String result3 = name2.orElseThrow(() -> new IllegalArgumentException("Name is absent")); // 빈 Optional에서 예외 발생
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // "Name is absent" 출력
}
// 8. map(Function<? super T, ? extends U> mapper)
Optional<Integer> nameLength = name1.map(String::length); // "Alice"의 길이인 5 반환
// 9. flatMap(Function<? super T, Optional<U>> mapper)
Optional<String> upperName = name1.flatMap(n -> Optional.of(n.toUpperCase())); // "ALICE" 반환
// 10. filter(Predicate<? super T> predicate)
Optional<String> filteredName = name1.filter(n -> n.startsWith("A")); // "Alice" 반환
}
}
Date & Time API
Date와 Time API가 새롭게 도입되었다. 이 API는 날짜와 시간의 처리를 보다 간편하게 관리할 수 있도록 돕는다.
import java.time.LocalDate;
public class DateTimeExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
System.out.println(today);
}
}
Java 11
단일 소스파일 실행
Java 11에서는 단일 소스파일을 실행할 수 있는 기능이 추가되었다. 이로 인해 별도의 컴파일 과정 없이 바로 실행 가능하다.
정리하면 아래와 같다.
- 컴파일 과정: Java 11 이전에는 소스파일을 컴파일하여 클래스 파일을 생성해야 했지만, Java 11 이후에는 단일 소스파일을 직접 실행할 수 있다
- 사용자 편의성: Java 11부터는 하나의 명령어로 실행이 가능하므로 개발자 경험이 향상되었으며 이를 통해 특히 간단한 프로그램이나 스크립트 작성 시 시간과 노력을 절약할 수 있다
// 이전
$ javac HelloWorld.class
$ java HelloWorld
// 이후
$ java HelloWorld.java
Java 14 이후
Text block
Java 14에서 도입된 Text Block은 여러 줄의 문자열을 간편하게 작성할 수 있다.
public class TextBlockExample {
public static void main(String[] args) {
String textBlock = """
Hello,
World!
""";
System.out.println(textBlock);
}
}
Switch Expression
Switch Expression은 switch 문을 표현식으로 사용할 수 있게 해준다. 이를 통해 변수를 선언할 수 있다. 과거에는 선언한 변수에서 switch 문을 통해 값을 할당해주는 방식으로 코드를 작성했는데, 이제는 switch 를 바로 변수에 할당해줄 수 있다.
만일 이 차이에 대해 이해하지 못한다고 하면 "값, 식, 문" 에 대해서 이해해보자.
public class SwitchExpressionExample {
public static void main(String[] args) {
String day = "MONDAY";
int dayNumber = switch(day) {
case "MONDAY" -> 1;
case "TUESDAY" -> 2;
default -> 0;
};
System.out.println(dayNumber);
}
}
Record
Record는 데이터를 간편하게 표현할 수 있는 구조체 같은 역할을 한다. 주로 불변 객체를 생성하는 데 사용된다.
Record 클래스의 특징
- 간결한 구문: 클래스를 정의할 때 필요한 코드가 상당히 줄어든다
- 불변성: Record 객체는 기본적으로 불변으로 처리된다. 즉, 생성 후에 필드 값을 변경할 수 없다
- 자동 생성되는 메서드: equals(), hashCode(), toString() 메서드가 자동으로 생성된다
- 구조화된 데이터 표현: 주로 데이터 전송 객체(DTO)와 같이 여러 속성을 포함하는 경우에 바로 사용할 수 있다
public record Person(String name, int age) {}
public class RecordExample {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
System.out.println(person.name());
}
}
sealed 타입
Java 17에서 도입된 Sealed 클래스는 클래스의 상속을 제한할 수 있는 기능을 제공한다. 이를 통해 특정 클래스만 서브클래스로 정의할 수 있게 하여, 더욱 안전하고 예측 가능한 코드 구조를 형성할 수 있다.
Sealed 클래스의 특징
- 상속 제한: Sealed 클래스를 어떻게 상속할 수 있는지를 명시적으로 정의할 수 있다
- 유지 보수 용이: 클래스 구조를 명확히 하여 코드의 가독성과 유지보수성을 향상시킨다
- 유형 안전성: 서브클래스가 제한되어 있기 때문에, switch 문과 같은 구조에서 타입 오류를 방지할 수 있다
특히 switch 문과 함께 사용하면 효과적으로 사용할 수 있는게 컴파일 타임에 구현체를 전부 알고 있기 때문이다.
// Sealed 클래스 정의
sealed class Shape permits Circle, Square, Rectangle {
}
// Circle 서브클래스 정의
final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
public double area() {
return Math.PI * radius * radius;
}
}
// Square 서브클래스 정의
final class Square extends Shape {
private final double side;
public Square(double side) {
this.side = side;
}
public double area() {
return side * side;
}
}
// Rectangle 서브클래스 정의
final class Rectangle extends Shape {
private final double width;
private final double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double area() {
return width * height;
}
}
public class SealedClassExample {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape square = new Square(4);
Shape rectangle = new Rectangle(3, 6);
System.out.println("Circle Area: " + circle.area()); // 원의 면적 출력
System.out.println("Square Area: " + square.area()); // 정사각형의 면적 출력
System.out.println("Rectangle Area: " + rectangle.area()); // 직사각형의 면적 출력
}
}
// switch 와 함께 응용하면 default 를 설정해주지 않아도 된다
public static void printShape(Shape shape) {
switch (shape) {
case Circle c -> System.out.println("Circle with radius: " + c.radius);
case Square s -> System.out.println("Square with side: " + s.side);
case Rectangle r -> System.out.println("Rectangle with width: " + r.width + " and height: " + r.height);
}
}
// Sealed 인터페이스 정의
sealed interface Vehicle {
}
// Car 서브클래스 정의
final class Car implements Vehicle {
private final String model;
public Car(String model) {
this.model = model;
}
public String getModel() {
return model;
}
}
// Bike 서브클래스 정의
final class Bike implements Vehicle {
private final String type;
public Bike(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
'Java' 카테고리의 다른 글
Java 에서 다형성과 객체지향 프로그래밍 (1) | 2025.01.29 |
---|---|
Java 에서 상속 (0) | 2025.01.29 |
프로젝트, 코드 품질 관리에 대해서 (0) | 2025.01.26 |
Java 메모리 영역과 Overflow (0) | 2025.01.23 |
Modern Java 란 무엇일까? (1) | 2025.01.16 |