kotlin 기본 정리
kotlin

kotlin 기본 정리

반응형

기본적인 특징

- 자바와 마찬가지로 정적 타입 지정 언어로 컴파일 시 모든 것이 결정된다.

- 자바와는 별개로 모든 타입을 직접 명시할 필요 없이 컴파일러가 타입 추론을 통해 자동 추출한다.

- nullable type을 제공하여 컴파일 시 npe 발생 여부를 미리 확인 할 수있다.

- 코틀린 사용에 있어서 실용성, 간결성, 안전성(casting, npe 이점), 상호 운용성(자바와 호환)등의 장점이 있음.

- 컴파일 시 코틀린 런타임 라이브러리에 의존한다.

- java의 기본 가시성은 default인것에 반해 코틀린의 기본 가시성은 public이다.

- 클래스를 만들지 않고 함수와 변수를 만들 수 있으며 이는 자동으로 컴파일 시 별도의 클래스를 만들도록 해준다.

- 최상위 변수(프로퍼티)도 getter, setter가 생기는데 자바의 public static final 필드처럼 상수로 사용하기 위해서는 const를 붙이면 된다. (const는 최상단 영역에서만 노출된다.)

const val NAME = "wedul"

- 자바에서는 == 는 주소에 대한 비교를 위해 사용하고 .equals를 통해 실제 객체가 같은지 비교 하는데 코틀린에서는 == 를 equals ===를 객체 참조에 대한 비교로써 사용한다.


 

 

공통

- if는 문장이 아닌 식으로 사용된다. 코틀린에서 대부분의 제어 구조는 루프만 제외하고 모두 식이다.

return if (a > b) a else b

- 코틀린은 여러 클래스를 한 파일에 넣을 수 있고 어느 디렉터리에 소스코드 파일을 위치시키든 관계없다.

 

타입체크

- 타입 체크를 하면 별도의 캐스팅 없이 바로 사용할 수 있는 것이 스마트 캐스팅이라고 한다.

// 타입 체크
val variable = "123";
println(variable is String)

// 캐스팅
val castVal = variable as Int;
println(castVal is Int)


-------
// 선언
fun smartCastTest(a : Any): Int {
    if (a is Int) {
        return 1 + a
    }
    return -1
}

// smart casting 테스트
println(smartCastTest(10))

 

반복문

// 반복문 형태
for (i in 100 downTo 0 step 2) {
    append(i)
}

// with와 함께 사용
fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    toString()
}

// index와 함께 사용
val result = StringBuilder(prefix);
for ((index, element) in collection.withIndex()) {
    if (index > 0) result.append(separator)
    result.append(element)
}

 

Default parameter

- default parameter 지정이 가능하고 인자 넘길 시 파라미터를 직접 지정해서 넘길 수 있다.

fun <T> joinToStringFuc(
        collection: Collection<T>,
        separator: String,
        prefix: String = "prefix",
        postfix: String = "postfix"
): String {
    val result = StringBuilder(prefix);
    for ((index, element) in collection.withIndex()) {
        if (index > 0) result.append(separator)
        result.append(element)
    }

    result.append(postfix)
    return result.toString()
}

// 호출
println(joinToStringFuc(listOf(1, 2, 3), ",", postfix = "postfix"))

 

가변인자

- 자바에서 가변인자는 ...으로 표기하였으나 코틀린에서는 vararg를 사용한다.

// function
fun createOneStr(vararg values: String): String {
    return values.joinToString(separator = "")
}

// usage
println(createOneStr("we", "dul","zzang"))

 

Enum

- 자바와 달리 enum class로 명칭하여 사용

// 선언
enum class Food(val cal: Int) {
    BANANA(100)
}

// 호출
println(Food.BANANA.cal)

 

when

- 자바의 switch처럼 사용할 수 있는 것이 when

- if문과 같이 when도 식으로써 사용이 가능

- switch와 다르게 인자가 없는 when식 생성 가능

// 인자가 없는 when
fun whenNotExistParameterTest(): Int {
    val random = Random(10);

    return when {
        random.nextInt() > 5 -> 10
        random.nextInt() < 3 -> 20
        else -> 30
    }
}

// 인자가 있는 when 
fun whenExistParameterTest(a: Int) = when (a) {
    1 -> "wedul"
    2 -> "chul"
    else -> "babo"
}

// when test
println(whenExistParameterTest(1))
println(whenNotExistParameterTest())

 

함수

- fun 키워드로 선언

- 자바와 달리 꼭 클래스 내부에 선언할 필요 없다.

- 타입 추론을 하는 코틀린에서는 식이 본문인 함수와 같이 컴파일러가 타입을 찾을 수 있는 경우에는 별도의 타입을 지정하지 않아도 되고 return도 생략이 가능하다.

 

변수

- 타입추론이 있기에 별도의 type을 명시 하지 않아도 컴파일러가 타입을 추론해준다.

- val : 초기화 후 변경 불가능하다. 자바의 final과 같음 (기본적으로 val로 선언하고 필요시에만 var을 사용)
   var : 변경가능한 참조

 

문자열 템플릿

- 자바처럼 문자열을 이어붙이지 않아도 사용 가능

- 문자열 뿐 아니라 ${} 내부에 식 사용도 가능

// 문자열 템플릿
val name = "wedul"
println("name : $name")

 

Collection

val set = setOf(1,2,3)
val listCollection = listOf(1,2,3)
val mapCollection = mapOf(1 to "one",2 to "two")

 

 

확장 함수, 확장 프로퍼티

- 클래스의 멤버 메서드인 것 처럼 호출하지만 실제로는 클래스 외부에 선언된 함수

- 확장함수를 사용한다고 해도 해당 클래스 내부에 private, protected 멤버에 접근은 불가능하기 때문에 클래스 자체의 캡슐화를 깨지 않는다.

- 선언에 사용되는 클래스를 receiver type이라 하고 확장 함수 내에서 호출하는 대상이 되는 값을 receiver object라고 한다.

- 확장 함수를 정의해도 프로젝트 내부에서 모두 사용이 가능한 것은 아니기 때문에 import해서 사용해야 한다. 

package extendfunction

// String은 receiver type
// this, "wedul"는 receiver object
fun String.lastChar(): Char = this.get(this.length - 1)

fun main (args: Array<String>) {
    val input: String = "wedul"
    println(input.lastChar())
}



// 외부 코드에서 확장함수 호출을 하기 위해서는 확장함수를 임포트 해야한다.
import extendfunction.lastChar

// 확장 함수 사용
println("wedul".lastChar())

- 확장 함수는 parent - child 관계라고 해도 각 클래스 마다 개별 함수이다. override가 되지 않는다.

open class Father {
}

class Son: Father() {
}

fun Father.speak(): String = "study";
fun Son.speak(): String = "No";

val father = Father()
val son = Son()

println(father.speak())
println(son.speak())

// 결과
study
No

 

 

로컬 함수

- 같은 함수 내부에서 자주 사용하는 기능을 함수안에 함수를 만들어서 관리할 수 있다.

// 데이터가 늘어날 때마다 함꼐 늘어나는 validation
fun createFather(father: Father) {
    if (father.name == "") {
        throw IllegalArgumentException("Not Valid Argument")
    }
    
		if (father.school == "") {
        throw IllegalArgumentException("Not Valid Argument")
    }

		save(father)
}

// 로컬 함수로 깨끗하게 정리
fun createFather(father: Father) {
    fun validation(input: String) {
        if (input == "") throw IllegalArgumentException("Not Valid Argument")
    }

    validation(father.name)
    validation(father.school)
    save(father)
}

 

 

클래스

- 코틀린은 자바와 달리 기본 접근지시자가 public이므로 대부분 생략해도 된다. 

- 코틀린 클래스에서 val, var을 통해 property를 명시할 수 있는데 이를 사용하여 getter, setter없이 접근해서 사용할 수 있다.

val em = Wedul("wedul", 21);
// getter
println(em.name);
println(em.age)

// setter (val 타입이라 setter 안됨)
em.name = "babo"
// var type은 setter 가능
em.address = "seoul kangdong"
// custom property 호출
println(em.isOldMan)


class Wedul(val name: String, val age: Int, var address: String = "seoul") {   
    // custom property
    val isOldMan: Boolean
        get() {
            return age > 20
        }
}

- 코틀린은 클래스와 메소드는 기본적으로 final로 선언되기 때문에 클래스의 상속을 열어주기 위해서는 클래스 앞에 open변경자를 붙여주어야 한다. 메소드도 마찬가지로 override를 허용하기 위해서는 open 키워드를 붙여줘야 한다.

package family

open class Father(val name: String,
                val age: Int,
                val school: String): Faily {

    // 상속 가
    open fun salary(): Int {
        return 10000000
    }

}

- abstract를 통해 추상 클래스 선언이 가능하고 메소드에도 붙여줄 수 있으며 추상클래스에서도 기본적으로 메소드의 선언은 final이므로 별도 override를 허용하고 싶은 경우 open 키워드를 붙여줘야 한다.

- 클래스를 캡슐화하거나 코드 정의한 곳을 가까이 두고 싶은 경우 중첩 클래스를 사용하는데 코틀린에서는 클래스 내부에 클래스 선언시 기본적으로 static 클래스가 되기 때문에 내부 클래스로 만들기 위해서는 inner 지시자를 붙여야 한다.

내부 클래스 생성 별 차이

 

- 코틀린은 primary 생성자와 secondary 생성자를 구분하고 다양한 형태의 생성자를 만들 수 있다. 

1) primary 생성자

// 초기화 블록
class Father(_name: String) {
	val name: String
	init {
		name = _name
	}
}

// 자동 초기화
class Father(_name: String) {
	val name: String = _name
}

// 간략화
class Father(val name: String) {
}

// 인스턴스 화를 막기 위한 
class ProtectInitializeClass private constructor() {}

 

2) secondary 생성자

// 부모 클래스
open class SecondaryParentClass(salary: Int)

// 자식 클래스
class SecondaryConstructor: SecondaryParentClass {

    constructor(salary: Int,name: String): super(salary) {

    }

    constructor(salary: Int, name: String, age: Int): super(salary) {

    }

}

- 싱글톤 객체 생성을 object를 붙여서 쉽게 할 수 있다. 

object class SingleTonTest

- companion object를 사용해서 클래스의 static 메소드를 생성 할 수 있다. 

class LocalFunction {

    fun createFather(father: Father) {
        fun validation(input: String) {
            if (input == "") throw IllegalArgumentException("Not Valid Argument")
        }

        validation(father.name)
        validation(father.school)
    }

}

 

 

인터페이스

- 자바 인터페이스와 다르게 인터페이스에 property가 들어갈 수 있다.

- 자바는 @Override 애노테이션을 꼭 붙이지 않아도 되지만 코틀린은 함수 선언 전 override를 꼭 기재하지 않으면 컴파일 에러가 발생된다.

- 자바와 다르게 상속, 구현 모두 :를 사용하여 확장한다.

// Father는 상속 Family는 인터페이스
class Son(name: String, age: Int, school: String) : Father(name, age, school), Faily {
		override fun runRole() {
        println("I'm son, so cute")
    }
}

// interface
interface Faily {
    // default method    
    fun runRole() = println("")
}

- 인터페이스에는 final, open, abstract 등의 키워드를 사용할 수 없다.


 

 

접근 지시자

- 자바와 달리 기본 접근 지시자는 public이고 코틀린에서는 단순히 패키지를 네임스페이스 관리를 위해서 존재 할 뿐이기 때문에 패키지를 구분하는 default는 없다.

- 모듈 별로 접근이 가능한 internal이라는 타입이 생겼다. 

- private를 최상위에 선언하는지 클래스 내부에 선언하는지에 따라서 클래스내부에서 접근이 가능하고 같은 파일안에서만 접근이 가능하다.

- public에서 internal로 선언된 것을 접근을 못하게 하여 본인 보다 낮은 가시성을 가진 데이터에 대한 접근을 차단한다.

본인보다 낮은 가시성을 가진 데이터에 접근이 차단되는 모습


 

 

with, apply

- 특정 객체의 이름이 지속적으로 호출되어야 할 때 사용

- with는 람다 코드를 실행한 결과로 람다식에 있는 마지막 식의 값이다. 대신 apply는 전달된 객체(수신 객체)를 반환한다.

// 기존 코드 
// 지속적 반복
fun createFullAlphabet(): String {
    val result = StringBuffer()
    for (text in 'A'..'Z') {
        result.append(text)
    }
    return result.toString()
}


// with
fun createFullAlphabet(): String {
    return with (StringBuffer()) {
        for (text in 'A'..'Z') {
            append(text)
        }
        toString()    
    }
}

// apply
fun createFullAlphabet(): String {
    return StringBuffer().apply {
        for (text in 'A'..'Z') {
            append(text)
        }
    }.toString()
}

 

 

Type

1) nullablity type

- 컴파일 시 널이 될 수 있는지 여부를 체크해서 런타임시에 발생 할 수 있는 예외의 가능성을 줄일 수 있다.

- nullablilty하지 않은 상태에서 null 데이터를 넘기게 되면 컴파일러에 의해서 에러가 발생한다. 

- nullablilty하게 하기 위해서는 타입 이름 뒤에 ?을 붙인다.

fun main(args: Array<String>) {
    getStrLen(null)
}

fun getStrLen(s: String?): Int {
    return s?.length ?: -1
}

- 자바에서는 npe를 제대로 다루기 어렵고 실수로 처리를 못했을 경우 런타임 시 문제가 발생한다. 그래서 코틀린에서는 nullablilty를 사용하여 별도의 null 처리를 진행하고 ?. !! 와 같이 데이터를 다루는데 있어서 불편함이 없도록 제공해준다.

  • ?. 키워드
    • ?.은 null 검사와 메소드 호출을 한번에 해주는 것으로 if (null != str) str.toString else null과 같은 문법이다.
    • 정리하면 실행 객체가 null이 아닐 때만 실행하고 null이면 null 값 그대로를 반환한다.
    • fun main(args: Array<String>) {
          println(getStrThreeStr("wedul"))
          println(getStrThreeStr(null))
      }
      
      fun getStrThreeStr(s: String?): String? {
          return s?.substring(0, 3)
      }
      
      // 결과
      wed
      null
  • 엘비스 연산자 ?:
    • null 대신 사용할 디폴트 값을 지정할 때 사용
    • fun main(args: Array<String>) {
          println(getStrThreeStr("wedul"))
          println(getStrThreeStr(null))
      }
      
      #1. 엘비스 표현으로 기본값 전달
      fun getStrThreeStr(s: String?): String? {
          return s?.substring(0, 3) ?: "null 이다 임마."
      }
      // 결과 
      wed
      null 이다 임마.
      
      #2. throw 처리도 가능하다.
      fun getStrThreeStr(s: String?): String? {
          return s?.substring(0, 3) ?: throw Exception("null 이라니...")
      }
      // 결과
      wed
      Exception in thread "main" java.lang.Exception: null 이라니...
      	at com.search.core.KotlinTestKt.getStrThreeStr(KotlinTest.kt:10)
      	at com.search.core.KotlinTestKt.main(KotlinTest.kt:6)
  • 캐스팅
    • as? 를 사용해서 변환을 시도하면 변환이 가능하면 변환이 되고 아니면 null을 반환한다.
    • fun main(args: Array<String>) {
          println(castingStrToInt(null))
          println(castingStrToInt("123"))
      }
      
      fun castingStrToInt(s: String?): Int? {
          return s as? Int
      }
      
      // 엘비스로 기본값 지정도 가능
      fun castingStrToInt(s: String?): Int? {
          return s as? Int ?: 456
      }
  • null 아님 단언 !!
    • nullablilty한 값을 받을 때 null이 아니라고 단언 할 수 있다.
    • fun main(args: Array<String>) {
          println(castingStrToInt(null))
          println(castingStrToInt("123"))
      }
      
      fun castingStrToInt(s: String?): Int? {
          val changeStr: String = s!!
          return changeStr as? Int ?: 456
      }
  • let 함수
    • null이 아닌 경우에만 다름 lambda에 인자로써 넘길 수 있다. null인 경우에는 실행이 되지 않는다.
    • null을 임시로 대입하고 ?.let을 호출한 경우에는 실행이 안된 것을 알 수 있다.
    • fun main(args: Array<String>) {
          var input: String? = "123"
          input?.let { printStr(it) }
      
          input = null
          input?.let { printStr(it) }
      }
      
      fun printStr(s: String){
          print(s)
      }
      
      // 결과
      123
  • Any 타입
    • 자바에서는 최상위 타입이 Object 이지만 코틀린에서는 Any 타입이다
    • Any는 null이 될 수 없다.
    • 내부에서 Any는 java.lang.Object에 대응하기에 자바 메소드에서 Object를 인자로 받거나 반환하면 코틀린에서는 Any로 취급한다.
  • Unit 타입
    • 자바에서 Void타입을 코틀린에서는 Unit를 사용한다.
    • 자바의 void와는 달리 Unit는 모든 기능의 일반적인 타입으로 써 타입의 인자로써 사용이 가능하다.
  • Nothing 타입
    • 성공적으로 값이 반환되지 않는 상태일 때 사용된다. 이 타입을 명시함으로써 컴파일러에게 정상적인 종료 상태가 아님을 알려줄 수 있다.
    • fun main(args: Array<String>) {
          exit()
      }
      
      fun exit(): Nothing {
          throw Exception()
      }
    • Nothing은 아무 값도 포함하지 않는다.
    • Nothing은 함수의 반환 타입이나 반환타입으로 쓰일 타입 파라미터로 사용 가능하다.

 

반응형