반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
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 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item9. use를 사용하여 리소스를 닫아라 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item9. use를 사용하여 리소스를 닫아라

어리둥절범고래 2023. 9. 16. 14:14
반응형

close 메서드를 사용해서 명시적으로 닫아야 하는 리소스가 있습니다.

 

  • InputStream과 OutputStream
  • java.sql.Connection
  • java.io.Reader(FileReader, BufferedReader, CSSParser)
  • java.new.Socket과 java.util.Scanner

이러한 리소스들은 AutoCloseable을 상속받는 Closeable 인터페이스를 구현하고 있습니다.

// Closeable
public interface Closeable extends AutoCloseable {
    void close() throws IOException;
}
// AutoCloseable
public interface AutoCloseable {
    void close() throws Exception;
}

 

최종적으로 리소스에 대한 레퍼런스가 없어질 때, 가비지 컬렉터가 처리하지만 굉장히 느리며 비용이 많이 들어갑니다.

따라서 명시적으로 close 메서드를 호출해 주는 것이 좋으며, 전통적으로 try-finally 블록을 사용해서 처리했습니다.

 

fun countCharactersInFile(path: String): Int {
    val reader = BufferedReader(FileReader(path))
    try {
        return reader.lineSequence().sumBy { it.length }
    } finally {
        reader.close()
    }
}

 

리소스를 닫을 때 예외가 발생할 수 있는데, 이러한 예외를 따로 처리하지 않기 때문에 좋은 코드가 아닙니다.
또한 try-finally를 사용하여 처리하면, try 블록이나 finllay 블록 내부에서 오류가 발생하면 둘 중 하나만 전파됩니다.

use 함수 내부 구현을 보면 try와 finally 각각에서 오류를 핸들링 해주고 있으며, 이를 직접 구현하려면 코드가 길고 복잡해집니다.

@InlineOnly
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 함수를 사용해서 위 코드를 적절하게 변경하면 다음과 같습니다.

fun countCharactersInFile(path: String): Int {
    val reader = BufferedReader(FileReader(path))
    reader.use {
        return reader.lineSequence().sumBy { it.length }
    }
}

 

람다 매개변수로 리시버가 전달되는 형태로도 작성할 수 있습니다.

fun countCharactersInFile(path: String): Int {
    BufferedReader(FileReader(path)).use { reader ->
        return reader.lineSequence().sumBy { it.length }
    }
}

 

파일을 한 줄씩 처리할 때 활용할 수 있는 useLines 함수도 제공합니다.

fun countCharactersInFile(path: String): Int {
    File(path).useLines { lines ->
        return lines.sumBy { it.length }
    }
}

 

 

 


 

정리

 

use를 사용하면 Closeable/AutoCloseable을 구현한 객체를 쉽고 안전하게 처리할 수 있습니다.

반응형