ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java Features
    카테고리 없음 2023. 4. 23. 14:06
    요즘들어 Kotlin만 사용해서 Java의 새로운 문법? 기능들에 관심이 없었다. 그래서 오늘은 자바11~17까지의 문법적인 변화에 대해 알아보도록 하자. 대부분 문법적인 부분들만 살펴볼 예정이니 자세한 내용들은 해당 공식문서를 살펴보면 좋겠다.

    Local-Variable Syntax for Lambda Parameters

    Java 10부터 지역변수에 var 키워드를 통해 타입추론을 할 수 있었다. 그런데 Lambda 표현식의 변수엔 사용 불가 했다. 하지만 이제는 Lambda expression에서도 변수에 var 키워드를 사용가능하다.
    Stream.iterate(1, (var number) -> number + 1).limit(10).collect(Collectors.toList())
    
    만약 변수가 2개이상일 경우엔 혼합해서 사용하면 안된다.
    BiConsumer<Integer, Integer> foo = (var b1, Integer b2) -> {};
    
    해당 코드는 올바르지 않다.

    http client

    java.net 의 HttpClient가 java 11부터 정식으로 표준화 되었다. java9에서 인큐베이팅 된 후 두번의 업그레이드 후 출시 되었다. 기존의 HttpURLConnection들은 사용하기 어렵고 사용자 친화적이지 않다. 그래서 다른 써드 라이브러리들을 많이 사용해왔다. 그러나 이제는 조금 편리하고 사용자 친화적인 HttpClient를 사용하면 된다.
    public static void main(String[] args) throws Exception {
    
        var httpClient = HttpClient.newBuilder()
                .build();
        var httpRequest = HttpRequest.newBuilder()
                .GET()
                .uri(URI.create("https://httpstat.us/200"))
                .build();
        var httpResponse = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        System.out.println(httpResponse.body());
    
    }
    
    근데 사용할 일이 있나 모르겠다.

    Pattern Matching for instanceof

    java 14부터 진행되고 있는 instanceof Pattern Matching 이다.

    if (records instanceof Records) { var r = (Records) records; // use r }
    이전에는 위와 같이 instanceof로 비교한 후 해당 타입으로 캐스팅을 했다. 어쩌면 불필요한 코드였을지도 모른다. 그러나 이제는 그럴필요 없다.
    if (records instanceof Records r) {
        // use r 
    } else {
        // cant use r
    }
    
    이제는 해당 타입의 캐스팅하지 않고 바로 사용할 수 있다. 만약 해당 조건에 만족하지 않으면 사용할 수 없다.
    if (!(records instanceof Records r)) {
        // cant use r
    } else {
        // use r 
    }
    
    해당 변수로 조건문을 추가 할 수도 있다.
    if (records instanceof Records r && r.name.length() > 10) {
      // use r
    }
    
    편리한 기능이 추가 되었다.

    Switch Expressions and Pattern Matching

    java 12 부터 Switch Expressions을 사용할 수 있고 java17 부터 Pattern Matching을 사용할 수 있다. 이전에 switch문은 실수의 여지도 크며 불필요한 코드가 많았다.
    public static void getTransportPrint(Transport transport) {
    
            switch (transport) {
                case BUS:
                    System.out.println("take a bus");
                    break;
                case TAXI:
                    System.out.println("take a taxi");
                    break;
                case SUBWAY:
                    System.out.println("take a subway");
                    break;
                default:
                    System.out.println("walking");
                    break;
    
            }
        }
    
    break키웓드를 실수로 넣지 않으면 버그가 나오기 싶다. 또한 break키워드는 불필요한 코드일지도 모른다. 이제는 간단한 표현식으로 바꿀수도 있다.
    public static void getTransportPrint(Transport transport) {
    
        switch (transport) {
            case BUS -> System.out.println("take a bus");
            case TAXI -> System.out.println("take a taxi");
            case SUBWAY -> System.out.println("take a subway");
            default -> System.out.println("walking");
        }
    }
    
    실수의 여지도 적으며 코드도 간결해졌다. 물론 case에 여러 조건도 가능하다.
    switch (transport) {
        case BUS, TAXI -> System.out.println("take a bus and taxi");
        case SUBWAY -> System.out.println("take a subway");
        default -> System.out.println("walking");
    }
    
    표현식이라 리턴도 받을 수 있다.
    public static int getTransportNumber(Transport transport) {
        return switch (transport) {
            case BUS, TAXI -> 1;
            case SUBWAY -> 2;
        };
    }
    
    추가적으로 yield 키워드도 추가되었다. case문에 추가적인 코드가 들어갈때 사용하면 된다.
    public static int getTransportOrdinal(Transport transport) {
        return switch (transport) {
            case BUS -> 0;
            case TAXI -> {
                var ordinal = transport.ordinal();
                // bla bla            
                yield 1200;
            }
            case SUBWAY -> {
                // bla bla
                yield 8000;
            }
        };
    }
    
    이전에는 타입체크를 할 경우 if문과 instanceof를 사용해 체크를 했다.
    public static double getTypeNumberSwitch(Object o) {
        var result = 0d;
        if (o instanceof Number) {
            var i = (Integer) o;
            result = i.doubleValue();
        } else if (o instanceof String) {
            var s = (String) o;
            result = Double.parseDouble(s);
        }
        return result;
    }
    
    불필요한 코드들도 많고 result에 계속 어싸인을 하고 있어 실수의 여지도 크다. 이제는 그럴 필요 없다.
    public static double getTypeNumberSwitch(Object o) {
        return switch (o) {
            case Number i -> i.doubleValue();
            case String s -> Double.parseDouble(s);
            default -> 0;
        };
    }
    
    간단하면서 한눈에 알아 볼수 있는 코드가 되었다.

    Text Blocks

    java 13 부터 나온 기능이다. 다른언어는 진작에 있었긴 한데 그래도 나오니 한결 나은거 같다. 멀티 라인의 string을 손쉽게 작성할 수 있다. 이전에는 멀티라인의 string 컨트롤하기 힘들었다. 예를들어 json을 예쁘게 만들고 싶다면 아래와 같이 해야 했다.
    final var text = "{\
    " +
            "\\t\\"name\\" : \\"wonwoo\\",\
    " +
            "\\t\\"address\\": \\"Carson, CA, 90746\\"\
    " +
            "}";
    System.out.println(text);
    
    보기만 해도 힘들다. 만들긴 더 힘들다. 그러나 이제는 손쉽게 만들수 있다.
    final var text = """
            {
                "name" : "wonwoo",
                "address": "Carson, CA, 90746"
            }
            """;
    
    마지막 행으로 공백이 결정되므로 마지막행의 위치가 중요하다.
    final var text = """
                {
                    "name" : "wonwoo",
                    "address": "Carson, CA, 90746"
                }
            """;
    
    위와 같이 작성했다면 아래와 같이 출력된다.
    |    {
    |        "name" : "wonwoo",
    |        "address": "Carson, CA, 90746"
    |    }
    
    | 해당 표시로 여백을 표시하였다. 만약 text block 에는 여러줄을 표시하고 한줄로 출력이 되게 하고 싶다면 \\(역슬래스)를 추가적으로 넣으면 된다.
    final var text = """
            Lorem ipsum dolor sit amet, consectetur adipiscing \\
            elit, sed do eiusmod tempor incididunt ut labore \\
            et dolore magna aliqua.\\
            """;
    
    출력시에는 한줄로 표기 된다. 여러 이스케이프문자들이 있는데 이건 문서를 찾아보자! 아쉬운게 하나 있는데 interpolation이 없다. 잠깐 보기에는 향후에 추가 될 수도 있다고 하니 기다려보자. 아쉬운대로 몇가지 방법으로 해결 할 수 있다. replace, String.format와 text block을 지원하기 위해 추가된 String.formatted을 사용하면 된다. replace와 String.format은 이전부터 있었으니 생략하고
    var text = """
            red
            green
            blue
            %s
            """.formatted("white");
    
    내부적으로는 String.format과 동일하다.

    Record

    java 14부터 추가된 Record는 값 타입을 쉽게 표현할 수 있는 기능이다. 자바는 쓸때없이 너무 장황하다라는 말이 많다. 값 타입의 클래스를 한번 만드려면 생성자, 접근자, equals, hashCode, toString, getter 등 너무 불필요하게 코드들을 작성해야 한다. Record를 사용하면 이제 그럴 필요 없다.
    public record Records(String name, String address) {
    }
    
    이제는 위와 같이만 해도 생성자, 접근자, equals, hashCode, toString, getter? 등이 만들어 진다. 추가적으로 생성자를 작성할 수 있다.
    public Records(String name) {
        this(name, "1528 Fillmore st, San Francisco");
    }
    
    Compact 생성자를 만들수 있다. Compact생성자는 지루한 변수할당없이 로직에 집중할 수 있도록 도와준다.
    record Records(String name, String address) {
    
        public Records {
            if (name == null) {
                throw new IllegalArgumentException("name must be not null!");
            }
            if (address == null) {
                throw new IllegalArgumentException("addrss must be not null!");
            }
        }
    }
    
    예를들어 위의 코드는 name 과 address의 대한 유효성을 체크하는 부분이다. 혹은 다음과 같이 변수할당없이 로직에만 집중할 수 있게 도와주기도 한다.
    record Rational(int num, int denom) {
        Rational {
            int gcd = gcd(num, denom);
            num /= gcd;
            denom /= gcd;
        }
    }
    
    이는 다음과 같은 코드이다.
    record Rational(int num, int denom) {
        Rational(int num, int demon) {
            // Normalization
            int gcd = gcd(num, denom);
            num /= gcd;
            denom /= gcd;
            // Initialization
            this.num = num;
            this.denom = denom;
        }
    }
    
    몇가지 제약조건들이 있다. 예를들어 extends절이 없고, final class 이며 abstract도 할 수 없다. 더 많은 제약 조건이 있으니 문서를 참고하면 되겠다.

    Sealed Class

    kotlin의 Sealed 클래스랑 문법만 다르지 거의 동일하다. Pattern Matching에도 사용할 수 있다. 예제부터 보자.

    sealed interface TransportSealed permits BusSealed, TaxiSealed { } sealed class BusSealed implements TransportSealed { public String getBus() { return "take a bus"; } } final class TaxiSealed implements TransportSealed { public String getTaxi() { return "take a taxi"; } } final class ExpressBus extends BusSealed { @Override public String getBus() { return super.getBus() + " Express"; } }
    sealed class 는 sealed 키워드를 통해 만들수 있으니 봉인된 클래스를 permits 키워드를 통해 선언 할 수 있다. permits에 없는 클래스는 사용할 수 없다. 봉인된 클래스에는 non-sealed, sealedfinal, 세 키워드중에 하나를 선택해야만 한다. non-sealed 은 상속이 가능하다. final 은 상속이 불가하다. sealed은 하위 클래스가 있어야 한다.
    static void getTransport(TransportSealed transportSealed) {
    
        switch (transportSealed) {
            case ExpressBus expressBus -> System.out.println(expressBus.getBus());
            case BusSealed bus -> System.out.println(bus.getBus());
            case TaxiSealed taxi -> System.out.println(taxi.getTaxi());
        }
    }
    
    위와 같이 switch을 이용해서 Pattern Matching도 할 수 있다. switch 문에 봉인된 클래스가 하나라도 없으면 컴파일에러가 발생한다. 또한 record 클래스에도 사용할수 있으니 참고하면 되겠다.

    Miscellaneous

    Helpful NullPointerExceptions

    NullPointException의 message가 좀 더 명확하게 나온다.
    private static void lowerCase(String str) {
        System.out.println(str.toLowerCase());
    }
    
    String str = null;
    //Null Point Exception
    lowerCase(str);
    
    위와 같은 코드가 있을 경우 정확히 어디에서 에러가 발생한지 메시지로 통해 확인할 수 있다.
    Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toLowerCase()" because "str" is null
    
    사실 위의 코드는 굳이 메시지 말고도 라인만 봐도 명확하다. 하지만 좀 더 복잡한 경우에는 도움이 많이 될 것 같다. 아래와 같이 말이다.
    lowerCase(a.b.c.i);
    
    만약 b가 null이라면 메시지는 다음과 같다.
    Exception in thread "main" java.lang.NullPointerException: Cannot read field "c" because "a.b" is null
    

    toList

    Strem을 자주 사용한다면 많이 사용되는 terminal operation .collect(Collectors.toList())을 간편하게 줄일 수 있다.
    Stream.of(1, 2, 3, 4, 5).toList();
    
    귀찮게 collect를 사용하지 않아도 바로 List로 만들 수 있다. 필자가 알아본건 여기까지다. 물론 더 많은 내용이 있으니 공식문서들을 잘 살펴보면 되겠다. 사용법들을 알았으니 좀 더 각자 딥하게 들어가는 것도 좋겠다. 필자는 언젠가 다시 자바를 사용할 수 있으니 그때 더 딥하게 알아보자.

    댓글

Designed by Tistory.