Java - JDK1.7(JAVA 7) 특징 try-with-resource 등

2019. 2. 11. 20:27프로그래밍언어/Java&Servlet

Java - JDK1.7 특징 try-with-resource 등



JAVA 10버전 이상 나온 시점에 자바 7의 특징을 설명하자니 많이 뒤쳐진 느낌이 들지만 저처럼 아직도 자바 5,6 세대에 머무르고 있는 분들이 있어서는 안되지만 혹시라도 계시는 분들이 있을 것이기에 간단하게 정리해보았습니다. 자바7도 안되있는 상태에서 자바8,9,10 등을 공부하는게 뭔가 순리에 어긋나는 것같습니다. 부족하지만 하나하나 천천히 공부해 나가야 할것 같습니다. 간단히 대략 몇 가지정도만 정리하려고 합니다.



Type Interface



Java 7 이전에는 제너릭 타입 파라미터를 선언과 생성시 중복해서 써주어야했습니다. 사실 저는 현재 생성자 쪽은 제네릭타입을 따로 넣지 않고 사용했었는데, 이게 당연히 원래 되는 건줄 알고 있었는데, 알고보니 JAVA 7에서 개선된 점이었내요.


JDK 7 이전

1
2
Map<String, List<String>> employeeRecords = new HashMap<String, List<String>>();
List<Integer> primes = new ArrayList<Integer>();

JDK 7

1
2
Map<String, List<String>> employeeRecords = new HashMap<>();
List<Integer> primes = new ArrayList<>();



물론 기존에도 제너릭 메소드를 제공함으로써 Type Inference가 가능했는데요. 예를 들어 Static Factory Method를 아래처럼 사용하면 별도의 타입 파라미터 없이도 간단히 제너릭 객체를 리턴 받아 사용 가능합니다. Guava 라이브러리 등에서 자주 사용하던 방식입니다.

1
2
3
4
5
public static <K,V> HashMap<K,V> newContacts() {
   return new HashMap<K,V>();
}
 
HashMap<String, Set<Integer>> contacts = newContacts();

하지만 Java7에서는 스펙 자체에 <> 연산자가 추가되어 굳이 불필요한 Static Factory Method를 안만들어도 된다는 장점이 있겠죠?
<>조차 사용 안하면 어때?라는 생각도 들지만 제너릭 지원 이전의 버젼 호환성을 위해서는 새로운 연산자의 등장은 어쩔 수 없는 선택이었나 봅니다.


String value in Switch


Switch문 내에서 문자열 사용이 가능해졌습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch (day) {
    case "NEW":
        System.out.println("Order is in NEW state");
        break;
    case "CANCELED":
        System.out.println("Order is Cancelled");
        break;
    case "REPLACE":
        System.out.println("Order is replaced successfully");
        break;
    case "FILLED":
        System.out.println("Order is filled");
        break;
    default:
        System.out.println("Invalid");
}

try-with-resource


JAVA 7 이전에는 DB Connection,File 등의 자원을 사용한 후에는 항상 try-catch-finally의 finally문에서 close()를 호출하여 닫아줘야했습니다. 항상 같은 패턴이지만 사용한 곳에서는 꼭 넣어줘야만 하는 로직이었습니다. 그래야 자원 반납이 되어 리소스 풀이 나지 않았으니, 그리고 또한 예상치 못한 오류가 발생할 수도 있기에 항상 finally 문 안에서 자원 반납하는 코드가 들어갔습니다.

JDK 7 이전

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
public static void main(String args[]) {
    FileInputStream fin = null;
    BufferedReader br = null;
    try {
        fin = new FileInputStream("info.xml");
        br = new BufferedReader(new InputStreamReader(fin));
        if (br.ready()) {
            String line1 = br.readLine();
            System.out.println(line1);
        }
    } catch (FileNotFoundException ex) {
        System.out.println("Info.xml is not found");
    } catch (IOException ex) {
        System.out.println("Can't read the file");
    } finally {
        try {
            if (fin != null)
                fin.close();
            if (br != null)
                br.close();
        } catch (IOException ie) {
            System.out.println("Failed to close files");
        }
    }
}

하지만 JAVA 7에서는 try-with-resource 구문이 추가 되어 자동으로 자원반납을 해줍니다. 하지만 전제? 필요조건이 있습니다. 그것은 사용한 자원이 AutoClosable 혹은 Closeable 인터페이스를 구현한 경우에만 해당 구문을 사용가능합니다. 기본적으로 우리가 사용하는 자원반납용 객체들은 다행이 구현하고 있습니다.

JDK 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String args[]) {
    try (FileInputStream fin = new FileInputStream("info.xml");
            BufferedReader br = new BufferedReader(new InputStreamReader(
                    fin));) {
        if (br.ready()) {
            String line1 = br.readLine();
            System.out.println(line1);
        }
    } catch (FileNotFoundException ex) {
        System.out.println("Info.xml is not found");
    } catch (IOException ex) {
        System.out.println("Can't read the file");
    }
}



Underscore in Numeric literal


숫자형(정수,실수)에 _(underscore) 문자열을 사용 할 수 있습니다. 금융권 등에서 큰 숫자들을 다룰 경우 가독성 향상에 도움이 되겠죠?

1
2
3
4
5
int billion = 1_000_000_000; // 10^9
long creditCardNumber = 1234_4567_8901_2345L; //16 digit number
long ssn = 777_99_8888L;
double pi = 3.1415_9265;
float pif = 3.14_15_92_65f;

_ 위치가 다음과 같을 경우엔 컴파일 에러가 발생하니 주의해야합니다.

1
2
3
double pi = 3._1415_9265; // 소수점 뒤에 _ 붙일 경우
long creditcardNum = 1234_4567_8901_2345_L; // 숫자 끝에 _ 붙일 경우
long ssn = _777_99_8888L; // 숫자 시작에 _ 붙일 경우


Catching Multiple Exception Type in Single Catch Block


단일 catch 블럭에서 여러개의 Exception 처리가 가능해졌습니다. (Multi-Catch 구문 지원) JDK7 이전에는 2개의 Exception을 처리하기 위해서는 2개의 catch 블럭이 필요했었죠.

JDK 7 이전

1
2
3
4
5
6
7
try {
    //......
} catch(ClassNotFoundException ex) {
    ex.printStackTrace();
} catch(SQLException ex) {
    ex.printStackTrace();
}

JDK 7

1
2
3
4
5
try {
    //......
} catch (ClassNotFoundException|SQLException ex) {
   ex.printStackTrace();
}

단, Multi-Catch 구문 사용시 다음과 같이 하위클래스 관계라면 컴파일 에러가 발생하므로 주의하세요.

1
2
3
4
5
6
7
8
try {
    //...... }
catch (FileNotFoundException | IOException ex) {
    ex.printStackTrace();
}
 
Alternatives in a multi-catch statement cannot be related by sub classing, it will throw error at compile time :
java.io.FileNotFoundException is a subclass of alternative java.io.IOException at Test.main(Test.java:18)

Java NIO 2.0


JDK7에서 java.nio.file 패키지를 선보였는데요. 기본파일시스템에 접근도 가능하고 다양한 파일I/O 기능도 제공합니다. 예를 들면 파일을 옮기거나 복사하거나 삭제하는 등의 유용한 메소드들을 제공하며, 파일속성이 hidden인지 체크도 가능합니다. 또한 기본파일시스템에 따라 심볼릭링크나 하드링크도 생성 가능합니다. 와일드카드를 사용한 파일검색도 가능하며 디렉토리의 변경사항을 감시하는 기능도 제공합니다. 어쨋든 외부 라이브러리로 해결했던 많은 일들이 JDK안으로 녹아들었네요.


More Precise Rethrowing of Exception


다음 예제를 보면 try 블럭안에서 ParseException, IOException의 Checked Exception이 발생 할 수 있습니다. 각각의 Exception을 처리하기 위해 Multi-Catch 또는 다중 Catch 문으로 예외처리를 할 수 있지만 예제에서는 최상위 클래스인 Exception으로 처리하였습니다. catch 구문에서 발생한 예외를 상위 메소드로 전달하기 위해 throw 할 경우 메소드 선언부에 해당 예외를 선언해주어야하는데요.

JDK7 이전 버젼에서는 다음 예처럼 catch 구문내에서 선언한 예외 유형만 던질 수 있었습니다. (Exception 클래스),

1
2
3
4
5
6
7
8
9
public void obscure() throws Exception {
    try {
        new FileInputStream("abc.txt").read();
        new SimpleDateFormat("ddMMyyyy").parse("12-03-2014");
    } catch (Exception ex) {
        System.out.println("Caught exception: " + ex.getMessage());
        throw ex;
    }
}

하지만 JDK7에서는 좀 더 정확하게 발생한 Exception을 전달할 수 있습니다. (ParseException, IOException 클래스) 물론 이전과 같이 throws Exception으로 처리할 수도 있지만 메소드를 호출한 쪽에 좀 더 정확한 예외를 던져주는게 좋겠죠? 

1
2
3
4
5
6
7
8
9
public void precise() throws ParseException, IOException {
    try {
        new FileInputStream("abc.txt").read();
        new SimpleDateFormat("ddMMyyyy").parse("12-03-2014");
    } catch (Exception ex) {
        System.out.println("Caught exception: " + ex.getMessage());
        throw ex;
    }
}



ArrayList와 HashMap의 개선


JAVA 7 초기 버전까지만 해도 초기값을 명시하지 않은 Default 생성자로 ArrayList 생성시 내부적으로 크기가 10인 Object 배열을 생성했습니다. 생성하고 사용하지 않았는데도 해당 리스트객체는 크기가 10인 Object배열을 쥐고 있는 것이죠. HashMap 또한 Capacity가 16인 Map을 디폴트로 생성합니다.

  • JDK 1.6의 ArayList 생성자
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    this(10);
}
 
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    this.elementData = new Object[initialCapacity];
}


  • JDK 1.6의 HashMap 생성자
1
2
3
4
5
6
7
8
9
10
/**
 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
 * (16) and the default load factor (0.75).
 */
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    threshold = (int) (DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
    table = new Entry[DEFAULT_INITIAL_CAPACITY];
    init();
}

JDK 1.7에서 변경된 사항

JDK 1.7 update 40 이후의 ArrayList는 다음과 같이 빈 Object 배열을 가지고 있습니다. static으로 선언되어 있으니 모든 ArrayList 인스턴스에서 공유하겠죠? Default 생성자로 ArrayList 생성시 해당 빈 Object를 초기값으로 설정합니다. 이전 버젼처럼 불필요하게 10인 Object 배열을 생성해 메모리를 낭비하지 않습니다. (Default 생성자의 주석은 아직 update가 안되었나보네요. ‘주석의 나쁜 예’겠죠?)

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * Shared empty array instance used for empty instances.
 */
private static final Object[] EMPTY_ELEMENTDATA = {};
 
/**
 * Constructs an empty list with an initial capacity of ten.
 */
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

HashMap도 별반 다르지 않습니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
 * An empty table instance to share when the table is not inflated.
 */
static final Entry<?, ?>[] EMPTY_TABLE = {};
 
/**
 * The table, resized as necessary. Length MUST Always be a power of two.
 */
transient Entry<K, V>[] table = (Entry<K, V>[]) EMPTY_TABLE;
 
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    // .....
}