[Java 21] 이름 없는 패턴과 변수 Unnamed Patterns and Variables (Preview)

yammmie·2024년 1월 8일

Java

목록 보기
4/9
post-thumbnail

개요

  • 부작용이 유일한 관심사일 때, 보일러플레이트 코드를 줄일 수 있게 해줌
  • 이름 없는 패턴은 Java 19의 레코드 패턴과 스위치의 패턴 매칭에 비해 개선됨
  • Java 14에서 미리보기로 도입된 레코드 기능에 익숙해져야 함

목적

  • 복잡한 객체를 다룰 때, 그 객체가 항상 갖고 있는 모든 데이터를 필요로 하지 않음 → 주어진 것 중 일부분만 사용
    • 이러한 예는 단일 책임 원칙(SRP)로 입증되는 OOP 전반에서 찾을 수 있음
    • 이름 없는 패턴과 변수는 자바가 최근에 시도한 더 낮은 스케일의 문제 해결

활성화

  • 미리보기 기능이기 때문에 활성화해야 함
  • Maven에서 다음을 포함하도록 컴파일러 플러그인 구성 수정
<compilerArgs>
    <arg>--enable-preview</arg>
</compilerArgs>

이름 없는 변수

  • 자바에서는 처음이지만 파이썬과 Go 같은 다른 언어에서 이미 사용되고 있음
  • 이름 없는 변수는 함수의 부작용에만 신경 쓸 때 사용됨
  • 필요한 만큼 정의할 수 있지만 나중에는 참조할 수 없음

향상된 for문

  • 간단한 Car 레코드
public record Car(String name) {}
  • 모든 Car를 계산하고 다른 비즈니스 로직을 위해 Car 컬렉션을 반복해야 함
for (var car : cars) {
    total++;
    if (total > limit) {
        // side effect
    }
}
  • Car 컬렉션의 모든 요소를 살펴봐야 하지만, 그것을 사용할 필요는 없음
  • 변수의 이름을 지정하면 코드를 읽기가 어려워지므로 이름 없는 변수 사용
for (**var _** : cars) {
    total++;
    if (total > limit) {
        // side effect
    }
}
  • 이것은 maintainer에게 Car가 사용되지 않는다는 것을 분명히 함
  • 물론 이것은 일반적인 for문과 함께 사용할 수 있음
  • 유의: sendOneTimeNotification()은 한 번만 호출됨
    • 또한 첫 번째 초기화와 같은 타입을 리턴해야 함
    • 우리의 경우 i
for (**int i = 0, _** = sendOneTimeNotification(); i < cars.size(); i++) {
    // Notify car
}

할당문

  • 함수의 부작용과 일부 리턴 값이 모두 필요할 때 가장 유용함
  • 세 가지 요소를 제거하고 첫 번째 요소만 리턴하는 방법이 필요한 경우
  • 같은 블록에서 여러 개의 _ 사용 가능
static Car removeThreeCarsAndReturnFirstRemoved(Queue<Car> cars) {
    var car = cars.poll();
    **var _** = cars.poll();
    **var _** = cars.poll();
    return car;
}

try-catch

  • catch문에서 유용하게 사용됨
  • 예외가 무엇을 포함하고 있는지 여러 번 작성할 필요 없이 예외를 처리하고 싶은 경우
try {
    someOperationThatFails(car);
} catch (**IllegalStateException _**) {
    System.out.println("Got an illegal state exception for: " + car.name());
} catch (**RuntimeException _**) {
    System.out.println("Got a runtime exception!");
}
  • 같은 catch에서 여러 예외 유형에 대해 작동함
catch (**IllegalStateException | NumberFormatException _**) { }

try-with-resources

  • try-catch보다는 적지만, 유용하게 사용됨
  • 예를 들어 DB로 작업 시, 보통 트랜잭션 객체가 필요하지 않음
class Transaction implements AutoCloseable {

    @Override
    public void close() {
        System.out.println("Closed!");
    }
}
static void obtainTransactionAndUpdateCar(Car car) {
    try (**var _ = new Transaction()**) {
        updateCar(car);
    }
}
try (**var _ = new Transaction(); var _ = new FileInputStream("/some/file")**)

람다 매개 변수

  • 람다 함수는 본질적으로 코드를 재사용하는 좋은 방법을 제공함
  • 좋은 예는 Map 인터페이스의 computeIfAbsent() 메소드
    • Map에 값이 있는지 확인하거나 함수를 기반으로 새로운 값을 계산함
  • 보통 람다의 매개 변수가 필요하지 않음
static Map<String, List<Car>> getCarsByFirstLetter(List<Car> cars) {
    Map<String, List<Car>> carMap = new HashMap<>();
    cars.forEach(car ->
        carMap.computeIfAbsent(car.name().substring(0, 1), _ -> new ArrayList<>()).add(car)
    );
    return carMap;
}
map.forEach(**(_, _)** -> System.out.println("Works!"));

이름 없는 패턴

  • 향상된 레코드 패턴 매칭
  • 분명한 문제를 해결함: 해체하는 레코드의 모든 필드를 필요로 하지 않음
abstract class Engine { }

class GasEngine extends Engine { }
class ElectricEngine extends Engine { }
class HybridEngine extends Engine { }
  • Engine 타입에 따라 재사용할 수 있도록 매개 변수화된 타입을 지원하도록 Car를 확장
  • color라는 새로운 필드 추가
public record Car<T extends Engine>(String name, String color, T engine) { }

instanceof

  • 패턴으로 레코드를 해체할 때, 이름 없는 패턴은 필요하지 않은 필드를 무시할 수 있게 해줌
  • 얻은 객체가 Car일 때, 그것의 색상을 얻고 싶은 경우
static String getObjectsColor(Object object) {
    if (object instanceof **Car(String name, String color, Engine engine)**) {
        return color;
    }
    return "No color!";
}
  • 필요하지 않은 변수도 함께 정의되어 있음
  • 이를 이름 없는 패턴으로 개선
static String getObjectsColorWithUnnamedPattern(Object object) {
    if (object instanceof **Car(_, String color, _)**) {
        return color;
    }
    return "No color!";
}
  • 간단한 instanceof 정의에도 사용되지만 그닥 유용하지는 않은 경우
if (car instanceof **Car<?> _**) { }

switch 패턴

  • switch 패턴으로 해체하면 필드를 무시할 수 있음
static String getObjectsColorWithSwitchAndUnnamedPattern(Object object) {
    return switch (object) {
        case **Car(_, String color, _)** -> color;
        default -> "No color!";
    };
}
  • 매개 변수화된 경우도 사용 가능
    • switch문에서 Engine 타입 처리
return switch (car) {
    case **Car(_, _, GasEngine _)** -> "gas";
    case **Car(_, _, ElectricEngine _)** -> "electric";
    case **Car(_, _, HybridEngine _)** -> "hybrid";
    default -> "none";
};
  • case를 가드와 페어링하기
return switch (car) {
    case Car(_, _, GasEngine _), Car(_, _, ElectricEngine _) when someVariable == someValue -> "not hybrid";
    case Car(_, _, HybridEngine _) -> "hybrid";
    default -> "none";
};

결론

  • 이름 없는 패턴과 변수는 단일 책임 원칙을 다루는 훌륭한 추가 사항
  • 자바 8 이전 버전의 경우 획기적인 변화이지만, 이후 버전의 경우 변수 이름 지정이 허용되지 않으므로 영향 받지 않음
  • 보일러플레이트 코드를 줄이고 가독성을 향상시키는 기능



부작용 Side effect

  • = 의도하지 않은 부수적인 효과
  • 어떤 함수가 어떤 일을 수행하기 위해 매개 변수 외부의 어떤 것에 의존하거나 수정하는 것을 의미
  • 예를 들어 자신의 인수, 데이터베이스, 파일 또는 콘솔 외부의 변수에서 읽거나 쓰는 함수는 부작용이 있다고 설명할 수 있음

Statement

  • 프로그램 내에서 하나의 동작을 기술하는 것
  • 러프하게 보면 코드를 구성하는 하나하나의 문장
    • 가독성을 위해 여러 줄로 나눈 문장 말고 세미콜론으로 끝나는 완전한 문장

참고

https://www.baeldung.com/java-unnamed-patterns-variables

https://www.inflearn.com/questions/12660/side-effect에-대해서-조금-더-자세히-설명해주실-수-있으신가요

https://next-block.tistory.com/entry/Side-Effect-부수효과

0개의 댓글