Java9 - Language Changes


Improvement of try-with-resources statements

try-with-resources는 java8부터 도입된 개념으로 사용 후 close 되어야 하는 resource에 대해 명시적인 close() 호출없이도 자동으로 resource가 반환될 수 있도록 해주는 안전장치라고 할 수 있다.

java8 이전
void printFile(String filePath) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(new File(filePath)));
    String line;

    try {
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } finally {
        if (br != null) {
        br.close();
    }
}
java8
void printFile(String filePath) throws IOException {
    BufferedReader br1 = new BufferedReader(new FileReader(new File(filePath)));
    String line;

    try (BufferedReader br2 = br1) {
        while ((line = br2.readLine()) != null) {
            System.out.println(line);
        }
    } 
}
java8에서는 위와 같이 명시적인 close() 메소드 호출 없이도 resource 반환이 이루어진다.(Exception 발생 여부와 관계없이) java9에서는 BufferedReader br2 = br1; 과 같이 resource를 재할당하는 구문조차 생략이 가능해졌다.
void printFile(String filePath) throws IOException {
    BufferedReader br1 = new BufferedReader(new FileReader(new File(filePath)));
    String line;

    try (br1) {
        while ((line = br1.readLine()) != null) {
            System.out.println(line);
        }
    } 
}

Deprecation

java9에서는 이전 버전보다 좀 더 향상된 deprecation 정의를 제공한다. java8이하에서는 특정 API가 deprecated 되더라도 취소선만 표시될 뿐 실제로 그 API가 삭제되는 경우는 없었는데 java9에서는 실제로 삭제될 것임을 명시하여 사용자에게 좀 더 신중한 migration 계획을 세우도록 예고할 수 있게 되었다. deprecation에 대한 표기는 이전 버전과 같이 @Deprecated 어노테이션을 사용한다.

Deprecation in java9
@Deprecated(since="[version]")
[version] 값은 해당 API가 deprecated된java version을 나타내며 default값은 empty string("") 이다.
@Deprecated(forRemoval=[boolean])
forRemoval flag는 향후 해당 API의 삭제 여부를 나타내며 default 값은 false 이다.
아래 코드는 java1.5 버전부터 deprecated된 Thread.destroy() 메소드가 향후 삭제될 것임을 알려주고 있다.
public class Thread implements Runnable {
    ...
    @Deprecated(since="1.5", forRemoval=true)
    public void destroy() {
    ...
    }
    ...
}

Immutable Collections

java에서 immutable이란 말그대로 '변하지 않는' 것들을 의미한다. 대표적으로 String 클래스가 있다. 아래 코드를 실행해보면 결과가 바뀌지 않는 것을 알 수가 있는데, 그 이유는 String은 value자체를 변경하는 것이 아니라 새로운 String instance를 생성하여 value를 새롭게 할당하는 Immutable class이기 때문이다.

public static void main(String[] args) {
    String str = "Korea : Sweden = 3 : 2";
    str.toUpperCase();
    System.out.println(str);
}
=> 결과
Korea : Sweden = 3 : 2
Set, List, Map과 같은 Collection 객체들도 Immutable로 생성할 수 있는데, java8에서는 아래와 같이 생성하였다.
List strList = Arrays.asList("s", "t", "r");
strList = Collections.unmodifiableList(strList);
    
Set strSet = new HashSet<>(Arrays.asList("s", "t", "r"));
strSet = Collections.unmodifiableSet(strSet);

Map strMap = new HashMap<>();
strMap.put("s", "m");
strMap.put("t", "a");
strMap.put("r", "p");
strMap = Collections.unmodifiableMap(strMap);
이렇게 생성된 immutable list는 변경할 수 없으며, 변경을 시도하면 아래와 같이 Exception이 발생한다.
strList.add("a);

=> Exception in thread "main" java.lang.UnsupportedOperationException
 at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1056)
java9에서는 이보다 짧게, 아래와 같이 생성이 가능해졌다.
List strList = List.of("s", "t", "r");

Set strSet = Set.of("s", "t", "r");

Map strMap = Map.of("s", "m", "t", "a", "r", "p");
Immutable Map의 경우에는 아래와 같이 생성할 수도 있다.
Map strMap = Map.ofEntries(Map.entry("s", "m"), Map.entry("t", "a"), Map.entry("r", "p"));

Iteration

일반적으로 HashSet, HashMap으로 생성된 collection의 iteration은 순서가 변하지 않는다. 

Set intSet = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));
Map intMap = new HashMap<>();
intMap.put("a", 1);
intMap.put("b", 2);
intMap.put("c", 3);

System.out.println(intSet);
System.out.println(intMap);

=> 결과
[1, 2, 3, 4, 5]
{a=1, b=2, c=3}
하지만 아래와 같이 Set.of(), Map.of()로 생성된 collection은 iteration이 순차적으로 일어나지 않을 수 있음에 유의할 필요가 있다.
Set intSet = Set.of(1, 2, 3, 4, 5);
Map intMap = Map.ofEntries(Map.entry("a", 1), Map.entry("b", 2), Map.entry("c", 3));

System.out.println(intSet);
System.out.println(intMap);

=> 결과
[5, 4, 3, 2, 1]
{c=3, b=2, a=1}

Immutable vs Unmodifiable

Immutable Object는 기본적으로 '불변'을 원칙으로 하지만, immutable collection들은 반드시 그렇지만은 않다. immutable collection이 가지는 element들이 immutable하지 않은 경우가 많기 때문이다.

List mutableList = new ArrayList<>();
mutableList.add("eye");
mutableList.add("nose");

List> immutableList = List.of(mutableList);
System.out.println(immutableList);

mutableList.add("mouth");

System.out.println(immutableList);

=> 결과
[[eye, nose]]
[[eye, nose, mouth]]
위와 같이 immutableList의 element는 얼마든지 바뀔 수 있다. 하지만 immutable collection을 직접 수정하려고 하는 시도는 Exception(UnsupportedOperationException)을 발생시키므로 'immutable'의 개념은 가지고 있다고 할 수 있다. 이렇게 mutable object를 가지는 immutable collection을 사용하는 것은 예기치 못한 상황을 발생시킬 수 있으므로 사용에 주의해야 한다. 가장 좋은 방법은 immutable object를 element로 가지는 immutable collection을 사용하는 것이다. Immutable collection은 기본적으로 thread-safe 하고, java9에서의 immutable collection은 메모리 절약에도 큰 일조를 하므로 제대로만 사용한다면 유용한 도구가 될 것이라 생각된다.
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class SizeCompare {

    public static void main(String[] args) {
        SizeCompare sc = new SizeCompare();

        Set oldSet = new HashSet<>();
        oldSet.add("korea");
        oldSet.add("mexico");
        oldSet = Collections.unmodifiableSet(oldSet);

        Set newSet = Set.of("korea", "mexico");

        System.out.println("size of oldSet(bytes) = " + sc.sizeOf(oldSet));
        System.out.println("size of newSet(bytes) = " + sc.sizeOf(newSet));
    }

    private long sizeOf(Object object){
        if (object == null) {
            return 0;
        }

        SizeCounter counter = new SizeCounter();
        try (counter) {
            ObjectOutputStream oos = new ObjectOutputStream(counter);
            oos.writeObject(object);
            return counter.getSize();
        } catch (IOException e){
            return -1;
        }
    }

    class SizeCounter extends OutputStream {
        private long size = 0;

        @Override
        public void write(int b) {
            size++;
        }

        @Override
        public void write(byte[] b, int off, int len) {
            size += len;
        }

        public long getSize() {
            return size;
        }
    }
}

=> 결과
size of oldSet(bytes) = 212
size of newSet(bytes) = 72
위와 같이 기존의 new HashSet<>(), Collections.unmodifiableSet() 으로 생성하는데에 사용되는 heap 메모리의 1/3 정도의 space로 collection 생성이 가능하다. 이 경우 불필요한 Wrapper(unmodifiable set), HashSet등의 object 생성을 생략하기 때문이다.

그 밖의 변화들

@SafeVarargs

@SafeVarargs 어노테이션은 일반적으로 static method나 final instance method 등 override 할 수 없는 method에 적용된다. java9에서는 private instance method에도 적용하는 것이 가능해졌다.
public class MyStringBuilder {

    private StringBuilder sb;

    public MyStringBuilder() {
        sb = new StringBuilder();
    }

    @SafeVarargs
    private void appendStrings(String... strings) {
        Arrays.asList(strings).forEach(e -> sb.append(e));
    }
}

Underscore character in field

변수명에 under score('_')를 사용하는 것이 불가능해졌다.

private interface method

private interface method를 선언하는 것이 가능해졌다. 이는 interface 내의 method 들이 코드를 공유할 수 있게 하기 위함이다.

Diamond operator for anonymous class

public abstract class MyClass {

    private T data;

    MyClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    abstract void doSomething();
}
위와 같은 abstract class가 있다고 가정하면 java8의 anonymous class에서는 아래와 같이 사용하였다.(diamond operator를 사용할 수 없었음)
public class DiamondOperatorTest {

    public static void main(String[] args) {

        MyClass<String> stringClass = new MyClass<String>("abc") {
            @Override
            void doSomething() {
                System.out.println(getData());
            }
        };

        stringClass.doSomething();
    }
}
하지만 java9에서는 아래와 같이 사용가능해졌다.
MyClass<String> stringClass = new MyClass<>("abc");
MyClass<Integer> intClass = new MyClass<>(1);

댓글

이 블로그의 인기 게시물

예제로 배우는 JAVA9

Java9 - New JDK Tools