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();
}
}
java8void 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);
댓글
댓글 쓰기