개발자 끄적끄적

코틀린(Kotlin) 기초 본문

안드로이드 프로그래밍

코틀린(Kotlin) 기초

햏치 2023. 3. 11. 02:11

<Kotlin>
- 편리함을 추구하는 언어
- 간결함을 추구하는 언어
- 명령형 언어, 객체지향 언어에 함수형 언어 개념

<코틀린의 특징>
- 실용성
  - 이미 검증되고 많이 사용되는 언어 기능을 채택

- 간결성
  - 타입 추론(타입을 명시하지 않아도 된다), getter/setter

- 안정성(Null Safety) 
- Interoperability(자바와 100%호환)

- 다양한 플랫폼 지원
  - JVM 기반의 서버, 안드로이드, 코틀린 자바스크립트, 코틀린-native IOS, MacOS, AndroidNDK, Windows, Linux

- 정적 타입 언어
  - statically typed lang. (java, kotlin)
  - dynamically typed lang. (python)

- 함수형 언어(Functional Lang)
- 오픈 소스(코틀린의 새로운 기능들을 만들수도 있다)


<패키지(Package)>
- 자바와 다르게 코틀린은 소스 파일의 위치와 이름을 정하는 데에 제한이 없다
  - 한 파일에 여러 개의 클래스를 넣을 수 있다
  - 패키지 구조와 디렉토리 구조가 일치할 필요가 없다

package com.example.mykotlin
/*이 파일이 com/example/mykotlin/에 있을 필요가 없다
  이 파일의 이름은 마음대로 지으면 된다*/
fun main(){
  println("Hello, Kotlin")
  //코틀린은 세미콜론이 없어도 된다
}
//자바와 달리 main()함수가 독립적으로 존재


<변수와 값(Variables & Value)>
- 코틀린은 변수를 선언하는 키워드가 val, var
- 변수 선언 키워드 다음에 변수 명과 콜론, 그리고 타입을 쓴다
- val
  - val로 선언된 변수(?) 불가 -> 초기화할 때 값을 바꿀 수 없다
  - 변수라고 부르지 않고 값이라고 부른다
  - val 자체가 value의 줄임말

- var
  - var로 선언된 변수는 변경 가능
  - variable의 줄임말

val a: Int = 10 
var b: Int = 12
//a=11// compile error
b = 13 // OK

*val a = 객체
 객체안에 들어있는 멤버변수의 값들을 바꾸는 것들은 가능하다
 하지만 a에 들어있는 값을 바꾸는 것은 불가능


<타입 추론(Type Inference)>
- 코틀린 변수를 선언할 때 타입을 쓰지 않아도 대입되는 값에 따라 컴파일러가 알아서 타입을 정해준다


<타입(Types)>
- 코틀린은 자바와 달리 원시타입(Primitive types)와 wrapper type을 구분하지 않는다
  - Byte, Short, Int, Long, Float, Double, Char, Boolean
  - UByte, UShort, UInt, ULong
- 코틀린은 서로 다른 타입의 값을 자동으로 변환해주지 않기 때문에 명시적으로 값 변환을 해줘야 한다

val i = 10
val i2 : UInt = 10U
//vale lov : Long = i// Compile Error
val lov2 : Long = i.toLong() //명시적 값 변환(i.toLong()은 멤버함수 메소드)


<if문>
- 자바와 문법은 비슷하지만 코틀린에서 if는 statement가 아니라 expression(식)으로 사용 가능하다
- 즉, if문의 결과로 어떠한 값을 받을 수 있다
val x = if(a>10) "big" else "small"


<Any 타입, 타입 체크(is)>
- 자바의 Object와 비슷한 개념으로 코틀린에는 Any가 있다
- 코틀린은 우너시타입(Primitive types)에 대해서도 Any가 가능한다
- 타입 체크
  - A is 타입

val t : Any = 10 
println((t is Int)) //true


<타입 변환(as)>
- 타입 A as 타입B -> A타입을 B타입으로 바꾼다(A, B는 서로 상속관계)

fun main(){
  fun test(obj : Any){
    if(obj is Int) println("obj is Int")
    if(obj is String) println("obj is String")

    println("print obj as string > ${(obj as? String ? : "")}"})
  }
test(1)
// obj is Int
// print obj as string > ""
test("string")
//obj is String
//print obj as string > strings


<문자열(String)>
- 자바에서 문자열 중간에 변수 값을 추가하고 싶으면 '+' 연산자를 사용했지만,
  코틀린은 문자열 내부에 변수(심지어 expression(수식)까지도)를 넣을 수 있다
- 3중 따옴표를 쓰는 문자열 -> ' """ '
  - 어떤 문자든 이스케이프 없이 그대로 쓸 수 있다
  - 여러 행의 문자열도 가능하다

var version = "1.3.50"
val javaStyle = "Hello, Kotlin" + version + "!" -(1)
val kotlinStyle = "Hello, Kotlin ${version}!" -(2)
-> (1), (2)는 동일

val num = 10
println("val num is equal to 10 : ${num==10}")

println("""\$""") -> \$
println("\$") -> $


<비교 연산(== or ===)>
- 자바와 달리 '객체 내용' 비교에 '==' 연산자를 사용하고, '객체 레퍼런스(reference) 비교'에는 '==='연산자를 사용한다
  - 자바에서는 객체 내용 비교에 equals()를 사용
- 문자열 비교도 당연히 '=='연산자를 쓸 수 있다

data class MyClass(val a:Int, val b:String)
//data class auto genderates equals/HashCode/toString/copy

fun main(){
  val str1 = "Hello, Kotlin"
  var str2 = "Hello, Kotlin"
  val class1 = MyClass(10, "class1")
  val class2 = MyClass(10, "class1")
  val class3 = MyClass(20, "class1")
//3개의 객체들의 reference는 다르다

  println(str1==str2) //true(문자열 비교)
  println(class1==class2) //true(문자열 비교)
  println(class==class3) //false(문자열 비교)
  println(class1===class2) //false(reference가 다르기 때문에)


<배열(Array)>
- 배열은 arrayOf(), arrayOfNulls(), emptyArray()로 생성

fun main(){
  val intArrays = arrayOf(1,2,3,4,5) //형 변환(Int)을 하지 않아도 된다
  val strArrays = arrayOfNulls<String>(5) //5개짜리의 배열(타입 지정 - String)
  val dbArrays = emptyArray<Double>() //비어있는 배열에 대한 타입지정 - Double

  println(intArrays[0]) //배열 인덱스에 해당하는 값
  intArrays[0] = 10 //val로 정의된 배열이지만, 배열 내의 값은 변경이 가능하다
  println(intArrays[0])

  for(s in strArrays){
   print("$s, ")
  }
  println("")

  println(dbArrays.size) //배열의 크기
}


<for and Iteration>
- for문은 iterator와 같이 사용하는 것이 편리
- 특정 범위의 iterator를 만들기 위해 Range를 사용할 수 있다
  - Range는 시작과 끝 숫자(또는 문자)를 ...로 연결
  - ex) 1..10, 'a'..'z'('.' - 2개)

func main(){
  val array = arrayOf("Hello", "This", "is", "Kotlin")

  for(a in array) //interator of array
    print("$a ") 
  println("") //Hello This is Kotlin

  for((idx, a) in array.withIndex()) //with index(값뿐만 아니라 인덱스까지)
    print("($idx, $a)")
  println("") //(0, Hello) (1, This) (2, is) (3, Kotlin)

  for(i in 1..10) //with range
    print("$i")
  println("") //1 2 3 4 5 6 7 8 9 10

  for(i in 1..10 step 2) //with range and step
    print("$i")
  println("") // 1 3 5 7 9(2씩 점프)

(10 downTo 1).forEach{ //with lambda(downTo 거꾸로)
    print("$it") //it은 default인자
  }
  println("") //10 9 8 7 6 5 4 3 2 1 

//when we use 'while'
var i = 0
while(i<10){
  i++
  print("$i")
  }
println("") //1 2 3 4 5 6 7 8 9 10
}


<Function>
- fun 함수이름(매개변수 리스트) : 리턴타입{함수 정의}
- fun 함수이름(매개변수 리스트) = expression //함수의 내용을 한 줄짜리로 표현 가능할 떄
  - 함수 내용이 expression인 경우, 리턴타입이 추정 가능하므로 생략된다
- Default argument 지원 - 인자를 주지 않았을 때 사용
- Named argument 지원 - 인자를 줄 때 매개변수 이름과 값을 줄 때 사용

fun myFunc(arg1: Int, arg2: String = "default", arg3: Int = 10)
{
  println("$arg1, $arg2, $arg3")
}

fun sumFunc(a: Int, b: Int) = a+b

fun main(){
  myFun(1, "hello", 5) //1, hello, 5
  myFun(arg1=2) //2, default, 10
  myFunc(2, arg3=5) //2, default, 5

  //local function
  fun localFunc(a: Int) : Int{
    return a + 10
  }
  println(sumFunc(1,2)) //3
  println(localFunc(10) //20
  } 


<Funciton - Lambda
- 람다(lambda) 함수는 익명 함수
  - 람다 함수는 {인자리스트 -> 함수 내용}과 같은 형식으로 작성한다
  - 인자가 한 개이고 타입이 생략 가능한 경우 디폴트 인자를 써서 인자리스트 부분을 생략 가능
  - 람다함수는 따로 return을 따로 쓰지 않기 때문에 expression형태(x+y)로 표현한다

val sum = {x: Int, y: Int -> x+y} //람다함수 정의
println(sum(10, 20))

fun lambdaTest(a: (Int) -> Int) : Int{ //(int)라는 함수로 되어있는 인자 하나를 받아서 Int를 리턴하는 타입
  return a(10)
}

lambdaTest({x -> x+10}) //즉, {x: Int -> x+10}
lambdaTest({it + 10}) //인자 타입 생략 가능, 인자가 1개뿐이므로 인자리스트 생략
lambdaTest{it + 10} //람다가 함수의 유일한 인자이면, 함수의 괄호를 생략할 수 있다(lambdaTest함수의 인자{it+10}로 주어진다)



<Function - Lambda(예제)>

data class MyClass(val a: Int, val b: String)

fun lambdaTest(a: (Int) -> Int) : Int{
}

fun main(){
  val sum = {x: Int, y: Int -> x+y}
  println(sum(10, 20))
  
  val array = arrayOf(MyClass(10, "class1"), MyClass(20, "class2"), MyClass(30, "class3"))
  println(array.filter({c: MyClass -> c.a < 15})) //[MyClass(a=10, b=class2)]
  //filter는 함수를 인자로 받아 각 원소의 경우에 함수의 조건에 따라 실행을하고 True 값만 쓴다
  //c는 MyClass를 받아서 MyClass안의 a라는 속성의 조건이 15보다 작은 값
  //람다가 함수의 인자의 마지막으로 사용되면 함수의 괄호 밖으로 뺄 수 있다

  array.filter(){c: MyClass -> c.a < 15}
  //람다가 함수의 유일한 인자이면, 함수의 괄호를 생략할 수 있다

array.filter{c: MyClass -> c.a <15}
//인자 타입 생략 가능한 경우

array.filter {it.a < 15} 
//디폴트 매개변수 이름으로 it을 사용할 수 있고, 일반적으로 많이 사용하는 형태이다

print(lambdaTest{it + 10})

  val title = "Num:"
  val list = ListOf(1,2,3,4)
  list.forEach{println("$title $it)} //access title in outside of the lambda
}


<Function - Lambda(예제2)>
- 안드로이드 프로그래밍에서 흔히 사용되는 예

1- val btn = findViewById<Button>(R.id.button1)
2- btn.setOnclickListener{
3-   Toast.makeText(this. R.string.button_clicked_msg, Toast.LENGTH_SHORT).show()
}
- 3번줄은 2로 등록을 해놓고 3번줄이 호출이되는 건 버튼이 눌릴 때 호출이 된다


<When>
- switch문과 비슷하지만 케이스마다 타입이 달라도 되며 expression으로 사용할 수 있다

fun test(arg : Any){
  when(arg){
    10 -> println("10")
    in 0..9 -> println("0<x<=9")
    is String -> println("Hello, $arg") -> 'is' type checking하는 것
    !in 0..100 -> println("x < 0 and x>100")
    else -> {
      println("unknown")
    }
  }
}


<Null Safety>
- Null이 가능한 타입과 불가능한 타입 구분
  - 타입 이름 뒤에 '?'을 붙이면 Nullable 타입

import java.lang.NullPointerException

fun testNull(arg : String?) //String? -> Null인 값이 들어올 수 있다
{
  println(arg?.uppercase()) //arg? return null if arg is null
  println(arg?.uppercase()? : "-")
    //Elvis(?:) return right operand("-") if left operand is null
}

fun main(){
  var nullable: String? = null
  var nonNullable: String = "nonNullable"

  //nonNullable = null // compile Error

  testNull(nonNullable) //NONNULLABLE NONNULLABLE
  testNull(nullable) //null -

  //nullalble.uppercase() //Compile Error
  nullable?.uppercase() //return null because nullable is null
  try{
    nullable!!.uppercase() //cause Exception!
  }catch(e : NullPointerException){
    println("NullPointerException")
  }
}


<Exception>
- try, catch문은 자바와 거의 같으며, try, catch문을 expression을 사용할 수 있다
- 코틀린은 자바와 달리 모든 예외를 catch하게 강제하지 않느다

fun main(){
  val x = try{
    parseInt("10")
  }catch(e : NFE) {
    0
  }
   println(x) //정상적으로 parsing되면 x에 10이 들어가고 오류가 나면 0이 들어간다
}


<Collections>
- Array(배열의 크기는 변경할 수 없지만 값은 변경가능), List, Set, Map(immutable : 내용 변경 불가)
- ArrayList(배열의 크기와 값을 변경할 수 있다), MutableList, MutableSet, MutableMap(mutable)

val array = arrayOf(1,2,3) 
val arrayList = array.toMutableList() //arrayListof(1,2,3)

val list = listOf(1,2,3)
val mutableList = list.toMutableList() //mutablsListOf(1,2,3)

val set = setOf(1,2,3,3)
val mutableSet = set.toMutableSet() //mutableSetOf(1,2,3,3)

val map = mapOf("one" to 1, "two" to 2, "three" to 3)
val mutableMap = map.toMutableMap() //mutableMapOf("one" to 1, "two" to 2, "three" to 3)


<Collections에 대한 iteration>
- 빈 Collection 생성, iteration

fun main(){
  val e_array = arrayListOf<String>()
  val e_list = mutableListOf<Int>()
  val e_set = mutableSetOf<Int>()
  val e_map = mutableMapOf<String, Int>()

  e_array.add("1")
  e_list.add(1)
  e_set.add(1)
  e_map["one"] = 1

  for(a in e_array) print("$a")
  for(a in e_list) print("$a")
  for(a in e_set) print("$a")
  for((k, v) in e_map) print("$k - $v")
}

'안드로이드 프로그래밍' 카테고리의 다른 글

안드로이드 레이아웃(LayOut)  (0) 2023.03.30
코틀린FP  (0) 2023.03.23
코틀린OPP  (0) 2023.03.18