Post

Java 11버전은 뭐가 달라졌나

개요

자바 11버전부터는 LTS(Long-Term Support)개념이 들어간다.

일반적인 소프트웨어는 새로운 버전이 출시될 때마다 이전 버전에 대한 업데이트를 중지하는데, LTS는 긴 기간 동안 지속적인 지원을 받을 수 있기 때문에, 기업이나 조직 등에서 사용하여 안정성을 어느정도 보장받을 수 있다.

✏️1. Local-Variable Syntax for Lambda Parameters

Java10에서 var에 대해 알아봤다.

Java11에서는 람다식에 var를 생략할 수 있게 하였다.

일반적으로 람다식에는 매개변수 타입을 명시해야하는데, Java11에서는 매개변수 타입을 생략할 수 있게 해준다는 것이다.

1
2
3
4
5
6
7
8
9
10
11
//기존 사용방식
var arrInteger = new Integer[]{5, 9, 3, 6, 2, 4, 8, 7, 1, null};
//기본 명시적 타입
long cnt = Arrays.stream(arrInteger).filter(
        (Integer x) -> (x != null && x > 5)).count();
System.out.println(cnt);

//var를 쓴 명시적 타입
cnt = Arrays.stream(arrInteger).filter(
        (var x) -> (x!= null && x> 5)).count();
System.out.println(cnt);

이렇듯 매개변수 타입명을 적어줬는데 타입을 생략할 수 있게 한다는 것이다.

1
2
3
4
//기본 암묵적 타입
cnt = Arrays.stream(arrInteger).filter(
        x -> (x != null && x > 5)).count();
System.out.println(cnt);

자바 컴파일러가 자동으로 타입을 추론한다. 이를 통해 코드를 짧게 쓸 수 있다.

var를 명시적으로 쓰는 경우에는 validation을 위해 쓰는 경우가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arrInteger = new Integer[]{5, 9, 3, 6, 2, 4, 8, 7, 1, null};
//var에 어노테이션을 추가 Null이면 에러
try {
    cnt = Arrays.stream(arrInteger).filter(
            (@Nonnull var x) -> (x > 5)).count();
    System.out.println(cnt);
}catch (NullPointerException e){
    System.out.println(e);
}

//var에 어노테이션을 추가 Null허용 null인경우 10으로 초기화해서 count값을 증가
cnt = Arrays.stream(arrInteger).filter(
        (@Nullable var x) -> {
            if(x == null)
                x = 10;
            return x > 5;
        }).count();
System.out.println(cnt);
}

이 경우 위의 결과는 에러가 발생할 것이고, 밑에는 5가 나올 것이다.

image

위의 모든 코드를 돌렸을때

두 가지를 혼용하지 말라고 한다. 타입을 모두 생략하거나 타입을 모두 적거나 모든 타입에대해 var를 쓰거나 하라고 한다.

✏️2. HTTP Client (Standard)

Java11버전 이전에는 HTTP통신을 하려면 많은 양의 코드를 요구하였다.

Java11에서는 새로운 HttpClient클래스가 생겨났고, 이 클래스가 제공하는 API들이 코드를 효율적으로 짤 수 있게 도와주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
@DisplayName("자바 11버전 이전 포스트 요청")
void HttpClientBefore() throws IOException {
    URL urlObj = new URL("http://localhost:8080/api/test");
    HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
    con.setRequestMethod("POST");
    con.setRequestProperty("Content-Type", "application/json");

    // Send data
    con.setDoOutput(true);
    String data = "test";
    try (OutputStream os = con.getOutputStream()) {
        byte[] input = data.getBytes(StandardCharsets.UTF_8);
        os.write(input, 0, input.length);
    }

    // Handle HTTP errors
    if (con.getResponseCode() != 200) {
        con.disconnect();
        throw new IOException("HTTP response status: " + con.getResponseCode());
    }else {
        // Read response
        String body;
        try (InputStreamReader isr = new InputStreamReader(con.getInputStream());
                BufferedReader br = new BufferedReader(isr)) {
            body = br.lines().collect(Collectors.joining("n"));
        }
        System.out.println("Body: " + body);
        con.disconnect();
    }
}

이 코드를 실행 시키기 위해서 SpringMVC를 추가하고 Controller를 추가하였다.

컨트롤어에는 요청 바디에 있는 값을 그대로 돌려주는 역할을 한다. 즉, body에 test를 포함해 Post요청을 하였으므로 test가 반환되어야 할 것이다.

image

코드가 정말 긴데, Java 11버전부터 새로운 HttpClient클래스가 생겼다.

이를 통해 이전 버전에 비해 매우 짧고 효율적은 코드를 작성할 수 있게 되었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
@DisplayName("자바 11이후 포스트 요청")
void HttpClientNew() throws IOException, InterruptedException {
    URL url = new URL("http://localhost:8080/api/test");
    HttpClient client = HttpClient.newHttpClient();

    String data = "test";
    HttpRequest request =
            HttpRequest.newBuilder()
                    .uri(URI.create(String.valueOf(url)))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(data))
                    .build();

    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

    if (response.statusCode() != 200) {
        throw new IOException("HTTP response status: " + response.statusCode());
    }
    System.out.println("Body: "+response.body());
}

image

새로생긴 HttpClient클래스는 매우 다양한 기능을 수행한다고 한다.

  • 위 코드에서 BodyPublisher.ofString()을 통해 문자열을 보냈는데, BodyPublisher클래스에 있는 ofByteArray(),ofByteArrays(),ofFile() 등을 통해 여러 데이터를 보낼 수 있다.
  • 마찬가지로 응답에 있는 BodyHandlersofString()을 통해 응답을 문자열로 받을 수 있지만 파일, 배열 등으로도 받을 수 있다.
  • HttpClient는 HTTP/2.0을 지원하기 시작.
  • HttpClient는 비동기적 요청도 지원한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
@DisplayName("자바 11 비동기 post요청")
void AsyncPostTest() throws MalformedURLException, ExecutionException, InterruptedException {
    URL url = new URL("http://localhost:8080/api/test");
    HttpClient client = HttpClient.newHttpClient();

    String data = "kms async test";
    HttpRequest request =
            HttpRequest.newBuilder()
                    .uri(URI.create(String.valueOf(url)))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(data))
                    .build();

    //요청이 비동기적이라 결과를 받아, 저장할 곳이 필요함. CompletableFuture클래스를 이용.
    //사실상 요청이 완료될때까지 프로그램을 대기시키기에 비동기라 할 수 없겠지만 출력물으 보여주기 위해 어쩔 수 없음. 실제 업무에서는 이렇게 쓰지 말 것.
    CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

    HttpResponse<String> response = responseFuture.get();
    System.out.println(response.body());
}

image

✏️3. New Collection.toArray() Method

기존 Collection 인터페이스는 두 가지의 toArray()라는 컬렉션을 배열로 바꾸는 함수를 제공했다.

1
2
3
4
5
6
List<String> list = List.of("kms","test","java11");

Object[] strings1 = list.toArray();

String[] strings2 = list.toArray(new String[list.size()]);
String[] strings3 = list.toArray(new String[0]);

인자를 받지 않는 toArray()의 경우 리턴값이 Object형이다. 이는 Type erasure라는 현상 때문인데, Type erasure란 자바에서 리스트를 생성할때 제너릭을 사용하면 컴파일시점에 타입이 결정이 된다. 근데 컴파일러가 코드를 컴파일하면서 제너릭의 타입 정보는 사라지게 되는데, 이렇게 컴파일 시점에 타입 정보가 사라지는 현상을 Type erasure라고 한다. 따라서 리스트의 타입 정보는 런타임때에 알 수 없어서 오브젝트 형으로 반환되는 것이다.

즉, List<String> list는 컴파일때 타입 소거로 인해 단순히 List가 되어버리기에 list.toArray()Object형으로 반환해야 한다.

두 번째 toArray() 메서드는 매개변수로 배열을 지정할 수 있는 오버로드 된 메서드이다. 이 메서드는 매개변수로 지정한 배열의 타입을 요청하게 된다. 만약 지정한 배열의 크기가 리스트의 요소 개수보다 적다면, 새로운 배열을 생성하여 리스트의 요소를 복사한 후 리턴하고,지정한 배열의 크기가 리스트의 요소 개수보다 크거나 같다면, 지정한 배열에 리스트의 요소를 복사한 후 리턴한다.

Java 11에서는 새로운 방식으로 쓸 수 있게 되었는데,

1
String[] strings = list.toArray(String[]::new);

이 방식을 사용하면 Collection 클래스가 절달된 배열 생성자의 참조를 사용하여 필요한 크기의 배열을 만들 수 있게 된다.

그런데 이 기능은 잘 사용되지 않는다. 그 이유는 컬렉션 인터페이스에만 구현되어 있기 때문이다.

이 코드의 구현방식을 보면 빈 배열을 만들고, 기존 toArray()를 호출하는 방식으로 되어 있다.

1
2
3
default <T> T[] toArray(IntFunction<T[]> generator) {
    return toArray(generator.apply(0));
}

문제는 이 메소드를 오버라이드 하지 않는 컬렉션 구현체가 많다는 것이다.

✏️4. New String Methods

Java 11버전에서 String클래스에 함수들이 추가적으로 생겼다.

String.strip(), stripLeading(), stripTailing()

예시를 보는게 빠르다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 문자열 앞 뒤 공백 제거 함수 strip()
String stripTest = "              1 2 3 4 5 6          ";
System.out.println(stripTest.strip() + "7");

//문자열 앞 공백 제거 함수 stripLeading()
System.out.println(stripTest.stripLeading() +"7");

//문자열 뒤 공백 제거 함수 stripTailing()
System.out.println(stripTest.stripTrailing() + "7");

// trim() vs strip()
String trimTest = "\u2003\u2003\u2003\u2003\u2003kms";

System.out.println(trimTest);
System.out.println(trimTest.trim());
System.out.println(trimTest.strip());

image

기존 trim()와 뭐가 다른걸까?

  • trim(): 이 함수는 ‘\u0020’이하의 공백들만 제거한다.
  • strip(): 유니코드의 공백들을 제거한다. 유니코드에는 ‘\u0020’ 이하의 공백문자 뿐만아니라 다양한 공백문자가 있다. 그 중 하나가 위와 같은 ‘\u2003’같은 것들이다.

String.isBlank()

이 함수는 각 요소들이 Character.isWhitespace()가 true이면 true를 리턴한다. Character.isWhitespace()함수는 공백 문자인지 여부를 판단하는 함수라서 문자열에 속해있느 모든 문자가 공백인지를 확인하여 공백이면 true를 리턴한다는 것이다.

1
2
3
4
//String.isBlank()
String isBlankTest = "          ";
//True
System.out.println(isBlankTest.isBlank());

String.repeat()

이 함수는 주어진 String을 연속적으로 나열하는 기능을 한다.

1
2
3
//String.repeat()
//hi?hi?hi?hi?hi?hi?hi?hi?hi?hi?
System.out.println("hi?".repeat(10));

String.lines()

이 함수는 line을 기준으로 String을 분리하는 기능을 한다. 리턴형은 Stream이다.

1
2
3
//String.lines()
Stream<String> lines = "foo\nbar\nbaz".lines();
lines.forEachOrdered(System.out::println);

✏️5. Files.readString() und writeString()

Java 6 이후로 텍스트 파일을 읽고 쓰는 것이 더욱 간편해졌다. Java 6에서는 파일을 읽기 위해 FileInputStream을 열고, InputStreamReader와 BufferedReader를 사용해야 했고, 텍스트 파일의 각 줄을 StringBuilder로 읽은 다음, finally를 통해 reader와 stream을 닫아야 했다.

Java 7에서는 Files.newBufferedWriter()와 Files.newBufferedReader()를 사용하면 finally구문이 없어도 reader와 stream을 닫을 수 있어서 더 간편하게 구현이 가능해졌다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
@DisplayName("자바7 파일 입출력")
void Java7FileWriteRead() throws IOException{
        //write
        Path path = Paths.get("./samplejava7.txt");
        String text="java 7 file test";
        try(BufferedWriter writer = Files.newBufferedWriter(path,StandardCharsets.UTF_8)){
            writer.write(text);
        }

        //read
        StringBuilder sb = new StringBuilder();
        try(BufferedReader reader = Files.newBufferedReader(path,StandardCharsets.UTF_8)){
            String line;
            while((line = reader.readLine()) != null){
                sb.append(line).append('\n');
            }
        }
        System.out.println(sb.toString());
}

image

Java11은 더욱 간편하게 표현이 가능해졌다.

1
2
3
4
5
6
7
8
9
10
11
12
@Test
@DisplayName("자바11 파일 입출력")
void Java11FileWriteRead() throws IOException{
        //write
        Path path = Paths.get("./samplejava11.txt");
        String text="java 11 file test";
        Files.writeString(path,text,StandardCharsets.UTF_8);

        //read
        String reader = Files.readString(path,StandardCharsets.UTF_8);
        System.out.println(reader.toString());
}

image


✏️6. Path.of()

이전에 Path객체를 생성하려면 Paths.get()을 사용했는데, 이는 getter처럼 보여서 혼동이 있었다고 한다.

그래서 Path.of로 바꾸고 기능은 동일하게 바꾸었다.

실제로 Paths.get()메소드의 구현 방식을 보면 Path.of()를 호출하게 되어있다.

1
2
3
public static Path get(String first, String... more) {
        return Path.of(first, more);
}

즉, 혼동을 줄이기 위해 이름을 바꾸었다는 것이다.

그래서 위의 예제는 다음 코드로 바꿀 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
@Test
@DisplayName("자바11 파일 입출력")
void Java11FileWriteRead() throws IOException{
        //write
        Path path = Path.of("./samplejava11.txt");
        String text="java 11 file test";
        Files.writeString(path,text,StandardCharsets.UTF_8);

        //read
        String reader = Files.readString(path,StandardCharsets.UTF_8);
        System.out.println(reader.toString());
}

✏️7. Epsilon: A No-Op Garbage Collector

Epsilon GC라는 가비지 컬렉터가 추가되었다.

Epsilon GC는 힙에 할당된 객체들을 관리하는 가비지 컬렉터인데 일반적인 가비지 컬렉터 기능인 메모리해제 기능을 수행하지 않는다.

왜냐하면 Epsilon GC는 시스템의 성능을 측정하거나, 시스템에서 가비지 컬렉션이 정상적으로 수행되는지 확인하는데 쓰이기 때문이다.

1
-XX:+UseEpsilonGC

이 커맨드를 java명령어에 추가해서 Epsilon GC를 사용할 수 있게 되었다.


✏️8. Launch Single-File Source-Code Programs

1
2
3
4
5
6
7
8
9
public class Hello {
  public static void main(String[] args) {
    if (args.length > 0) {
      System.out.printf("Hello %s!%n", args[0]);
    } else {
      System.out.println("Hello!");
    }
  }
}

이러한 코드가 있을때 이를 실행하려면 컴파일을 거치고 실행했어야했다.

1
2
3
4
5
6
$ javac Hello.java
$ java Hello Anna

⟶

Hello Anna!

Java 11부터는 컴파일 명령어를 생략할 수 있게 되었다.

1
2
3
4
5
$ java Hello.java Anna

⟶

Hello Anna!

위와 같이 달라져서 소스코드는 작업메모리에서 컴파일되고 실행이 되는 하나의 과정을 거치게 된다.


✏️9. Nest-Based Access Control

이부분은 해석하기 어려워서 다른사람글을 가져왔습니다.

출처: https://dreamchaser3.tistory.com/4

1
2
3
4
5
6
7
8
class Test {  
    static class Nest1 {  
        private int nest1Var; 
     } 
     static class Nest2 {  
        private int nest2Var;  
    }
 }

위와 같이 nested class의 경우, ‘Test’, ‘Nest1’, ‘Nest2’는 모두 ‘nestmate’이다. 기존 JVM 상에서는 nestmate끼리 private 멤버 변수를 접근하려면 컴파일러가 중간에 bridge method를 만들어야 했다. 따라서, reflection을 사용하여 nestmate class의 private 멤버 변수에 접근하려고 하면, llegalAccessException이 발생한다. 이러한 모순을 해결하고자, 새로운 ‘nest’라는 class file 개념을 도입해 하나의 중첩 클래스이지만 서로 다른 클래스파일로 분리하여 bridge method의 도움 없이도 서로의 private 멤버에 접근할 수 있도록 하였다.


✏️10. Java Flight Recorder

Java에는 개발을 도와주는 많은 분석 툴들이 있다. 근데 애플리케이션 런타임에 발생하는 문제들을 잡기는 보통 어렵다. 런타임에 발생하는 문제를 재발생 시키거나 재구현 하는게 까다로운 일인데 Java Flight Recoder는 런타임의 JVM 데이터나 여러환경들을 실시간으로 저장하고 분석하기 쉬운 파일로 저장할 수 있게 하여 에러 분석하는데 도와준다.

Java Flight Recorder는 Oracle JDK에는 상용화 되었는데, 이번에 OpenJDK의 일부가 되면서 자유롭게 사용할 수 있게 되었다.

1
2
3
#https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/run.htm#JFRUH164
# 이런식으로 실행이 가능하다.
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr myApp

✏️11. JDK Mission Control

JDK Mission Control은 Java 개발자를 위한 도구 중 하나로, Java 프로그램의 디버깅과 성능 모니터링을 돕는 데 사용된다. 이 도구는 JDK에 포함되어 있으며, Java 프로그램을 실행하는 동안 사용자가 실행 중인 작업을 모니터링하고 디버깅을 수행할 수 있도록 도와준다. 또한, 이 도구를 사용하면 Java 프로그램의 성능을 향상시킬 수 있는 방법을 찾을 수 있다고 한다.


✏️ 12. ZGC: A Scalable Low-Latency Garbage Collector

Z Garbage Collector = ZGC는 Oracle에서 개발한 새로운 가비지 컬렉터로 전체 GC의 STW(Stop-The-World)하는 시간을 최대 10ms까지 줄여버린다. Java11기준 리눅스에서만 사용이 가능하고 다음 명령어를 통해 사용할 수 있다.

1
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

여담으로 ZGC는 Java15버전에서 프러덕션으로 상용화 되었다.


결론

Java11버전 에서는 람다 표현식에 var를 사용할 수 있게 되었고,

새로운 HttpClient클래스가 나와 편리성을 도왔으며, 파일 입출력의 변화로 단 한 줄로 파일 입출력이 가능해졌다. 또한, Path클래스에 대한 변화를 줘서 코드 가독성이 힘을 주었고, Epsilon GC를 도입함으로써 시스템 성능을 향상 시켰다.

java 명령을 통해 별도의 컴파일 없이 컴파일부터 실행까지 진행할 수 있게 되었다.

Nest-Based Access Control를 통해서 컴파일러가 nested class를 인식하기 편하게 되었고, 여러 분석툴(Flight Recorder, JDK Mission Control)을 통해 애플리케이션을 추적, 모니털이 하기 쉬워졌다.

This post is licensed under CC BY 4.0 by the author.