자바

[자바] Java 17 버전 특징과 주요 변경 사항

kyjdummy 2025. 5. 8. 21:45

[ 자바 17의 지원 일자 ]

  • 무료 지원 일자 : 2021-09 ~ 2026-09
  • 유료지원 일자 : 2031-09

[ 호환 스프링 부트 ]

  • Spring Boot 호환 : 2.6.x ~ 3.x
  • 안정 적인 버전 :  자바 17은 3.1.x(5)가 안정적

[ 가비지 컬렉터 ]

  • 자바 17에서는 G1 가비지 컬렉터와 ZGC(Z Garbage Collector)가 개선되었습니다.

[ 특징 및 주요 변경 사항 ]

 

1. 봉인 클래스(Sealed Classes)

  • 자바 17에서는 상속을 제한할 수 있는 봉인 클래스가 정식으로 도입되었습니다. 기존 자바에서는 클래스 상속을 제한하는 방법이 제한적이었습니다. final 키워드 사용 시 상속 자체를 완전히 막을 수 있고, 접근 제어자로 일부 제한은 가능했으나 “특정 클래스만 상속 가능하도록” 하는 명시적 제한이 없었습니다.
  • 사용 시 주의할 점
    • 봉인 클래스와 허용된 서브 클래스는 같은 모듈 또는 패키지에 있어야 합니다.
    • 허용된 서브 클래스는 반드시 상위 클래스를 상속해야 합니다.
    • 각 서브 클래스는 final, sealed, non-sealed 중 하나로 선언돼야 합니다.
// sealed 키워드를 사용하여 상속 가능한 클래스를 명시적으로 제한
public sealed class Shape permits Circle, Rectangle, Triangle {
    // 공통 메소드 및 필드
}

// 허용된 서브클래스는 final, sealed, non-sealed 중 하나여야 함
public final class Circle extends Shape {
    private final double radius;
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    // Circle 구현
}

// 추가 상속을 허용하고 싶다면 non-sealed 사용
public non-sealed class Triangle extends Shape {
    private final double a, b, c;
    
    public Triangle(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    
    // Triangle 구현
}

// Triangle은 non-sealed이므로 추가 상속 가능
public class EquilateralTriangle extends Triangle {
    public EquilateralTriangle(double side) {
        super(side, side, side);
    }
}

 

 


2. 레코드(Records) 기능

  • 레코드는 불변 데이터 객체를 간결하게 선언할 수 있는 기능입니다. 기존에는 불변 데이터 클래스(DTO)를 만들 때, getter, setter, equals 등 메소드등을 모두 작성해야 했으나, 레코드는 이러한 반복적 코드를 제거하고 데이터를 더 간결하게 표현할 수 있게 합니다.
  • 사용 시 주의사항
    • 레코드는 암시적으로 final 클래스입니다. 상속이 불가합니다.
    • 모든 필드는 private final로 선언됩니다.
    • 레코드는 다른 클래스를 상속할 수 없으나, 인터페이스 구현은 가능합니다.
    • 각 필드에 대한 접근자는 필드명과 동일한 이름을 가집니다(ex name())

 

// 레코드를 사용한 불변 DTO - 모든 상용구 코드가 자동 생성됨
public record Person(String name, int age) {
    // 컴팩트 생성자로 유효성 검사 추가 가능
    public Person {
        if (age < 0) {
            throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.");
        }
    }
    
    // 추가 메소드도 정의 가능
    public boolean isAdult() {
        return age >= 18;
    }
}

// 레코드 인스턴스 생성
Person person = new Person("홍길동", 30);

// 자동 생성된 접근자 사용 (getName() 대신 name() 사용)
System.out.println(person.name()); // 출력: 홍길동
System.out.println(person.age());  // 출력: 30

// 자동 생성된 toString()
System.out.println(person); // 출력: Person[name=홍길동, age=30]

// 자동 생성된 equals()와 hashCode()
Person anotherPerson = new Person("홍길동", 30);
System.out.println(person.equals(anotherPerson)); // 출력: true

// 레코드를 Map의 키로 사용
Map<Person, String> personMap = new HashMap<>();
personMap.put(person, "정보");
System.out.println(personMap.get(anotherPerson)); // 출력: 정보

3. 패턴 매칭 for instanceof

  • 기존의 instanceof 사용 후 타입 캐스팅은 반복적이고 오류 발생이 쉬었으나, 패턴 매칭은 이 과정을 단순화하여 코드 가독성과 안전성을 향상시킵니다.
// 기존 방식
public String getTypeInfo(Object obj) {
    if (obj instanceof String) {
        String s = (String) obj;
        return "문자열 길이: " + s.length();
    } else if (obj instanceof Integer) {
        Integer i = (Integer) obj;
        return "정수 값: " + i;
    } else {
        return "알 수 없는 타입";
    }
}

// 패턴 매칭을 사용한 방식
public String getTypeInfoWithPatternMatching(Object obj) {
// instanceof String s 는 객체가 String 타입인지 확인하고 맞다면 바로 변수 s에
// 값을 할당하여 코드를 더 간결하고 실수를 줄입니다.
    if (obj instanceof String s) {
        return "문자열 길이: " + s.length();
    } else if (obj instanceof Integer i) {
        return "정수 값: " + i;
    } else {
        return "알 수 없는 타입";
    }
}

4. 텍스트 블록(Text Blocks)

  • 자바 15에서 정식 추가되었고 17에서도 사용 가능한 텍스트 블록은 여러 줄의 문자열을 쉽게 작성할 수 있게 합니다. 기존에는 각 줄마다 문자열을 분리하고 연결 연산자(+)를 사용해야 했으나 텍스트 블록은 삼중 큰따옴표(”””)로 시작하고 끝납니다.
// 기존 방식
String json = "{\n" +
              "  \"name\": \"홍길동\",\n" +
              "  \"age\": 30,\n" +
              "  \"address\": {\n" +
              "    \"city\": \"서울\",\n" +
              "    \"zipcode\": \"12345\"\n" +
              "  }\n" +
              "}";

// 텍스트 블록 사용
String jsonTextBlock = """
        {
          "name": "홍길동",
          "age": 30,
          "address": {
            "city": "서울",
            "zipcode": "12345"
          }
        }
        """;

// HTML 예제
String html = """
        <html>
            <body>
                <h1>Java 17 특징</h1>
                <ul>
                    <li>봉인 클래스</li>
                    <li>레코드</li>
                    <li>패턴 매칭</li>
                    <li>텍스트 블록</li>
                </ul>
            </body>
        </html>
        """;

 

5. 스위치 표현식 추가

  • 자바 14에서 정식 추가되었고, 17에서도 사용 가능한 스위치 표현식은 기존의 switch 문을 더 강력하고 간결하게 만듭니다. 기존 switch 문은 fall-throught 동작(break 없이 다음 case로 넘어가는 것)으로 인해 버그가 발생하기 쉬웠습니다. 스위치 표현식은 이런 문제를 해결하고, 값을 직접 반환할 수 있게 합니다.
  • 주의사항
    • 화살표(→) 구문을 사용하면 자동으로 다음 case로 넘어가지 않고 리턴됩니다.
    • 스위치 전체가 표현식으로 사용되어 값을 직접 반환할 수 있습니다.
    • 복잡한 블록에서는 yield 키워드를 사용해 값 반환이 가능합니다.
    • 여러 레이블을 쉼표로 구분해 하나의 case에 묶을 수 있습니다.
    • 열거형 사용 시 모든 가능한 입력 값을 처리하지 않으면 컴파일 오류 납니다
// 기존 방식
public String getDayType(DayOfWeek day) {
    String result;
    switch (day) {
        case MONDAY:
        case TUESDAY:
        case WEDNESDAY:
        case THURSDAY:
        case FRIDAY:
            result = "평일";
            break;
        case SATURDAY:
        case SUNDAY:
            result = "주말";
            break;
        default:
            result = "알 수 없음";
            break;
    }
    return result;
}

// 스위치 표현식 사용 - 화살표와 콜론 구문 혼합 가능
public String getDayTypeWithSwitchExpression(DayOfWeek day) {
    return switch (day) {
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "평일";
        case SATURDAY, SUNDAY -> "주말";
        default -> {
            System.out.println("알 수 없는 요일: " + day);
            yield "알 수 없음";  // yield 키워드로 값 반환
        }
    };
}

// 열거형과 함께 사용
enum Size { SMALL, MEDIUM, LARGE }

public int getDiscount(Size size) {
    return switch (size) {
        case SMALL -> 5;
        case MEDIUM -> 10;
        case LARGE -> 15;
    };  // 열거형의 모든 경우가 처리되므로 default 불필요
}

 


6. 강화된 의사 난수 생성기

  • 기존 Random 클래스는 알고리즘이 다양하지 않고, 확장이 제한되었으나, 자바 17에서는 이것이 개선되었습니다.
import java.util.random.RandomGenerator;
import java.util.random.RandomGeneratorFactory;

public class RandomGeneratorExample {
    public static void main(String[] args) {
        // 기본 난수 생성기 사용
        RandomGenerator defaultRandom = RandomGenerator.getDefault();
        System.out.println("기본 난수: " + defaultRandom.nextInt(100));
        
        // 특정 알고리즘의 난수 생성기 사용
        RandomGenerator xoroshiro = RandomGeneratorFactory.of("Xoroshiro128PlusPlus")
                                                        .create(42);  // 시드 값 지정
        System.out.println("Xoroshiro128PlusPlus 난수: " + xoroshiro.nextInt(100));
        
        // 사용 가능한 모든 알고리즘 출력
        System.out.println("사용 가능한 알고리즘:");
        RandomGeneratorFactory.all()
                             .map(factory -> factory.name())
                             .sorted()
                             .forEach(System.out::println);
    }
}

7. 컴팩트 넘버 포맷팅

  • 자바 12에 도입된 기능으로 17에서도 사용 가능한 기능으로, 큰 숫자를 읽기 쉬운 형태로 원하는 대로 포맷팅이 가능합니다. 큰 숫자(백만, 천억)를 표시할 때 더 읽기 쉽게 만들기 위해 추가되었습니다.
import java.text.NumberFormat;
import java.util.Locale;

public class CompactNumberFormattingExample {
    public static void main(String[] args) {
        NumberFormat formatter = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
        System.out.println(formatter.format(1000));       // 출력: 1K
        System.out.println(formatter.format(1000000));    // 출력: 1M
        System.out.println(formatter.format(1000000000)); // 출력: 1B
        
        // 한국어 로케일 사용
        formatter = NumberFormat.getCompactNumberInstance(Locale.KOREA, NumberFormat.Style.SHORT);
        System.out.println(formatter.format(1000));       // 출력: 1천
        System.out.println(formatter.format(1000000));    // 출력: 100만
        System.out.println(formatter.format(1000000000)); // 출력: 10억
        
        // LONG 스타일 사용
        formatter = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
        System.out.println(formatter.format(1000));       // 출력: 1 thousand
        System.out.println(formatter.format(1000000));    // 출력: 1 million
        
        // 소수점 설정
        formatter.setMaximumFractionDigits(1);
        System.out.println(formatter.format(1234567));    // 출력: 1.2 million
    }
}

 

 


8. Stream.toList() 간소화

  • 자바 16에서 추가된 기능으로, Stream을 List로 변환하는 작업이 간소화되었습니다.
// 기존 방식
List<String> list = stream.collect(Collectors.toList());

// 새로운 방식
List<String> list = stream.toList();