Kotlin extensions use를 알아보고, 사용법을 알아보자.
개인 광고 영역
Kotlin의 extensions 중 use가 있다. 이 use의 알맞은 사용 방법을 알아보려면 Java의 Closeable을 간단하게 알고 넘어가면 좋을 것 같다.
이 글에서는 kotlin에서의 Closeable 처리를 알아보고, use를 간단하게 소개한다.
이 글에서 알아볼 내용
- AutoCloseable과 Closeable을 알아본다.
- kotlin의 exception 처리를 알아본다.
- kotlin의 use를 이용하자.
close가 꼭 필요한 것들
개발을 하면서 당연하게 close 처리해야 할 것들이 있다.
- InputStream, OutputStream
- java.io.Reader를 상속받는 부분(InputStreamReader 등등)
- java.net.Socket
- 등등
이런 class들은 Java 1.7에 추가되어 있는 interface AutoCloseable을 상속 받고 있는데, java 1.5에 추가된 interface Closeable에서 AutoCloseable을 다시 상속받는 형태로 구성되어 있다.
/**
* 1.7에서 추가
*/
public interface AutoCloseable {
void close() throws Exception;
}
/**
* 1.5에서 추가
*/
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
AutoCloseable
AutoCloseable은 아래와 같이 try
에 값을 바로 사용하고, 내부적으로 AutoCloseable의 close()를 호출해 준다고 한다.
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
Java 1.7 이상에서는 위와 같은 코드가 동작한다. 그리고 1.7 이전 버전에서는 이미 익숙할 try/catch/finally를 직접 정의해 사용했었다.
아래 코드가 누가 보아도 익숙한 코드일듯하다.
static String readFirstLineFromFileWithFinallyBlock(String path)
throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}
kotlin exceptions 처리 방법
kotlin의 exceptions 처리는 버전과 상관없이 아직은 아래와 같이 작성해야 한다.
try {
// some code
}
catch (e: SomeException) {
// handler
}
finally {
// optional finally block
}
kotlin에서 close 처리 방법
코틀린의 close 처리 방법은 다음과 같다.
@Test
fun test() = runBlocking {
val socket = Socket("thdev.tech", 80)
val inputStream = socket.getInputStream()
val reader = InputStreamReader(inputStream)
println(reader.readText())
reader.close()
inputStream.close()
socket.close()
}
여기에 try/catch/finally를 추가한다면 아래와 같이 추가할 수 있다. 여기서 주의해야 할 부분을 알아보면
- Socket을 생성하는 부분은 Exception 발생할 수 있다.
- getInputStream 부분 역시 Exception 발생할 수 있다.
- finally에 포함한 close 역시 Exception이 발생할 수 있다.
@Test
fun test() = runBlocking {
var socket: Socket? = null
var inputStream: InputStream? = null
var reader: InputStreamReader? = null
try {
socket = Socket("thdev.tech", 80)
inputStream = socket.getInputStream()
reader = InputStreamReader(inputStream)
println(reader.readText())
} catch (e: Exception) {
// ...
} finally {
socket?.close()
inputStream?.close()
reader?.close()
}
}
이 코드를 좀 더 수정하면 아래와 같이 finally 역시 exceptions을 처리해야 한다.
이렇게 처리해야 안전하게 처리하는 방법에 해당한다.
} finally {
try { socket?.close() } catch (e: Exception) {}
try { inputStream?.close() } catch (e: Exception) {}
try { reader?.close() } catch (e: Exception) {}
}
매우 코드가 복잡해졌고, Nullable까지 허용해야 한다.
Closeable을 상속받는 모든 class에 접근할 수 있는 use 확장 함수가 있다.
다행히 코틀린 1.2부터 제공하는 use {}
이 제공된다. 이 use extensions은 위에서 보았던 모든 try/catch/finally까지 포함하고 있다.
거기에 close 처리 시 안전하게 처리해 주기 위한 try/catch를 포함하고있다.
하지만 이 코드에서 주의해야 할 부분이 하나 있는데, catch의 오류는 throw e
를 하고 있기 때문에 안전하게 사용하려면 최소한의 try/catch 문을 한 번 더 묶어줘야 한다.
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored here
}
}
}
}
use를 적용해보자.
use를 이용해 매우 간단하게 처리할 수 있다. 다만 use를 사용하더라도, try/catch를 한번 묶어줘야 함을 기억해야 한다.
내부 코드에서 보았듯 exception은 그대로 흘려보내 밖으로 노출시켜준다. 결국 읽기 하다가 오류가 발생하면 밖에서 알아야 하기 때문에 이를 처리해 준다.
그리고 잊지 않고 사용할 수 있는 close는 내부에서 처리해 준다.
@Test
fun test() = runBlocking {
try {
Socket("thdev.tech", 80).use {
it.getInputStream().use {
InputStreamReader(it).use {
// throw java.lang.Exception("Read error!!!!")
println(it.readLines())
}
}
}
} catch (e: Exception) {
// ...
}
}
결국 필요한 오류 정보는 외부로 보내주고, 그 외에는 안전하게 사용할 수 있도록 만들어준 extensions을 활용할 수 있다.
Use를 사용할 때 읽기에 주의하라.
위에서 작성한 코드는 중첩으로 use를 활용하고 있다. 모두 it으로 접근하고 있다.
읽기 좋은 Kotlin 코드! Property와 Scope Functions!에서 다루었지만 저렇게 중첩으로 쓸 때는 어디가 어디인지 알기 어렵다.
그래서 명칭을 명확하게 작성해 줄 필요가 있다. it이 어디가 어디까지인지를 명확한 스쿱으로 지정해주는 게 필요하다. 그렇기에 it 대신 이름을 명확하게 지정해 주고 사용하길 추천한다.
@Test
fun test() = runBlocking {
try {
Socket("thdev.tech", 80).use { socket ->
socket.getInputStream().use { inputStream ->
InputStreamReader(inputStream).use { reader ->
println(reader.readLines())
}
}
}
} catch (e: Exception) {
// ...
}
}
마무리
간단하게 close 처리를 해야 하는 클래스들의 사용에 대해 알아보았고, kotlin의 use를 이용한 간단한 처리를 알아보았다.
이 글만 보기 아쉽죠. Effective Kotlin 더 보기
지금까지 작성한 EffectiveKotlin 관련 글은 태그로 모아 링크를 추가한다.
Comments