Java SE 7 : 새로운 코딩 기법 소개

|

Java SE의 새 버전인 7이 7월 8일 릴리즈되었다.
아직 1.4나 5 사용하는곳도 많은데 7이라니....







7이 업무에서 사용되기는 시간이 좀 더 걸리겠지만,
나중에 7으로 코딩된 코드를 파악하기 위해 7에서 추가된 코딩 기법들을 소개해 본다.

(사실은 블로그 포스팅이 너무 없어서 시간내어 써보는 글. 요새 쫌 바쁨...)






Java SE 7에서 변경된 주요 기능은 이곳에서 확인할 수 있으며,
이 포스트는 그 중, Java Programming Language에 대해 설명한다.

주. 이 포스트의 샘플 코드는 Java 공식 사이트의 그것을 그대로 가져왔다는 점을 밝히며....







- Binary Literals
Java의 기본형(byte, short, int, long)에 대해 이진 표현식을 사용할 수 있게 되었다.
16진수의 값을 표현하기 위해 앞에 0x를 붙이는 방식과 마찬가지로,
앞에 0b를 붙이면 된다. (0B도 가능)

비트 연산등을 위해 16진수 표현을 많이 사용하지만,
역시 가독성에 있어서는 2진수 그대로 표현하는것이 더 낫지 싶다.
// 8-bit byte형
byte aByte = (byte)0b00100001;

// 16-bit short형
short aShort = (short)0b1010000101000101;

// 32-bit int형
int anInt1 = 0b10100001010001011010000101000101;
int anInt2 = 0b101;
int anInt3 = 0B101; // B는 대소문자 구분 없이 사용 가능하다.

// 64-bit long형. 뒤에 L을 덧붙인다.
long aLong = 0b1010000101000101101000010100010110100001010001011010000101000101L;


- Strings in switch Statements
switch문에 드디어 String형이 사용 가능하게 되었다.
별거 없고, 샘플코드를 보면 이해가 똷!

[code java]
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
     String typeOfDay;
     switch (dayOfWeekArg) {
         case "Monday":
             typeOfDay = "Start of work week";
             break;
         case "Tuesday":
         case "Wednesday":
         case "Thursday":
             typeOfDay = "Midweek";
             break;
         case "Friday":
             typeOfDay = "End of work week";
             break;
         case "Saturday":
         case "Sunday":
             typeOfDay = "Weekend";
             break;
         default:
             throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
     }
     return typeOfDay;
}
[/code]

요런식으로 사용이 가능하단 말씀.

비교는 String.equals()를 사용하여 비교를 하기 때문에 (물론) 대소문자 구별은 하게 되고,
바이트코드를 생성할때는 if-else if-else 으로 생성이 된다고 한다.


- The try-with-resources Statement
자바 개발을 한다면 이런 경험 한번씩 있을거다.
데이터베이스 사용시에 이용하는 Statement라든가, IO 관련 클래스(메소드) 등,
Exception을 발생시키는 녀석들 중 에러가 발생하게 되면 finally 블록에서 null 체크를 하고 close를 하는,
비슷비슷한 코드가 여러군데 들어가는 그런 경험.

7에서는 try문의 확장으로 번거로운 작업을 약간을 줄일 수 있을 것 같다.



기존 코드를 보자.
[code java]
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
  BufferedReader br = new BufferedReader(new FileReader(path));
  try {
    return br.readLine();
  } finally {
    if (br != null) br.close();
  }
}
[/code]

BufferedReader.readLine()이 예외를 발생시키기 때문에 try블럭으로 감싸고,
BufferedReader가 닫혀야 하므로 finally블럭에서 close()를 호출하여 닫아준다.

7에서는 다음과 같은 방식으로 사용이 가능하다.
[code java]
static String readFirstLineFromFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  }
}
[/code]

try(...)에서 생성한 객체는 try 블럭 안에서만 유효하고,
try블럭이 종료되게 되면 자동적으로 닫히게 된다.

여기서, 다시 예전의 코드를 살펴보면,
BufferedReader.readLine()이 예외를 발생시키지만, BufferedReader.close() 역시 예외를 발생시킨다.
finally블럭이 따로 있는 경우엔 다시 try로 감싸서 처리를 할 수 있지만,
새로운 방식에서는 어떻게 처리를 하느냐?

close()에서 예외가 발생하게 되면 try블럭 안에서 발생한 예외는 무시되고, close()에서 발생한 예외가 핸들링된다.
try블럭 안에서 발생된 예외는 Throwable.getSuppressed 메소드로 가져올 수 있다.

자동으로 닫을 대상 클래스는 java.lang.AutoCloseable이나 java.io.Closeable 를 상속받은 클래스만 사용이 가능하다.
대충 클래스 목록을 훓어 보면 왠만한 클래스는 사용이 가능한 것 같다.

그리고, 두개 이상의 객체를 생성하려면?
다음과 같이 구현한다.

[code java]
...
    try (
      java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
      java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
....
[/code]

별거 없다. 그냥 ;로 구분하여 생성해주면 된다. (이때의 ;는 일반적인 용도가 아니라서 맨 마지막에는 없다는걸 명심.)


try-with-resource구문을 사용하면 finally블럭은 사용을 못하는 것인가?
아니다. 이전 용도처럼 사용이 가능하다.
한가지 알아두어야 할 것은 finally구문은 try-with-resource로 생성된 객체들이 자동적으로 close된 다음 실행된다는 것을 명심.



- Catching Multiple Exception Types and Rethrowing Exceptions with Improved Type Checking
이번에도 예외처리에 관한 내용이다.

두가지 이상의 예외가 발생하는 try구문에서 각 예외에 대해 같은 처리를 하고 싶어도 지금까지는 이렇게 따로 썼다.

[code java]
catch (IOException ex) {
     logger.log(ex);
     throw ex;
catch (SQLException ex) {
     logger.log(ex);
     throw ex;
}
[/code]

이 구문을, 이제 이렇게 사용할 수 있다.

[code java]
catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}
[/code]

OR 연산자로 붙여주면 된다.

다중 catch문의 파라메터 (위 소스에서는 ex)는 암묵적으로 final 지시자가 지정되게 된다.
따라서 catch블럭에서 이 ex 파라메터에는 어떠한 값도 변경할 수 없다.


다음으로 Callee에게 예외를 던질 때 자동적으로 타입을 적용하는 기능이다.

[code java]
  static class FirstException extends Exception { }
  static class SecondException extends Exception { }

  public void rethrowException(String exceptionName) throws Exception {
    try {
      if (exceptionName.equals("First")) {
        throw new FirstException();
      } else {
        throw new SecondException();
      }
    } catch (Exception e) {
      throw e;
    }
  }
[/code]

이 코드는 FirstException, SecondException을 발생시키지만,
Callee에서 보기에는 Exception 으로만 받을 수 있었다.

7에서는 다음과 같은 코딩이 가능하다.

[code java]
  public void rethrowException(String exceptionName) throws FirstException, SecondException {
    try {
      // ...
    }
    catch (Exception e) {
      throw e;
    }
  }
[/code]

메소드의 선언 부분에 전달하고자 하는 예외의 종류를 추가적으로 명시하면
해당 예외 타입으로 전달되게 된다.

이때, 메소드 선언부분에 사용된 클래스가
try블럭 내부에서 throw되는 예외 클래스의 (같거나) 부모 클래스여야만 해당 타입으로 전달된다.



- Underscores in Numeric Literals
이것 역시 코드 가독성을 위한 기능이다.

[code java]
long creditCardNumber = 1234_5678_9012_3456L;
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
[/code]
 
숫자 값에는 영향을 주지 않고, 가독성을 위한 자리수 표시 등에 사용이 가능하다.

이 underscore가 사용될 수 없는 위치는 다음과 같다.
- 숫자의 시작이나 끝
- 소수점의 바로 앞이나 뒤
- F나 L표기의 앞
- 숫자로 구성된 문자열 (말그대로 그냥 문자열 안)

이건 설명보다 예제를 보는것이 빠르다.

[code java]
float pi1 = 3_.1415F;      // 불가. 소수점 앞
float pi2 = 3._1415F;      // 불가. 소수점 뒤
long socialSecurityNumber1 = 999_99_9999_L;         // 불가. L표식 바로 앞

int x1 = _52;              // _52 이건 그냥 변수명
int x2 = 5_2;              // OK (10진수)
int x3 = 52_;              // 불가. 맨 뒤 위치 불가
int x4 = 5_______2;        // OK (10진수)

int x5 = 0_x52;            // 불가. 0x사이에 들어갈 수 없음
int x6 = 0x_52;            // 불가. 숫자의 앞에 들어갈 수 없음
int x7 = 0x5_2;            // OK (16진수)
int x8 = 0x52_;            // 불가. 숫자의 뒤에 표시 불가

int x9 = 0_52;             // OK (8진수)
int x10 = 05_2;            // OK (8진수)
int x11 = 052_;            // 불가. 숫자 뒤 사용 불가.
[/code]


- Type Inference for Generic Instance Creation
제네릭을 사용할 때 새로운 객체를 생성하게 되면 다음과 같이 쓰고 있을 것이다.

[code java]
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
[/code]

컬렉션의 형을 지정하는 부분인 <String, List<String>>이 두번이나 들어가게 된다.

지정하기 귀찮다고 그냥 쓰시는 분들도 있는데.......





7부터는 이렇게 사용이 가능하다.

[code java]
Map<String, List<String>> myMap = new HashMap<>();
[/code]

R-Value에는 단순히 <>만 넣어주어도 정상적으로 사용이 가능하다.

물론 예전처럼,
[code java]
Map<String, List<String>> myMap = new HashMap();
[/code]
이렇게 사용하게 되면 unchecked convension warning이 발생하게 된다.

주1. 제네릭을 사용하는 생성자에도 적용이 가능하다. 좀 더 자세한 코드를 보고 싶다면 원본 문서로...

주2. <>를 가리켜 문서에서는 "diamond"라고 부른다.




대부분의 내용이 코딩 작성을 손쉽게 하는 것에 맞춰져 있다.
일단 제네릭 생략 부분과 문자열 switch사용은 종종 하게 될 것 같다.

And