devlog of ShinJe Kim

[Kotlin] 자바의 try-with-resource 구문과 코틀린의 use 함수

|

자바의 try with resources문

자원 입출력을 수행할 때 메모리 누수를 방지하기 위해 아래와 같이 try-finally 구문을 사용할 수 있습니다.

static readFirstLine(String path) throws IOException{
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine;
    }finally {
        br.close();
    }
}

try-finally 구문에는 문제점이 있습니다. 우선, 자원을 둘 이상 사용하게 되는 경우 try문이 중첩되어야 한다는 것입니다.

또 다른 문제점은 위와 같은 코드에서는 try블록과 finally 블록 둘 다에서 예외가 발생하는 경우 문제의 원인을 찾기가 힘들게 됩니다. 만약 기기에서 물리적인 문제가 생기면 try 블록의 readLine과 finally 블록의 close 모두에서 예외가 발생할 것입니다. 이렇게 되면 두 번째 예외가 첫 번째 예외를 덮어버려서 스택 추적 내의 첫 번째 예외에 관한 정보가 남지 않게 된다고 합니다.

이러한 문제들을 해결하기 위해 자바 7에서 try-with-resources라는 것이 나왔습니다. 위의 try-finally 구문을 try-with-resources로 변경하면 아래와 같습니다.

static readFirstLine(String path) throws IOException{
    try(BufferedReader br = new BufferedReader(new FileReader(path))){
        return br.readLine();
    }
}

코드가 더 짧고 명확해졌습니다. 이펙티브 자바의 아이템 9에서는 꼭 회수해야 하는 자원을 다룰 때에는 try-finally말고, try-with-resources를 사용하도록 해야 한다고 권합니다.

코틀린의 use 함수

그렇다면 자바의 try-with-resources를 코틀린에서는 어떻게 사용할까요? 코틀린에서는 이를 언어 구성 요소로 제공하지는 않습니다. 대신 자바의 try-with-resources와 같은 기능을 제공하는 use라는 함수를 제공합니다. 위의 try-with-resource는 코틀린의 use를 사용하여 작성하면 아래와 같습니다.

fun readFirstLine(path: String): String {
    BufferedReader(FileReader(path)).use { br ->
        return br.readLine()
    }
}

이 때 주의할점은 람다의 본문 안에서 사용한 return은 비지역 반환이라는 것입니다. 따라서 이 return문은 람다가 아니라 readFirstLine 함수를 끝내면서 값을 반환하게 됩니다.

참고 자료

[Android] 안드로이드 Context란?

|

이 글은 MindOrks의 Understanding Context In Android Application을 (나름대로) 번역한 글입니다. 잘못된 부분이 있으면 언제든지 알려주세요.

안드로이드에서 컨텍스트(Context) 무엇인가?

이름 그대로 해석한다면 애플리케이션(객체)의 현재 상태의 맥락(context)를 의미합니다. 컨텍스트는 새로 생성된 객체가 지금 어떤 일이 일어나고 있는지 알 수 있도록 합니다. 따라서 액티비티와 애플리케이션에 대한 정보를 얻기 위해서는 컨텍스트를 사용하면 됩니다.

또한, 컨텍스트(Context)는 시스템의 핸들과도 같습니다. 리소스. 데이터베이스, preferences 등에 대한 접근을 제공합니다. 안드로이드 앱에는 ‘액티비티’라는 것이 있습니다. 액티비티는 애플리케이션이 현재 실행중인 환경에 대한 핸들과도 같습니다. 액티비티 객체는 컨텍스트 객체를 상속받습니다. 액티비티는 애플리케이션의 특정 리소스와 클래스, 그리고 애플리케이션 환경에 대한 정보에 대해 접근할 수 있게 해줍니다.

안드로이드 개발에서 컨텍스트(Context)는 어디에나 있고, 가장 중요한 것입니다. 따라서 컨텍스트를 이해하고 올바르게 써야만합니다.

컨텍스트(Context)를 잘못 사용하는 것은 애플리케이션의 메모리 누수를 일으킬 수 있습니다.

안드로이드에는 다양한 유형의 컨텍스트가 있습니다. 아래에서 어떤 컨텍스트가 있는지, 그리고 언제 어떻게 사용해야 하는지 알아보겠습니다.

애플리케이션 컨텍스트(Application Context)

애플리케이션 컨텍스트는 싱글턴 인스턴스이며 액티비티에서 getApplicationContext()를 통해 접근할 수 있습니다. 이 컨텍스트는 애플리케이션의 라이프사이클과 연결되어 있습니다. 애플리케이션 컨텍스트는 현재의 컨텍스트와 분리된 라이프사이클을 가진 컨텍스트가 필요할 때나 액티비티의 범위를 넘어서 컨텍스트를 전달할 떄에 사용합니다.

예시: 만약 당신의 애플리케이션에서 싱글턴 객체를 생성하였는데 그 객체에 컨텍스트가 필요하다면, 애플리케이션 컨텍스트를 사용하면 됩니다.

만약 이러한 상황에서 액티비티 컨텍스트를 전달한다면 메모리 누수가 발생할 것입니다. 액티비티는 가비지 콜렉터에 의해 수집되지 않는데 액티비티 컨텍스트는 액티비티에 대한 참조를 계속 유지하기 때문입니다.

액티비티에서 라이브러리를 초기화해야 하는 경우, 액티비티 컨텍스트가 아닌 애플리케이션 컨텍스트를 전달해야합니다.

그 어떤 컨텍스트(Context)보다 오래 유지되는 컨텍스트(Context)가 필요할때에만 getApplicationContext()를 사용하십시오.

액티비티 컨텍스트(Activity Context)

액티비티 컨텍스트는 액티비티에서 사용 가능하며 이 컨텍스트는 액티비티의 라이프사이클과 연결되어 있습니다. 액티비티의 범위 내에서 컨텍스트를 전달하거나, 라이프사이클이 현재의 컨텍스트에 붙은 컨텍스트가 필요할 때(need the context whose lifecycle is attached to the current context) 액티비티 컨텍스트를 사용합니다.

예시: 라이프사이클이 액티비티에 붙은 객체를 생성해야 할 때 액티비티 컨텍스트를 사용할 수 있습니다.

ContentProvider에서의 getContext()

이 컨텍스트는 애플리케이션 컨텍스트이며 애플리케이션 컨텍스트와 비슷하게 쓰일 수 있습니다. 이는 getContext() 메소드로 접근할 수 있습니다.

언제 getApplicationContext()를 쓰지 말아야 할까?

  • 액티비티(Activity)가 하는 일 모두를 컨텍스트(Context)가 완전히 지원하는 것은 아닙니다. 컨텍스트(Context)를 사용하여 작업하려고 하는 많은 것들이 안될 것이며 특히 GUI와 관련된 것은 실패할 것입니다.
  • getApplicationContext()컨텍스트(Context)가 정리되지 않은 호출로 생성된 무언가를 유지하고 있으면 메모리 누수가 발생할 수 있습니다. 액티비티(Activity)를 사용하여 무언가를 가지고 있을떄, 액티비티(Activity)가 가비기 콜렉터에 의해 수집되면 다른 모든 것들도 같이 flush됩니다. 애플리케이션(Application) 객체는 프로세스 수명 동안 유지됩니다.

엄지 손가락의 법칙(The Rule Of Thumb)

대부분의 경우 현재 작업중인 것을 둘러싸는 컴포넌트에서 직접 사용할 수 있는 컨텍스트(Context)를 사용하세요. 참조가 해당 컴포넌트의 라이프사이클을 넘어서지 않는 이상 참조를 안전하게 유지할 수 있습니다. 액티비티나 서비스 이외의 객체에서 컨텍스트(Context)에 대한 참조를 저장해야 하는 즉시 해당 참조를 애플리케이션 컨텍스트로 전환하세요.

참고 자료

3개월간의 인턴 회고

|

1주일만 지나면 인턴을 한 지 딱 3개월이 된다. 처음 안드로이드 개발을 시작한 이후 지금까지 무엇을 했는지 정리해봐야겠다는 생각이 들었다. 아래에 대략적으로 정리해보았다.

  • 1개월차
    • 1주차: ConstraintLayout / 스토어 화면 그려보기
    • 2주차: 딥링크 / 동기&비동기 공부
    • 3주차: 앱 리소스(string, color, drawable 등) 모두 정리
    • 4주차: 안드로이드 미디어 플레이어 UI/UX 조사 / 코틀린 인 액션 1~2장(코틀린 기초)
  • 2개월차
    • 1주차: 코틀린 인 액션 3장 ~ 4장(함수, 컬렉션, 클래스, 객체, 인터페이스)
    • 2주차: 코틀린 인 액션 5장(람다, 컬렉션 API 등)
    • 3주차: 비밀번호 찾기 만들기 / 코틀린 인 액션 5장(람다, 컬렉션 API 등)
    • 4주차: 비밀번호 찾기 회고 / 코틀린 인 액션 6장(타입 시스템)
  • 3개월차
    • 1주차: 플레이어 UI 그려보기 / 안드로이드 Service
    • 2주차: bottom navigation 구조 변경 / 안드로이드 MediaPlayer, MediaSession
    • 3주차: 리사이클러뷰 세로 divider 그리기 / 홈 화면 동적으로 변경 / png 리소스 webp로 변경 / timepicker 조사
  • 기타

    자료 정리, 외부와의 커뮤니케이션 등 비개발적인 업무를 병렬로 진행

잘한 점

  • 꾸준히 블로깅(TIL, 공부 내용 정리 등)을 한 덕분에 과거의 나에게서 도움을 많이 받았다. 계속 블로그에 공부한 내용을 쌓아갈 것이다.

  • 업무를 항상 기록하고 계획을 짜서 업무를 진행했으며 진행 상황을 계속 공유했다.

  • 공식 문서를 꼼꼼하게 공부했다. 안드로이드에서 새로 해보는 것이 있으면 항상 공식 문서를 빠짐없이 읽어보고 정리한 뒤 진행했다.

  • 뷰를 신속하고 정확하게 짤 수 있게 되었다. 처음에는 뷰 하나 만드는 것도 어려워했는데 지금은 내가 아는 것에 대한 확신이 생겼다.

  • 비동기 흐름을 조금씩 이해하고 있다. 처음엔 비동기 개념을 공부하고 외워도 이해가 안되었다. 그런데 코드를 직접 짜보면서 ‘흐름 제어를 제대로 못하니깐 이런 에러가 나는구나’라는 것을 게속 경험하면서 비동기에 대한 감이 점점 생기고 있는 것 같다.

아쉬웠던 점

  • 매일 TIL을 쓰지 못했다.

  • 초반에 코틀린 공부를 병행하지 못했다. 처음에는 잘 하고 싶은 욕심에 늦은 시간까지 한껏 일을 하다가 집에 가서 코틀린 공부를 하려니 잘 안되었다. 이후에는 CTO님의 피드백을 받고 시간 분배를 조금 더 균형있게 할 수 있었다.

  • 회사 코드를 꼼꼼히 찾아보았다면 할 필요 없는 질문을 한적이 종종 있다. 회사 코드를 수시로 공부해서 빨리 내것으로 만들자.

  • 미뤄진 공부가 있다. 시간 분배를 해서 진작에 했어야 하는데 아직 나의 노션에는 ‘Todo’ 상태의 공부 카드가 3개 정도 있다. 2주 안으로 완료하는 것이 목표이다.

감사하게도 다음 달부터 팀에 정식으로 합류하게 되었다. 인턴에서 정식 주니어 개발자로, 그리고 시니어로 성장하는 과정까지 이곳에 계속 기록해보려 한다.

[TIL] 2019-10-25

|

Today I Learned

[TIL] 2019-10-24

|

Today I Learned

코틀린에서는 두 가지 방법으로 map을 만들 수 있다.

// 1번
... mapOf(
    Pair("one", 1),
    Pair("two", 2),
    Pair("three", 3)
)

// 2번
... mapOf(
    "one" to 1,
    "two" to 2,
    "three" to 3
)

2번 방식의 사용이 권장된다고 한다. 이펙티브 자바의 아이템 1을 정리하며 왜 그런지 공부해보았다.

생성자 대신 정적 팩터리 메서드를 고려하라

클래스는 해당 클래스의 인스턴스를 반환하는 단순한 정적 메서드인 정적 팩터리 메서드(static factory method)를 제공할 수 있다. 이 방식에는 장점과 단점이 모두 존재한다.

장점

  1. 이름을 가질 수 있다.

    생성자에 넘기는 매개변수와 생성자 자체만으로는 반환될 객체의 특성을 설명하지 못한다. 하지만 정적 팩터리 메서드는 이름을 잘 지으면 반환될 객체의 특성을 쉽게 묘사할 수 있다.

     BigInteger(int, int, Random) // 생성자
     BigInteger.probablePrime // 팩터리 메서드
    

    한 클래스에 시그니처가 같은 생성자가 여러 개 필요할 것 같으면, 생성자를 정적 팩터리 메서드로 바꾸고 각각의 차이를 잘 드러내는 이름을 지어주자.

  2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.

    이 덕분에 불변 클래스(immutable class; 아이템 17)는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있으며 같은 객체가 자주 요청되는 상황이라면 성능 향상에 상당한 도움이 된다. 대표적 예인 Boolean.valueOf(boolean) 메서드는 객체를 아에 생성하지 않는다.

  3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

    API를 만들 때 이 능력을 활용하면 구현 클래스를 공개하지 않고도 그 객체를 반환할 수 있어 API를 작게 유지할 수 있다. 이는 인터페이스를 정적 팩터리 메서드의 반환 타입으로 사용하는 인터페이스 기반 프레임워크(아이템 20)를 만드는 핵심 기술이기도 하다.

  4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

    반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. 심지어 다음 릴리스에서는 또 다른 클래스의 객체를 반환해도 된다.

  5. 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

    이러한 유연함은 서비스 제공자 프레임워크(service provider framework)를 만드는 근간이 된다. 서비스 제공자 프레임워크에서의 제공자(provider)는 서비스의 구현체다. 그리고 이 구현체들을 클라이언트에 제공하는 역할을 프레임워크가 통제하여, 클라이언트를 구현체로부터 분리해준다.

    서비스 제공자 프레임워크는 3개의 핵심 컴포넌트로 이뤄진다. 구현체의 동작을 정의하는 서비스 인터페이스(service interface), 제공자가 구현체를 등록할 떄 사용하는 제공자 등록 API(provider registration API), 클라이언트가 서비스의 인스턴스를 얻을 떄 사용하는 서비스 접근 API(service access API)가 그 주인공이다.

    클라이언트는 서비스 접근 API를 사용할 때 원하는 구현체의 조건을 명시할 수 있다. 조건을 명시하지 않으면 기본 구현체를 반환하거나 지원하는 구현체들을 하나씩 돌아가며 반환한다. 이 서비스 접근 API가 바로 서비스 제공자 프레임워크의 근간이라고 한 유연한 정적 팩터리의 실체다.

단점

  1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없다.

    앞서 언급된 컬렉션 프레임워크의 유틸리티 구현 클래스들은 상속할 수 없다는 이야기다. 어찌 보면 이 제약은 상속보다 컴포지션을 사용(아이템 18)하도록 유도하고 불변 타입(아이템 17)으로 만들려면 이 제약을 지켜야 한다는 점에서 오히려 장점으로 받아들일 수도 있다.

  2. 정적 팩터리 메서드는 프로그래머가 찾기 어렵다.

    생성자처럼 API 설명에 명확히 드러나지 않으니 사용자는 정적 팩터리 메서드 방식 클래스를 인스턴스화할 방법을 알아내야 한다. API 문서를 잘 써놓고 메서드 이름도 널리 알려진 규약을 따라 짓는 식으로 문제를 완화해줘야 한다.

핵심 정리

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관이 있다면 고치자.

출처