본문 바로가기

안드로이드 Android

안드로이드 개발 (39) Kotlin In Action 정리 - 3

(이전 편 다시보기) 

 

안드로이드 개발 (37) Kotlin In Action 정리 - 1

현업에서 일을 하다보면, Back To The Basic 을 통하여 디버깅 추적 및 안전한 코드 작성 능력을 강화 할 수 있다는 것을 알게 됩니다. 이 글은 J brains 에서 일하는 Dmitry Jemerov, Kotlin 개발팀의 일원인 Sv

gift123.tistory.com

 

 

 

Kotlin In Action 요약 세번째 편 입니다. 이전에 읽었던 책 이었지만, 다시 복습 겸 블로그 포스팅을 통해 내용 정리도 함께 하고 있습니다. 이번 편은 3장 요약 내용 입니다. 개인적으로 널리 알려진 내용 정리는 스킵 하였고, 다시 잊어 먹거나 반복 암기하면 좋을 내용들 위주로 정리하였습니다.

 

3장 - 함수 정의와 호출 

자바에서의 함수 정의와 호출 기능을 코틀린이 어떻게 개선 하였는지 살펴봅니다. 추가로 확장 함수와 프로퍼티를 사용해 자바 라이브러리를 코틀린 스타일로 적용하는 방법을 살펴봅니다. 크게 3가지 주제 콜렉션, 문자열, 정규식순서로 설명이 진행 됩니다. 

 

1) 코틀린에서 컬렉션 만들기 

kotlin 은 java의 표준 collection을 사용합니다. 이로써, 자바에서 코틀린 함수를 호출 하거나 코틀린에서 자바코드를 호출할 때 서로 변환할 필요가 없으므로 상호 작용하기가 훨씬 쉽습니다.

java.util 에서 사용되는 collection 들을 사용하는편 이다.

2) 함수를 호출하기 쉽게 만들기

 

- kotlin 은 인자의 이름 붙여 사용할 수 있고, default Argment를 넣어서 활용 할 수 있습니다.  

* Loner 생각: 매우 강력한 기능 입니다. 인자를 붙임으로써 별도의 빌더 패턴을 없앨 수 있고, 중복된 오버로딩 코드를 제거함으로써, 코틀린의 가치관에서 언급한 중요하지 않는 코드 중복 제거 목적에 맞는 기능 입니다.* 

 

fun main() {
    person(
        name = "asd",
    )
}

fun person(name:String = "asd",arg:Int = 123) = Unit
// 자바로 변환하면 아래와 같은 함수로 바뀝니다.
   public static void person$default(String var0, int var1, int var2, Object var3) {
      if ((var2 & 1) != 0) {
         var0 = "asd";
      }

      if ((var2 & 2) != 0) {
         var1 = 123;
      }

      person(var0, var1);
   }

 

- default Argment 를 자바에서 활용하려면 @JvmOverloads 애노테이션을 붙이면 자동적으로 오버로딩한 함수들이 생성됩니다.

@JvmOverloads
fun person(name:String = "asd",arg:Int = 123) = Unit
   //아래와 같이 변환 합니다.
   @JvmOverloads
   public static final void person(@NotNull String name) {
      person$default(name, 0, 2, (Object)null);
   }

   @JvmOverloads
   public static final void person() {
      person$default((String)null, 0, 3, (Object)null);
   }

 

- koltin 에서 클래스 없이 파일에 함수만 만들면 , java 로 디컴 파일 시에 클래스명이 파스칼 케이스로 변경되고 + Kt 접두사가 붙는 클래스가 생성되고 클래스 안에 함수가 생성되는것을 확인 할 수 있다.

 

//파일명은 util
fun testUtil() = Unit
자바에서 아래와 같이 변환 된다.
public final class UtilKt {
   public static final void testUtil() {
   }
}

- 위 처럼 최상위 함수를 사용할 때 자바에서 생성되는 클래스명을 바꾸고 싶다면 @file:JvmName을 활용 하면 된다.

* Loner 생각: 실무에서 여러 util 관련해서 최상위 함수를 쓰는 경우가 많아서 파일명을 고심해서 짓기 때문에 이미 고려한 파일명을 java 만 따로 다르게 만드는 경우가 거의 없을 것 같습니다. 있다면 kotlin 와 java를 같이 쓰는 프로덕션의 경우, 각 언어마다 Util 네이밍 규칙이 다를 경우 사용할 일이 있을 것 같습니다.* 

@file:JvmName("TestUtil")
fun testUtil() = Unit
//자바에서 아래와 같이 변환 된다.
@JvmName(
   name = "TestUtil"
)
public final class TestUtil {
   public static final void testUtil() {
   }
}

3) 확장 함수와 확장 프로퍼티 

 

kotlin은 기존 java를 사용하는 곳에서 새롭게 도입 하는 곳들이 많기 때문에 기존 레거시나 혹은 java로 만들어진 라이브러리 및 프레임워크를 사용하는 경우가 대부분 입니다. 이런 기존 자바 API 를 재작성하지 않고도 확장 함수 및 확장 프로퍼티를 활용해서 기존 API에서 추가적인 기능을 만들 수 있습니다.

 

- 확장 함수/ 확장프로퍼티 둘 다 수신객체 타입을 함수명 앞쪽에 명시해서 본문에서 수신객체를 넘겨서 사용합니다. 

- 수신객체의 비공개 method 나 비공개 property (Java라면 비공개 Field) 등등 외부로 노출되지 않는 것들은 사용할 수 없으며, 확장 함수는 오버라이드 할 수 없습니다. 

- 확장 프로퍼티는 아무 상태도 가질 수 없이 getter만 사용할 수 있습니다. 

 

* Loner 생각: 둘다. 확장성이 매우 뛰어나고 기존 객체에 영향을 받지 않으며, 선언형/함수형 코딩 방식을 쉽게 돕기도하며, private를 잘 활용해서 쓰면 필요한 부분에서만 가져다 쓸 수 있기 때문에 실전에서도 매우 유용하게 쓸 곳이 많습니다.* 

 

fun main() {
    println("Hi".lastChar())
    println("Hi".firstChar)
    println(1.intPlusTest)
    println(1.intPlusTest())
}

fun String.lastChar() = last()
val String.firstChar get() = first()

val Int.intPlusTest get() = this +1
fun Int.intPlusTest() = this +1
//자바로 변환하면 다음과 같습니다.
// 특이점은 kotlin에서 "Hi".lastChar() 이었던 호출 부가 
// Java에서 lastChar("Hi") 로 바뀌어 사용하게 됩니다. 
// java에서 확장함수는 static final 이며, 첫번째 인자로 수신객체를 받습니다.
public static final void main() {
      char var0 = lastChar("Hi");
      System.out.println(var0);
      var0 = getFirstChar("Hi");
      System.out.println(var0);
      int var1 = getIntPlusTest(1);
      System.out.println(var1);
      var1 = intPlusTest(1);
      System.out.println(var1);
   }

   public static void main(String[] var0) {
      main();
   }

   public static final char lastChar(@NotNull String $this$lastChar) {
      Intrinsics.checkParameterIsNotNull($this$lastChar, "$this$lastChar");
      return StringsKt.last((CharSequence)$this$lastChar);
   }

   public static final char getFirstChar(@NotNull String $this$firstChar) {
      Intrinsics.checkParameterIsNotNull($this$firstChar, "$this$firstChar");
      return StringsKt.first((CharSequence)$this$firstChar);
   }

   public static final int getIntPlusTest(int $this$intPlusTest) {
      return $this$intPlusTest + 1;
   }

   public static final int intPlusTest(int $this$intPlusTest) {
      return $this$intPlusTest + 1;
   }

 

 

4) 컬렉션 처리

 

 kotlin의 collection 은 java.util.~ 로 기존 java의 것을 사용하지만 공식적으로 지원해주는 collection의 확장 함수를 통해서 확장성 높은 collection으로 사용 되고 있습니다.

 

- vararg 는 Java랑 달리 * 를 쓰는 spread 연산자를 이용할 수 있다.

val temp = arrayOf(1,2,3)
    val list = listOf(1,*temp)

- 중위 호출 infix call 로 아래와 같은 문법을 만들 수도 있습니다.

fun main() {
    println("로너" age 31)
}

//좋은 코드는 아닙니다;; 
infix fun String.age(age:Int) = Person(this,age)
data class Person(
    val name:String,
    val age:Int
)

 

* Loner 생각: 공식 지원해주는 infix fun 과 혼잡이 올 수 있어서 커스텀한 infix call을 사용했을때 보장되는 효율이 있을 때만 사용하면 좋을 것 같습니다. * 

 

- 구조 분해 선언으로 인스턴스를 반환하여 여러 변수를 동시에 초기화 할 수 있습니다.

fun main() {
    val (name,age) = "로너" to 31
}

* Loner 생각: 개인적으로 when문에서 효율적으로 사용하는 경우가 많았습니다.* 

 

5) 문자열 나누기 

- java에서 문자열의 split 메서드를 사용할 때 split은 기본적으로 구분 문자열을 정규식으로 사용 되기 때문에 혼동이 되는 경우가 있어서 kotlin은 split 대신에 여러가지 조합의 파라미터를 받는 split의 확장 함수를 제공함으로써 혼동을 야기하는 메서드를 감춥니다.

 

또한 정규식의 혼동을 막기 위해 toRegex() 확장 함수를 통해 Regex 타입을 따로 받기도 합니다.

val name = "로 너".replace(" ".toRegex(),"")
    println(name)
    //로너

 

- kotlin 은 Regiex 타입을 넘기지 않아도 문자열 치환 하는 확장함수들을 많이 제공합니다.

- """ """ 를 사용하면 별도의 escape 할 필요가 없다.

- """ """ 는 프로그램 텍스트를 쉽게 문자열로 바꾸는 데도 사용한다.

 

6) 로컬 함수와 확장

- kotlin은 로컬 함수를 지원한다. 

fun function(){
    fun localFun(){
        //..//
    }
    localFun()
}

"Loner 생각: 책에서 나온 첫번째 예시는 좋으나.. 두번째 예시에서 함수 외부 객체를 통해 로컬 함수 내부의 코드량을 줄이던데, 함수형을 추구하는 코드를 짜고 있다면 두번째 예시가 꼭 좋다고만 볼 수는 없어보인다."

kotlin 기본적인 함수 호출,문자열, collection 등등을 알아보았습니다. 시간이 지나 다시 해석하니 감회가 새롭고, 당시에 지나쳤던 부분을 다시 음미할 수 있어서 좋네요