UI 구성하는 요소

  • 위젯
    • 정보를 출력, 입력 받기 위한 UI 구성 요소
    • TextView, Button, EditText, Checkbox
  • 레이아웃
    • 사용자 인터페이스에 대한 시각적 형태를 계층적 구조로 정의
      • 화면에 위젯을 어떻게 배치 할 건가를 결정하는 것
      • 레이아웃(부모)과 포함되는 UI요소들을 부모-자식 관계로 나타내는 계층적 구조
    • XML 파일로 작성하고, 소스 코드에서 로드하여 사용함
    • LinearLayout, ConstraintLayout, FrameLayout
  • View 클래스
    • UI요소들은 View를 상속하여 구현
      • 화면에 표시 가능한 사각형 영역
      • 각각의 뷰는 알아서 자신의 내요응ㄹ 그리고 이벤트 해결
      • ViewGroup은 View이지만 다른 View를 포함 ex:(레이아웃)
      • View의 속성이 곧 UI의 속성

'Development > Kotlin&Android' 카테고리의 다른 글

프래그먼트(Fragment)  (0) 2023.04.29
액티비티와 인텐트  (0) 2023.04.13
Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 2  (0) 2023.03.02

수학적 함수와 명령형 프로그래밍에서 사용되는 함수

  • 명령형 프로그래밍의 함수
    • 프로그램의 상태 값을 바꿀 수 있는 부수 효과가 생길 수 있음
    • 참조 투명성이 없고, 같은 코드라도 실행되는 프로그램의 상태에 따라 다른 결과값이 나옴
  • 함수형 프로그래밍의 함수(수학적 함수)
    • 함수의 출력 값은 함수에 입력된 인수에만 의존
    • 인수 x에 같은 값을 넣고 함수 f를 호출하면 항상 f(x)라는 결과가 나옴(참조 투명성)
    • 부수 효과를 제거하면 프로그램 동작 이해와 예측이 쉬움

함수형 프로그래밍 기본 요소

  • pure functino
    • 부수 효과(side-effect)가 없는 함수
    • thread-safe하여 병렬 계산 용이
  • anoymous function: 익명 함수 (람다 함수)
    • 코틀린에서는 x → x*x
  • higher-order functino: 고차(고계) 함수, 함수를 인자나 리턴으로 다루는 함수
    • 코틀린에서는 (1.10).map{it * it}

+& 코틀린은 순수한 함수형 언어가 아님, 함수형 언어의 요소 뿐 아니라 명령형 언어, 객체 언어 패러다임을 모두 가지고 있음

Lambda

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

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

fun main()
{
    val sum = {x:Int, y:Int -> x+y}
    println("sum = ${sum(10,20)}")

    val array = arrayOf(MyClass(10,"class1"),MyClass(20,"class2"),MyClass(30,"class3"))
    println(array.filter({c:MyClass -> c.a < 15}))

    array.filter() {c:MyClass -> c.a < 15 }
    array.filter { c -> c.a <15 }
    // 람다 파라미터가 하나일 경우 it 으로 생략이 가능함
    array.filter { it.a < 15 }

    print(lambdaTest { it+10 })

    val title = "Num:"
    val list = listOf(1,2,3,4)
    list.forEach{ println("$title $it")}
}
  • 어원: 람다 대수
  • 이름이 없는 함수
  • 형식: {파라미터 → 함수 바디}
    • {x: Int, y: Int → x+y}
    • 함수 바디에서 마지막 수식(expression)이 리턴 값이 됨
  • Lambda에서 로컬 변수 참조
    • val, var 구분 없이 모든 로컬 변수 참조가 가능함

Collection filter, map, groupBy와 lambda

data class Student(val name: String,val age:Int)

fun main(){
    val data = listOf(Student("Jun",21),Student("James",25)
    ,Student("Tom",21),Student("Jane",23),Student("John",23))
    println(data.filter { it.age >=22})
    println(data.map{it.age - 20})
    println(data.filter { it.age>=22 }.map (Student::name))
    // data.filter{it.age>22}.map{it.name}
    println(data.groupBy { it.age })

    val words = arrayOf("hello","hi","hot","aple","orange","access","order","about")
    println(words.groupBy { it.first() })
}
  • 함수형 프로그래밍에서 Collection을 다루는 방법
  • lambda가 편리하게 사용됨
  • filter, map
  • filter: 특정 조건을 만족하는 원소만 포함하는 Collection을 생성/리턴
    • filter에 주어진 람다를 모든 원소에 수행하여 true를 리턴하는 경우만 모음
    • filter에 넘겨주는 람다는 Boolean을 결과로 하는 수식이어야 함
  • map: 모든 원소에 대해 특정 연산을 수행한 결과를 모아서 Collection을 생성/리턴
    • map에 주어진 람다를 모든 원소에 대해 수행하고 그 결과를 모음
  • groupBy: 주어진 조건에 따라 Collection을 그룹으로 나눈 후 map을 생성/리턴
  • 참고: Collection은 언어 문법이 아니고 표준 라이브러리임

Collection all, any, count, find와 lambda

fun main(){
    val nums = arrayOf(-5,-4,-3,-2,-1,0,1,2,3,4,5)

    println(nums.all { it is Int })
    println(nums.all {it >0})
    println(nums.any{it>0})
    println(nums.count{it>0})
    println(nums.find{it>0})
}
  • all, any, count, find
  • all: 모든 원소가 특정 조건을 만족하면 true, 그렇지 않으면 false
  • any: 한 원소라도 특정 조건을 만족하면 true, 그렇지 않으면 false
  • count: 특정 조건을 만족하는 원소의 갯수를 리턴
  • find: 특정 조건을 만족하는 가장 처음 원소를 리턴

Collection flatMap과 lambda

fun main(){
    val lsit = listOf("abc","cde","efg")

    println(lsit.flatMap { it.toList() })
    println(lsit.flatMap { it.toList() }.toSet())

    class Classes(val name:String, val students:List<String>)
    val classes = listOf(Classes("Cprog", listOf("james","john","greg")),
        Classes("OS", listOf("james","john","jane","ton")),
        Classes("Net",listOf("john","jane","alex","sam")))

    println(classes.flatMap { it.students}.toSet().sorted())
}
  • 중첩된 Collection을 하나의 리스트로 생성/리턴
  • flatMap에 주어진 람다는 iterable 객체를 리턴
    • 이 iterable을 모두 연결하여 하나의 List로 만든다.

Collection asSequence

fun main(){
    val hugeNums = (1..1000000).toList()

    val elapsedTime = measureTimeMillis {
        hugeNums.map{it*it}.filter { it>10 }.map { it*it }
    }
    val elapsedTime2 = measureTimeMillis {
        hugeNums.asSequence().map { it*it }.filter { it>10 }.map { it*it }
    }
    val elapsedTime3 = measureTimeMillis {
        hugeNums.asSequence().map { it*it }.filter { it>10 }.map { it*it }.toList()
    }

    println("elapsedTime = ${elapsedTime}")
    println("elapsedTime2 = ${elapsedTime2}")
    println("elapsedTime3 = ${elapsedTime3}")

}
  • filter, map 등의 함수를 부를 때, 바로 다른 리스트를 생성하게 됨
  • 이런 함수를 매우 큰 데이터에 대해 여러 번 이어서 사용하면 매우 느려질 것임
  • 이런 경우 Sequence를 사용하면
    • 리스트 생성을 최대한 늦추게 하는 방법임
    • 실제 리스트 생성 작업을 최대한 늦추기 때문에 lazy 연산이라고 보통 부름
  • Seqence를 사용하여 연산을 끝낸 후에, 다시 toList()로 Collection을 바꾸어 사용함

lambda와 SAM

fun interface doInterface{
    fun doIt()
}

fun doSomething(di:doInterface)=di.doIt()

fun main(){
    doSomething(object :doInterface{
        override fun doIt() {
            println("Java-like way")
        }
    })

    doSomething(doInterface { println("SAM") })
    // 익명 함수 생략
    doSomething{ println("SAM 1")}
    val doi = { println("SAM 2")}
    doSomething(doi)
}
  • SAM: Single Abstract Method의 줄임말, 인터페이스가 하나의 메소드만 가진 경우
    • View.OnClickListener, Runnable 등 많은 인터페이스가 하나의 메소드만 가짐
  • 코틀린에서 이런 SAM인 경우 무명 클래스 인스턴스를 만드는 것이 아니라 lambda로 처리할 수 있음

Scope 함수

fun main(){
    val r1 = "hello".let {
        println("it = ${it}")
        it.length
    }
    val r2 = "hello".run {
        println("this = ${this}")
        this.length
    }
    val r3 = "hello".also {
        println("it = ${it}")
    }.length

    println("r1 = ${r1}")
    println("r2 = ${r2}")
    println("r3 = ${r3}")
}
fun main(){
    val str = with(StringBuilder()){
        // this. 생략
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
        toString()
    }
    println(str)

    val str2 = StringBuilder().apply{
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
        append("Hello, ")
    }.toString()
    println("str2 = ${str2}")
}
  • 객체의 이름을 반복하지 않고 그 객체에 대해 여러 연산을 수행할 수 있음
  • 종류: let, run, with, apply, alse
    • 주의: with 함수는 주어진 객체를 이용하여 블록 안에서 수행하는 작업을 처리한 후, 마지막 표현식의 값을 반환한다. (unit)
    • 주의: apply 함수는 주어진 객체를 이용하여 블록 안에서 수행하는 작업을 처리한 후, 객체 자신을 반환한다. (this)
  • 함수 인자로 lambda를 전달하는 데, 이 lambda 내에서 객체를 it으로 또는 this로 지칭
  • 함수의 리턴 값은 객체 자체 또는 lambda의 결과
  • 참고: Scope 함수는 언어 문법이 아니고 표준 라이브러리임

Collectino 연산자 오버로딩

data class Three(var x:Int, var y:Int, var z:Int):Iterable<Int>{
    operator fun get(idx:Int):Int{
        return when(idx){
            0 -> x
            1 -> y
            2 -> z
            else ->
                throw IndexOutOfBoundsException("Invaild index $idx")
        }
    }
    operator fun set(idx:Int, value:Int){
        when(idx){
            0 -> x = value
            1 -> y = value
            2 -> z = value
            else ->
                throw IndexOutOfBoundsException("Invaild index $idx")

        }
    }
    operator fun contains(value:Int) = (x==value||y==value||z==value)

    inner class MyIterator:Iterator<Int>{
        var curIdx = 0
        override fun next(): Int {
            val ret = this@Three[curIdx]
            curIdx++
            return ret
        }

        override fun hasNext(): Boolean {
            return curIdx <= 2
        }
    }

    override fun iterator(): Iterator<Int> = MyIterator()
}

fun main(){
    val three = Three(1,2,3)
    println("three[0] = ${three[0]}")
    println("three[1] = ${three[1]}")
    println("three[2] = ${three[2]}")
    println((3 in Three(1,2,3)))
    for (i in three)
        println(i)
}
  • []
    • get(idx), set(idx, value)
  • in
    • contains(value)
  • iterator
    • iterator Implementation
    • iterator method

가변 인자

fun main(){
    val list = listOf(1,2,3,4)

    val args = arrayOf("1","2","3","4")
    val list2 = listOf("1",*args)
    println(list2)
}
  • 가변 인자
    • varag를 매개 변수 이름 앞에 사용함
    • spread 연산자: *를 배열앞에 붙여서 가변 인자를 넘겨줄 수 있음

Infix Function

fun main(){
    // Any를 확장 하여 infix to를 정의한 것
    infix fun Any.to(other: Any) = Pair(this,other)
}
  • 함수 이름을 인자 중간에 넣어서 호출
  • 예) mapOf(1 to “one, 2 to “two”) 에서 to와 같은 함수
  • fun plus(1,2) → fun 1 plus 2

Destructuring declaration

data class Result(val result:Int, val status:String)
fun calcSomething(): Result{
    return Result(404,"Not Found")
}

fun main(){
    val (num,str) = 1 to "one"
    println("$num, $str")
    val collection = mapOf(1 to "one", 2 to "two")
    for((a,b)in collection){
        println("$a, $b")
    }
    val (result, status) = calcSomething()
    println("$result,$status")
}
  • 값 2개를 리턴 받아 2개의 변수에 대입

'Development > Kotlin&Android' 카테고리의 다른 글

액티비티와 인텐트  (0) 2023.04.13
안드로이드-레이아웃  (0) 2023.03.30
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 2  (0) 2023.03.02
Kotlin 문법 Part 1  (0) 2023.02.28

Class

class Animal(val name:String)

class Person(val firstName: String, tmpLastName:String){
    var lastName:String = tmpLastName
        get () = field.uppercase()
        set (value){
            field = "[$value]"
        }
}

fun main(){
    val a = Animal("Dog")
    println("Animal: ${a.name}")
    val p = Person("Sukung","Kim")
    // getter 호출됨
    println("Name: ${p.firstName} ${p.lastName}")
    // setter 호출됨 []이 붙여짐
    p.lastName = "KIM"
    println("Name: ${p.firstName} ${p.lastName}")

}
  • 단순한 클래스 정의
    • class 클래스_이름(생성자 인자 리스트)
    • 생성자 인자들 중 val/var이 붙은 것은 클래스의 속성이 됨
  • 자바와 다른점
    • 코틀린은 기본적으로 public class임
    • 객체를 생성할 때 new 키워드를 쓰지 않음
    • 속성 접근 메소드를 자동으로 만들어주며, 커스텀 접근 메소드를 만들 수도 있음

Interface

interface ClickListener{
    fun onClick()
    fun onTouch() = println("touched")
}

class View(private val id:Int):ClickListener{
    override fun onClick() {
        println("clicked $id")
    }
}

fun main(){
    val v = View(10)
    v.onClick()
    v.onTouch()
}
  • 자바의 인터페이스와 비슷함
  • 구현이 있는 메소드 정의가 가능 하지만 자바처럼 default를 붙이진 않음
  • 인터페이스 구현 클래스의 메소드 구현에서 override를 반드시 적어주어야 함
    • 클래스 상속 받을 때 메소드 오버라이드 할 때에도 override를 반드시 명시해 주어야 함

기본적으로 상속, 오버라이드 금지

class NoInherit(val name:String)
// 컴파일 에러
//class Child2(val name:String):NoInherit(name)

open class Inherit(val name:String){
    fun first() = println("first")
    open fun second() = println("second")
}

open class child(name:String):Inherit(name){
    final override fun second() {
        println("override second")
    }
    open fun third() = println("third")
}

class GrandChild:child(""){
    override fun third() {
         println("override third")
    }
}
  • open 이라는 키워드가 붙어야 가능
  • 단 interface, abstract class는 기본적으로 open 키워드가 디폴트로 들어가 있음
  • class 는 기본적으로 상속, 메소드 오버라이드 금지
    • open class Test()
  • 메소드 오버라이드 접근 변경
    • final: 오버라이드 금지 기본적인 디폴트 설정임
    • open: 오버라이드 가능
    • abstract: 반드시 오버라이드 해야 함
    • override: 오버라이드 한 것을 명시

기본적으로 공개(public)

  • 코틀린은 클래스와 메소드 모두 기본적으로 public
  • 접근 변경자
    • public: 디폴트 옵션 자유롭게 접근 가능
    • internal: 같은 모듈에서만 접근 가능
    • protected: 상속 받은 클래스에서만 접근 가능
    • private: 같은 클래스 에서만 접근 가능

중첩 클래스

class Outer(var m:Int=0){
    // inner 키워드를 통해 내부 클래스 선언 
    inner class Inner{
        fun doSomething(){
            this@Outer.m++
            println("Inner.doSomething ${this@Outer.m}")
        }
    }
    // Outer 클래스의 속성에 접근이 불가능
    // 기본적으로 정적(static) 중첩 클래스로 만들어짐 
    class NoInner{
        fun doSomething(){
            println("NoInner.doSomething")
        }
    }
}

fun main(){
    // Outer클래스를 만들고 나서야 Inner 클래스를 만들 수 있음
    // 대신 Outer 클래스의 속성에 접근이 가능함
    val inner = Outer().Inner()
    inner.doSomething()
    // Outer클래스를 만들지 않고 바로 NoInner 클래스를 만들 수 있음
    val noInner = Outer.NoInner()
    noInner.doSomething()
}
  • 코틀린에서 중첩 클래스는 자바에서 static 중첩 클래스와 같음
  • 자바처럼 내부 클래스를 만들려면 중첩 클래스 앞에 inner 키워드 사용

봉인(sealed) 클래스

sealed class Expr{
    class Num(val value:Int): Expr()
    class Sum(val left: Expr, val right: Expr): Expr()
}

fun eval(e: Expr): Int =
    when(e){
        is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right)+eval(e.left)
    }

fun main(){
    val r = eval(Expr.Sum(Expr.Num(10),Expr.Num(10)))
    println(r)
}
  • 특정 클래스를 상속하는 클래스를 제한할 수 있다.

Constructor 생성자

class ConstEx1 constructor(tmp:Int){
    val prop: Int
    val prop2: Int
    init {
        prop = tmp
        prop2= tmp
    }
}

class ConstEx2 constructor(tmp:Int){
    val prop = tmp
}

class ConstEx3 constructor(val prop:Int)

open class ConstEx4(val prop:Int,val prop2:Int=0){
    constructor(_prop:Int,_prop2:Int,_prop3:Int): this(_prop+_prop3+_prop2){

    }
}

class ConstEx5(_prop:Int) : ConstEx4(_prop)

fun main(){
    ConstEx1(1)
    ConstEx2(2)
    ConstEx3(3)
    ConstEx4(4)
    ConstEx4(4,4)
    ConstEx4(4,4,4)
    ConstEx5(5)
}
  • Primary constructor: 클래스 이름 옆에 정의하는 생성자
  • Secondary constructor: 클래스 내부에 정의 하는 생성자

Property와 getter/setter

interface GetSetI{
    var prop: String
}

class GetSet(tmp:String):GetSetI{
    override var prop: String = tmp
        set(value) {
            field = value.substringBefore('@')
        }
        get() = field.uppercase()
    var prop2:Int =0
        private set
}

fun main(){
    val getset = GetSet("test@removed")
    println("getset.prop = ${getset.prop}")
    getset.prop = "ok@remeved"
    println("getset.prop = ${getset.prop}")
    // 컴파일 오류 getset.prop2 = 10
}
  • Property의 커스텀 setter와 getter를 정의할 수 있다.
    • public 속성에 대해 setter를 private으로 하면 클래스 밖에서는 get만 가능함
  • Interface에 property를 정의할 수 있으나, 이 property는 구현 클래스에서 반드시 오버라이드 해야 한다.

Data Class

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

fun main(){
    val str1 = "Hello, kotil"
    val str2 = "Hello, kotil"
    val class1 = MyClass(10,"class1")
    val class2 = MyClass(10,"class1")
    val class3 = MyClass(20,"class2")

    println(str1 == str2)
    println(class1==class2)
    println(class1==class3)
    println(class1==class2)

}
  • class 정의 앞에 data 키워드 추가
  • data class 는 equals/ hashCode/ toString / copy를 자동으로 만들어준다.

object 키워드

interface ClickListner{
    fun onClick()
}
object ClickListenerImp : ClickListner{
    override fun onClick() {
        println("clicked")
    }
}

fun setClickListner(listner: ClickListner) = listner.onClick()

class Touch(){
    val objectNums : Int
        get() = num
    init{
        num++
    }
    // Touch 클래스가 아무리 많이 만들어져도 해당 클래스는 한 개만 존재 싱글톤
    companion object{
        var num:Int = 0
    }
}

fun main(){
    setClickListner(ClickListenerImp)
    setClickListner(object :ClickListner{
        override fun onClick() {
            println("click2")
        }
    })

    Touch()
    Touch()
    println(Touch().objectNums)
}
  • 클래스 정의 없이 바로 객체를 생성하는 방법
    • 싱글톤을 만들거나
    • companion object를 만들거나
    • anonymous object를 만들때 사용 (한 번만 사용하는 객체를 만들경우)
  • Companion object는 이 객체를 포함하는 클래스의 private 맴버에 접근 가능

Extension Method/Property

val String.lastChar:Char
    get() = get(length-1)

fun main(){
    println("Hello".lastChar)
}
fun<T> Collection<T>.join(separator:String = " "):String
{
    val result = StringBuilder()
    // this.withIndex (해당 컬렉션의 맴버)
    for((index,element)in withIndex()){
        if(index>0) result.append(separator)
        result.append(element)
    }
    return result.toString()
}

fun main()
{
    println(listOf("1","2","3").join())
    println(arrayListOf(1,2,3).join(","))
}
  • 이미 말들어진 클래스의 메소드와 속성을 클래스를 상속하거나 수정하지 않고도 추가 할 수 있음
  • 이렇게 추가도니 메소드는 오버라이드 안됨
  • 자바의 기존 Collection들에 다양한 유틸 함수들을 이 방법으로 추가
    • String의 새로운 유틸 함수: substringBefore, substringAfter 등

연산자 오버로딩

data class Complex(val real:Double,val img:Double){
    operator fun plus(other:Complex):Complex
        = Complex(real + other.real,img+other.img)
    override fun toString(): String = "$real+${img}i"
}

operator fun Complex.minus(other: Complex):Complex
    = Complex(real-other.real,img-other.img)

fun main(){
    val c1 = Complex(1.0,1.0)
    val c2 = Complex(2.0,2.0)
    val c3 = c1 + c2
    println(c3)
    println(c3==Complex(3.0,3.0))

}
  • 이항 산술 연산자 +,-,*,/,% 오버로딩 가능
    • 오버로딩할 때 연산자 이름 plus, minus, times, div, rem
  • 단항연산자
    • unaryPlus, unaryMinus, not, inc, dec
  • 비교 연산자
    • equals, compareTo

위임(Delegation)

interface Base{
    fun print()
    fun printHello()
}

class BaseImpl(val x :Int):Base{
    override fun print(){print(x)}
    override fun printHello() {
        println("Hello")
    }
}

class Derived(val b:Base):Base by b{
    override fun print() {
        b.print()
        println("ok")
    }
}

fun main(){
    val b = BaseImpl(10)
    val d = Derived(b)
    d.print()
    d.printHello()
}
  • 상속과 유사하지만 다른 개념
    • 상속은 강한 결합, 위임은 약한 결합
    • 상속 북가여도 위임은 가능
    • 위임(상속)할 클래스와 동일한 인터페이스를 구현해야 함
  • 위임 패턴은 위임(상속)할 객체를 맴버로 만들고 특정 메소드 호출시 위임 객체의 메소드로 포워드(호출)하는 방식으로 만듦
    • 위임 객체의 메소드 각각에 대해 메소드를 새로 만들어서 포워드 해야 함
  • 코틀린은 by 키워드로 위임을 쉽게 지원함

위임 속성(Delegated Property)

class User(val map:MutableMap<String, Any?>){
    val lazyValue: String by lazy{
        // 처음 실행시 출력됨
        println("computed!")
        "hello"
    }

    var name:String by map
    var age: Int by map
    var nameOb: String by Delegates.observable("<no name>"){
        prop,old,new ->
        println("$old -> $new")
    }
}

fun main(){
    val user = User(mutableMapOf(
        "name" to "SuKung",
        "age" to 25
    ))

    println(user.lazyValue)
    println(user.lazyValue)

    println(user.name)
    println(user.age)
    user.age = 30
    println(user.map)

    user.nameOb = "fisrt"
    user.nameOb = "second"
}
class MyClass{
    var newName: Int = 0
    @Deprecated("Use 'newNaem' instead",ReplaceWith("newName"))
    var oldName: Int by this::newName
}
  • by 키워드를 써서 속성의 get/set을 위임 객체의 getValue/setValue로 위임함
  • 이를 이용하여 특별한 성질을 갖는 속성을 만듦
    • Lazy 특성: 값을 처음 접근할 때 생성/계산함
    • Observable 특성: 값이 변경될 때 알려주는 리스너를 지정
    • Map을 이용하여 객체 속성 값을 저장
  • 다른 속성으로 위임할 수도 있음

Generic

fun <T:Number> myPlus(op1:T,op2:T):String = "$op1$op2"

class MyGeneric<T:Number>(private val prop:T){
    fun test(arg:T):String{
        println(arg)
        return myPlus(prop,arg)
    }
}
fun main(){
    println(myPlus(1,1))
    println(myPlus(1.0,1.0))
    println(MyGeneric(10).test(10))
}
  • 자바 제네릭과 비슷함

'Development > Kotlin&Android' 카테고리의 다른 글

안드로이드-레이아웃  (0) 2023.03.30
Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 2  (0) 2023.03.02
Kotlin 문법 Part 1  (0) 2023.02.28
스프링의 코틀린 지원  (0) 2023.02.27

컬렉션

  • 불변 컬렉션: 읽기 전용 컬렉션 (immutable)
  • 가변 컬렉션: 삽입, 수정, 삭제와 같은 쓰기 작업이 가능한 컬렉션 (mutable)
import java.util.LinkedList

fun main(){
    // immutable 리스트를 만들어주는 팩토리 함수
    val currencyList = listOf("달러","유로","원")

    // mutable
    val mutableCurrencyList = mutableListOf<String>()
    mutableCurrencyList.add("달려")
    mutableCurrencyList.add("유로")
    mutableCurrencyList.add("원")

    // 좀 더 가독성 있게 변경
    // mutable
    val mutableCurrencyList2 = mutableListOf<String>().apply {
        add("달러")
        add("유로")
        add("원")
    }

    // immutable set
    val numberSet = setOf(1,2,3,4)

    // mutable set
    val mutableSet = mutableSetOf<Int>().apply {
        add(1)
        add(2)
        add(3)
        add(4)
    }

    // imutable map
    val numberMap = mapOf("one" to 1,"two" to 2)

    // mutable map
    val mutableNumberMap = mutableMapOf<String,Int>()
    mutableNumberMap["one"] = 1
    mutableNumberMap["two"] = 2
    mutableNumberMap["three"] = 3

    // 컬렉션 빌더 안에서는 값을 넣을 수 있지만 바깥에서는 값을 넣을 수 없음
    val numberList: List<Int> = buildList{
        add(1)
        add(2)
        add(3)
        add(4)
    }
    // numberList.add(4)

    // linkedList
    val linkedList = LinkedList<Int>().apply {
        addFirst(3)
        add(2)
        addLast(1)
    }

    //
    val arrayList = ArrayList<Int>().apply {
        add(1)
        add(2)
        add(3)
    }

    val iterator = currencyList.iterator()
    while(iterator.hasNext()){
        println(iterator.next())
    }

    println("===============")

    for(currency in currencyList){
        println(currency)
    }

    println("=================")

    currencyList.forEach {
        println(it)
    }

    // for loop -> map
    val lowerList = listOf("a","b","c")

    val upperList = mutableListOf<String>()
    for(lowerCase in lowerList){
        upperList.add(lowerCase.uppercase())
    }

    // map을 사용한 간소화
    val upperList2 = lowerList.map{it.uppercase()}
    println(upperList2)

    lowerList.map{it.uppercase()}
    println(upperList)
    println("=================")

    // filter 함수 구현
    val filteredList = mutableListOf<String>()
    for(upperCase in upperList2){
        if(upperCase == "A"||upperCase=="C"){
            filteredList.add(upperCase)
        }
    }
    println(filteredList)

    // filter 추상화되 함수 사용
    val filteredList2 = upperList.filter { it=="A"||it=="C" }
    println(filteredList2)

    // 시퀀스 api 사용 데이터 5만~10만 건에서는 추천하지 않는 방법
    // 다루는 데이터량이 많아지거나 filter을 연속해서 사용해야 하는 경우 추천
    val filteredList3 = upperList
        .asSequence()
        .filter { it=="A"||it=="C" }
        .filter { it=="C" }
        .toList()

    println(filteredList3)
}

데이터 클래스

  • 데이터를 저장하고 전달하기 위한 목적으로 만들어진 클래스
  • 가변객체는 멀티스레드 환경에서 문제가 발생 할 수 있음
data class Person(var name:String, var age:Int)
// 위에 한줄을 인텔리제이의 자바로 디컴파일 기능을 사용하여 변환시킨 것
// BasicClassKt.java
import kotlin.Metadata;

@Metadata(
   mv = {1, 7, 0},
   k = 2,
   d1 = {"\\u0000\\b\\n\\u0000\\n\\u0002\\u0010\\u0002\\n\\u0000\\u001a\\u0006\\u0010\\u0000\\u001a\\u00020\\u0001¨\\u0006\\u0002"},
   d2 = {"main", "", "BasicClass"}
)
public final class BasicClassKt {
   public static final void main() {
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
// Person.java
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 7, 0},
   k = 1,
   d1 = {"\\u0000 \\n\\u0002\\u0018\\u0002\\n\\u0002\\u0010\\u0000\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\b\\n\\u0002\\b\\t\\n\\u0002\\u0010\\u000b\\n\\u0002\\b\\u0004\\b\\u0086\\b\\u0018\\u00002\\u00020\\u0001B\\u0015\\u0012\\u0006\\u0010\\u0002\\u001a\\u00020\\u0003\\u0012\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005¢\\u0006\\u0002\\u0010\\u0006J\\t\\u0010\\u000b\\u001a\\u00020\\u0003HÆ\\u0003J\\t\\u0010\\f\\u001a\\u00020\\u0005HÆ\\u0003J\\u001d\\u0010\\r\\u001a\\u00020\\u00002\\b\\b\\u0002\\u0010\\u0002\\u001a\\u00020\\u00032\\b\\b\\u0002\\u0010\\u0004\\u001a\\u00020\\u0005HÆ\\u0001J\\u0013\\u0010\\u000e\\u001a\\u00020\\u000f2\\b\\u0010\\u0010\\u001a\\u0004\\u0018\\u00010\\u0001HÖ\\u0003J\\t\\u0010\\u0011\\u001a\\u00020\\u0005HÖ\\u0001J\\t\\u0010\\u0012\\u001a\\u00020\\u0003HÖ\\u0001R\\u0011\\u0010\\u0004\\u001a\\u00020\\u0005¢\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\u0007\\u0010\\bR\\u0011\\u0010\\u0002\\u001a\\u00020\\u0003¢\\u0006\\b\\n\\u0000\\u001a\\u0004\\b\\t\\u0010\\n¨\\u0006\\u0013"},
   d2 = {"LPerson;", "", "name", "", "age", "", "(Ljava/lang/String;I)V", "getAge", "()I", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString", "BasicClass"}
)
public final class Person {
   @NotNull
   private final String name;
   private final int age;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public Person(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.age = age;
   }

   @NotNull
   public final String component1() {
      return this.name;
   }

   public final int component2() {
      return this.age;
   }

   @NotNull
   public final Person copy(@NotNull String name, int age) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Person(name, age);
   }

   // $FF: synthetic method
   public static Person copy$default(Person var0, String var1, int var2, int var3, Object var4) {
      if ((var3 & 1) != 0) {
         var1 = var0.name;
      }

      if ((var3 & 2) != 0) {
         var2 = var0.age;
      }

      return var0.copy(var1, var2);
   }

   @NotNull
   public String toString() {
      return "Person(name=" + this.name + ", age=" + this.age + ")";
   }

   public int hashCode() {
      String var10000 = this.name;
      return (var10000 != null ? var10000.hashCode() : 0) * 31 + Integer.hashCode(this.age);
   }

   public boolean equals(@Nullable Object var1) {
      if (this != var1) {
         if (var1 instanceof Person) {
            Person var2 = (Person)var1;
            if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
               return true;
            }
         }

         return false;
      } else {
         return true;
      }
   }
}
data class Person(var name:String, var age:Int)

class Person2(val name:String, val age:Int){
    override fun equals(other: Any?): Boolean {
        if(this===other) return true
        if(javaClass != other?.javaClass) return false

        other as Person

        if(name != other.name) return false
        if(age != other.age) return false

        return true
    }

/*    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + age
        return result
    }*/

}

fun main(){
    val person1 = Person(name = "tony",age = 12)
    val person2 = Person(name = "tony",age =12)
    val personNew2 = person1.copy();

    // component의 수는 프로퍼티 수와 동일하다.
    println("이름${person1.component1()}, 나이${person2.component2()}")
    // 데이터 클래스가 equals 를 자동으로 생성해주기 때문에 true
    println(person2==person1)
    val set = hashSetOf(person1)
    println(set.contains(person2))
    println(set.contains(personNew2))

    person1.name = "skung"
    // copy를 해서 만들어도 원본의 값을 변경하면 달라진다.
    println(set.contains(person2))
    println(set.contains(personNew2))

    println("#######################")

    val person3 = Person2(name = "tony",age = 12)
    val person4 = Person2(name = "tony",age =12)
    val set2 = hashSetOf(person3)
    println(set2.contains(person4))
    println(person3.toString())

    println("#######################")

    // 구조분해 할당
    var superMan = Person(name="히어로",age=300)
    val (name, age) = superMan

    println("이름=${name}, 나이=${age}")
}

싱글톤

  • 클래스의 인스턴스를 하나의 단일 인스턴스로 제한하는 디자인 패턴
  • 싱글톤 패턴을 구현할때는 몇가지 제약사항을 통해 구현한다
  • 주로 유틸리티 클래스를 만들때 사용된다.
  • 동반객체: 클래스 내부에서 생성되는 객체
  • 제약사항
    • 직접 인스턴스화 하지 못하도록 생성자를 private으로 숨긴다
      • private constructor()
    • getInstance()라는 클래스의 단일 인스턴스를 반환하는 static 메서드를 제공한다
    • 멀티-스레드 환경에서도 안전하게 유일한 인스턴스를 반환해야 한다.
  • 다양한 구현 방법들
    • DCL(Double Check Locking)
      • JVM 환경에선 거의 사용 안함
    • Enum 싱글톤
      • 이펙티브 자바에서 소개
    • 이른 초기화
    • 지연 초기화
  • 자바에서 많이 쓰이는 구현 방식
    • 이른 초기화
    public class JavaSingleton{
    	private static final JavaSingleton INSTANCE = new JavaSingleton();
    
    	private JavaSingleton(){
    
    	}
    	public JavaSingleton getInstance(){
    		return INSTANCE.INSTANCE;	
    	}
    }
    
    • 지연 초기화
    public class JavaSingleton{
    
    	private JavaSingleton(){
    
    	}
    	public JavaSingleton getInstance(){
    		return LazyHolder.INSTANCE;	
    	}
    	private static final LazyHolder{
    		private static final JavaSingleton INSTANCE = new JavaSingleton(); 
    	}
    
    }
    
  • 코틀린의 싱글턴
import java.time.LocalDateTime

// 싱글톤 클래스는 object 타입으로 선언한다.
object Singleton{

    val a = 1234

    fun printA() = println(a)
}

object DatetimeUtils{
    val now : LocalDateTime
        get() = LocalDateTime.now()

    const val DEFAULT_FORMAT = "YYYY-MM-DD"

    fun same(a: LocalDateTime, b:LocalDateTime):Boolean{
        return a == b
    }
}

class MyClass{

    // 생성자를 호출하지 못 하도록 private 선언
    private constructor()

    // 동반객체 키워드 companion
    companion object{
        val a = 1234

        fun newInstance() = MyClass()
    }
}

fun main(){
    println(Singleton.a)
    Singleton.printA()
    println("######")
    println(DatetimeUtils.now)
    println(DatetimeUtils.now)
    println(DatetimeUtils.now)

    println(DatetimeUtils.DEFAULT_FORMAT)

    val now = LocalDateTime.now()
    println(DatetimeUtils.same(now,now))
    println("######")
    println(MyClass.a)
    println(MyClass.newInstance())
}

실드 클래스

  • 하나의 상위 클래스 또는 인터페이스에서 하위 클래스에 대한 정의를 제안 할 수 있는 방법
    • 자바의 추상 클래스 개념과 유사
// 쉴드 클래스
sealed class Developer{
    abstract val name: String
    abstract fun code(language:String)
}

data class BackendDeveloper(override val name:String):Developer(){
    override fun code(language: String) {
        println("저는 백엔드 개발자입니다 ${language}를 사용합니다")
    }
}

data class FrontendDeveloper(override val name:String):Developer(){
    override fun code(language: String) {
        println("저는 프론트엔드 개발자입니다 ${language}를 사용합니다")
    }
}

data class AndroidDeveloper(override val name: String): Developer(){
    override fun code(language: String) {
        println("저는 안드로이드 개발자입니다 ${language}를 사용합니다")
    }

}

data class IosDeveloper(override val name: String): Developer(){
    override fun code(language: String) {
        println("저는 Ios 개발자입니다 ${language}를 사용합니다")
    }

}

// 쉴드클래스로 작성된 상위 클래스에 하위클래스가 새롭게 추가가 된다면
// 새롭게 추가한 클래스도 구현해 주어야 함
object OtherDeveloper: Developer(){
    override val name: String = "익명"

    override fun code(language: String) {
        TODO("Not yet implemented")
    }

}

// 싱글톤 클래스
object DeveloperPool{
    val pool = mutableMapOf<String, Developer>()

    fun add(developer: Developer) = when(developer){
        is BackendDeveloper -> pool[developer.name] = developer
        is FrontendDeveloper -> pool[developer.name] = developer
        is AndroidDeveloper -> pool[developer.name] = developer
        is IosroidDeveloper -> pool[developer.name] = developer
        is OtherDeveloper -> println("지원하지 않는 개발자종류입니다")
    }

    fun get(name:String) = pool[name]
}

fun main(){
    val backendDeveloper = BackendDeveloper(name="토니")
    DeveloperPool.add(backendDeveloper)

    val frontendDeveloper = FrontendDeveloper(name="스타크")
    DeveloperPool.add(frontendDeveloper)

    val androidDeveloper = AndroidDeveloper(name="샘숭")
    DeveloperPool.add(androidDeveloper)

    val iosDeveloper = IosDeveloper(name="애플")
    DeveloperPool.add(iosDeveloper)

    println(DeveloperPool.get("토니"))
    println(DeveloperPool.get("스타크"))
    println(DeveloperPool.get("애플"))
    println(DeveloperPool.get("샘숭"))

}

확장 함수

  • 클래스를 상속하거나 데코레이터 패턴과 같은 디자인 패턴을 사용하지 않아도 클래스를 확장할 수 있는 방법을 제공
  • 확장 함수를 만들때 동일한 시그니처를 가진 함수가 있다면 그것이 우선 됨
    • 이를 방지하기 위해서는 매개변수를 다르게 받거나 다른 명칭을 사용할 필요가 있음
fun String.first() : Char{
    return this[0]
}

fun String.addFirst(char: Char):String{
    return char + this.substring(0)
}
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(
   mv = {1, 7, 0},
   k = 2,
   d1 = {"\\u0000\\u0016\\n\\u0000\\n\\u0002\\u0010\\u0002\\n\\u0000\\n\\u0002\\u0010\\u000e\\n\\u0000\\n\\u0002\\u0010\\f\\n\\u0002\\b\\u0002\\u001a\\u0006\\u0010\\u0000\\u001a\\u00020\\u0001\\u001a\\u0012\\u0010\\u0002\\u001a\\u00020\\u0003*\\u00020\\u00032\\u0006\\u0010\\u0004\\u001a\\u00020\\u0005\\u001a\\n\\u0010\\u0006\\u001a\\u00020\\u0005*\\u00020\\u0003¨\\u0006\\u0007"},
   d2 = {"main", "", "addFirst", "", "char", "", "first", "BasicClass"}
)
public final class BasicClassKt {
   public static final char first(@NotNull String $this$first) {
      Intrinsics.checkNotNullParameter($this$first, "$this$first");
      return $this$first.charAt(0);
   }

   @NotNull
   public static final String addFirst(@NotNull String $this$addFirst, char var1) {
      Intrinsics.checkNotNullParameter($this$addFirst, "$this$addFirst");
      byte var4 = 0;
      String var10000 = $this$addFirst.substring(var4);
      Intrinsics.checkNotNullExpressionValue(var10000, "this as java.lang.String).substring(startIndex)");
      String var3 = var10000;
      return var1 + var3;
   }

   public static final void main() {
      char var0 = first("ABCD");
      System.out.println(var0);
      String var1 = addFirst("ABCD", 'Z');
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}
fun String.first() : Char{
    return this[0]
}

fun String.addFirst(char: Char):String{
    return char + this.substring(0)
}

class MyExample{
    fun printMessage() = println("클래스 출력")
}

// 확장 함수를 만들때 동일한 시그니처를 가진 함수가 있다면 동작하지 않음
fun MyExample.printMessage() = println("확장 출력")

fun MyExample.printMessageSu(message:String) = println(message)

fun MyExample?.printNullOrNotNull(){
    if(this == null) println("널인 경우에만 출력")
    else println("널이 아닌 경우에만 출력")
}

fun main(){
    var myExample: MyExample? = null
    myExample.printNullOrNotNull()

    myExample = MyExample()
    myExample.printNullOrNotNull()

    println("ABCD".first())
    println("ABCD".addFirst('Z'))
    // 클래스의 메소드가 호출됨
    MyExample().printMessageSu("확장 출력")
}

제네릭

// 공변성 구현
class MyGenenrics<out T>(val t:T){

}

class Bag<T>{

    fun saveAll(
        // 반공변성 구현
        to: MutableList<in T>,
        from: MutableList<T>,
    ){
        to.addAll(from)
    }
}

fun main(){
    val bag = Bag<String>()
    // 위에 in 키워드로 인해 CharSequence가 원래 String 보다 상위개념이지만 하위 개념으로 취급
    bag.saveAll(mutableListOf<CharSequence>("1","2"), mutableListOf<String>("3","4"))

    val generic = MyGenenrics<String>("테스트")
    // String의 상위 개념 CharSequence
    val charGenerics : MyGenenrics<CharSequence> = generic

    // 제네릭을 사용한 클래스의 인스턴스를 만드려면 타입아규먼트를 제공
    //val genenrics = MyGenenrics<String>("test")

    // 타입추론 기능으로 인해 생략가능
    val genenrics = MyGenenrics("test")

    // 변수의 타입의 제네릭을 사용한 경우
    val list1:MutableList<String> = mutableListOf()
    // 타입아규먼트를 생성자에서 추가
    val list2 = mutableListOf<String>()

    // 스타 프로제션
    val list3 : List<*> = listOf<String>("test")
    val list4 : List<*> = listOf<Int>(1,2,3,4)

    // PECS(Producer-Extends, Consumer-Super
    // 공변성은 자바 제네릭의 extends 코틀린에선 out <상위클래스> = <하위클래스> (대입)
    // 반공변성은 자바 제네릭의 super 코틀린에선 in <하위클래스> = <상위클래스> (대입)

}

지연초기화

  • 대상에 대한 초기화를 미뤘다가 실제 사용시점에 초기화하는 기법을 말한다
    • 웹페이지에서 특정 스크롤에 도달했을때 컨텐츠를 보여주는 무한 스크롤
    • 싱글톤 패턴의 지연초기화
    public class JavaSingleton{
    
    	private JavaSingleton(){
    
    	}
    	public JavaSingleton getInstance(){
    		return LazyHolder.INSTANCE;	
    	}
    	private static final LazyHolder{
    		private static final JavaSingleton INSTANCE = new JavaSingleton(); 
    	}
    
    }
    
    • JPA의 엔티티 LazyLoading 기능
    @OneToMany(fetch = FetchType.LAZY)
    
    • 이외에도 지연초기화는 많은 상황에서 사용된다.
    class HelloBot{
        // 지연 초기화 키워드 by lazy 기본적으로 멀티스레드 환경에서 안전하게 동작되도록 설계됨
        // 불변 객체는 멀티스레드 환경에서 안정성을 제공해줌
        // (LazyThreadSafetyMode.SYNCHRONIZED)는 생략 가능
        val greeting: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
            println("초기화 로직 수행")
            getHello()
        }
    
        fun sayHello() = println(greeting)
    }
    
    fun getHello() = "안녕하세요"
    
    class LateInit{
        // (?) not null 타입이 아닌데도 컴파일 오류 발생 x
        // by lazy 키워드와 동일
        lateinit var text:String
    
        // isInitialized을 외부에서 사용하는 방법
        val textInitialized: Boolean
            get() = this::text.isInitialized
        
        fun printText(){
            // text = "안녕하세요"
            // isInitialized 는 클래스 외부에서 사용이 불가능
            if(this::text.isInitialized){
                println("초기화됨")
            }else{
                text = "안녕하세요"
            }
            println(text)
        }
    }
    
    fun main(){
        val test = LateInit()
        test.printText()
    
        println("#################")
    
        val helloBot = HelloBot()
    
        for(i in 1..5){
            Thread{
                helloBot.sayHello()
            }.start()
        }
    
    }
    

Pair, Triple과 구조분해 할당

// f((1,3)) = 1 + 3 = 4
// f(1,3) = 1 + 3 = 4
//data class Tuple(val a:Int, val b:Int)

fun plus(pair:Pair<Int,Int>) = pair.first+pair.second

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

// Pair, Triple 클래스는 구조분해 할당에 하기 좋은 자료구조
fun main(){
    println(plus(1,3))
    // plus의 매개변수는 2개인대 Tuple Class 하나만 들어가도 성립됨
    val plus = plus(Pair(1,3))
    println(plus)

    // 페어 클래스의 각 값은 불변
    val pair = Pair("A",1)
    val newPair = pair.copy(first="B")
    println(newPair)

    val second = newPair.component2()
    println(second)

    // 인뮤터블 형태의 리스트 제공
    val list = newPair.toList()
    println(list)

    // 3개의 데이터를 담을 수 있는 Triple 까지 제공됨 그 이상은 따로 구현 해야 함
    val triple = Triple("A","B","C")
    println(triple)
    // 해당 값들은 triple과 똑같이 인뮤터블
    triple.first
    triple.second
    triple.third
    val newTriple = triple.copy(third = "D")
    println(newTriple)

    println(newTriple.component3())

    // 구조 분해 할당
    val(a:String,b:String,c:String) = newTriple
    println("$a, $b, $c")

    val list3:List<String> = newTriple.toList()
    val (a1,a2,a3) = list3
    println("$a1,$a2,$a3")

    list3.component1()
    list3.component2()
    list3.component3()
    //list3.component4()
    //list3.component5()

    // map의 구조분해 할당
    //val map = mutableMapOf("임수한" to "개발자")
    val map = mutableMapOf(Pair("임수한","개발자"))
    for((key,value)in map){
        println("${key}의 직업은 ${value}")
    }
}

람다식

fun main() {
	val sum = {x:Int, y:Int -> x+y}
    println(sum(10,20))
    
    // 해당 함수는 함수( int를 받아 int를 리턴하는 함수 a)를 인자로 받는 함수
    fun lamdaTest(a:(Int)->Int):Int{
        return a(10)
    }
    // 정석에 맞게 함수에 함수를 인자로 넣어서 호출한 경우
    println(lamdaTest({x->x+10}))
    // (추론을 통한 생략) 인자 타입 생략 가능, 인자가 1개 뿐이므로 인자리스트 생략
    println(lamdaTest({it+10})) 
    // 람다가 함수의 유일한 인자이면, 함수의 괄호를 생략 할 수 있음
    println(lamdaTest{it+10})
}

 

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

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

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}))
    // 람다가 함수 인자의 마지막으로 사용되면, 함수의 괄호 밖으로 뺄 수 있음.
    array.filter(){c: MyClass -> c.a <15}
    // 람다가 함수의 유일한 인자이면, 함수의 괄호를 생략 할 수 있음.
    array.filter{c: MyClass->c.a<15}
    // 인자 타입 생략 가능한 경우
    array.filter{c->c.a<15}
    // 디폴트 매개변수 이름으로 it를 사용할 수 있음
    array.filter{it.a<15} // 일반적으로 많이 사용되는 형태
    println(lambdaTest{it+10})
    
    val title="Num:"
    val list = listOf(1,2,3,4)
    list.forEach{println("$title $it")}
    
}

'Development > Kotlin&Android' 카테고리의 다른 글

Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 1  (0) 2023.02.28
스프링의 코틀린 지원  (0) 2023.02.27
Java와 Kotlin의 차이  (0) 2023.02.27

변수 선언

// Top-Level에 위치한 변수
var x = 6

fun main(){
	x+=1

// 키워드, 변수명, 타입, 값 순서로 작성 
	val a : Int = 1
// 타입 생략 가능 코틀린 컴파일러가 값을 보고 타입을 추론해줌 
	val b = 2
// 지연 할당 가능 단 타입을 명시해 주어야 함 
	val c : Int
	c = 3
// 컴파일 오류 
	val d
	d = 4
// val(value) 재할당 불가능 (상수)
// var(variable) 재할당 가능 (변수)
	var e : String = "Hello World"
	e = "World"

// var에서 타입이 고정되면 타입은 변경이 불가능	
	var f = 5
	f = "Hello"
}

문자열 처리

fun main() {
	val version = "1.3.50"
    val javaStyle = "Hello, kotlin" + version + "!"
    val kotlinStyle = "Hello, Kotlin ${version}!"
    println(javaStyle)
    println(kotlinStyle)
    
    val num = 10
    println("val num is equal to 10:${num == 10}.")
    println("""\$""")
    println("\$")
    println("""
    	|hello
        |my name is kotlin.
    """.trimMargin())
}

함수 선언

fun sum(a: Int, b: Int) : Int {
	return a + b
}

// 표현식 스타일
fun sum2(a: Int, b: Int) : Int = a + b

// 표현식 & 변환타입 생략
fun sum3(a: Int, b: Int) = a + b

// 몸통이 있는 함수는 반환 타입을 제거하면 컴파일 오류
fun sum4(a: Int, b: Int){
	return a + b
}

// 반환타입이 없는 함수는 Unit을 반환한다
fun printSum(a: Int, b: Int): Unit{
	println("$a + $b = ${a+b}")
}

// 디폴트 파라미터
fun greeting(message: String = "안녕하세요!!"){
	println(message)
}

// named argument
fun log(level: String = "INFO", message: String){
	println("[$level]$message")
}

fun main(){

}

흐름제어

/**
 * You can edit, run, and share this code.
 * play.kotlinlang.org
 */fun main() {

// if else 사용val job = "Software Developer"

    if(job == "Software Developer"){
        println("개발자")
    }else{
        println("개발자아님")
    }

// 코틀린의 if-else는 표현식 값을 리턴하는 것이 가능val age : Int = 10

    var str = if(age>10){
        "성인"
    }else{
        "아이"
    }

// 코틀린은 삼항 연산자가 없다. if else가 표현식이므로 불필요하다.val a = 1
    val b = 2
    val c = if(b>a) b else a

    val day = 2

// 자바의 switch문val result = when(day){
        1 -> "월요일"
        2 -> "화요일"
        3 -> "수요일"
        4 -> "목요일"
        else -> "기타"
    }
    println(result)

// 여러개의 조건을 콤마로 구분해 한줄에서 정의할 수 있다.
	when(getNumber()){
		0, 1 -> print("0 또는 1")
		else -> print("0 또는 1이 아님")
	}

// 범위 연산자 .. 을 사용해 for loop 돌리기 <
	for(i in 0 .. 3 ){
		println(i)	
	} 

// until 을 사용해 반복한다
// 뒤에 온 숫자는 포함하지 않는다 <=
	for (i in 0 until 3){
		println(i)
	}
	
// step 에 들어온 값 만큼 증가시킨다
	for (i in 0 .. 6 step 2){
		println(i)
	}

// downTo를 사용해 반복하면서 값을 감소시킨다
	for (i in 3 downTo 1){
		println(i)
	}
    
	(10 downTo 1).forEach{
        print("$it ")
    }
    println("")
// 전달받은 배열을 반복
	val numbers = arrayOf(1,2,3)

	for(i in numbers){
		println(i)	
	}

// 자바의 while문과 동일
// 조건을 확인하고 참이면 코드 블록을 실행한 후 다시 조건을 확인
	var x = 5

	while(x>0){
		println(x)
		x--
	}
}

타입체크

fun main() {
	fun test(obj: Any){
        if (obj is Int)println("obj is Int")
        if (obj is String) println("obj is String")
        // obj가 String 타입이 아니라면 공백으로 
        println("print obj as string> ${(obj as? String?:"")}")
    }
    test(1)
    test("Strings")
}

 

널 안정성 (NPE)

fun main(){
    // (컴파일 에러)
    // val a : String = null
    var b : String = "abcdef"
    // (컴파일 에러)
    // b = null

    // Nullable 한 타입임을 명시
    var c : String? = null
    // 해당 값이 null이 아니라면 실행
    println(c?.length)

    val d: Int = if(c!=null) c.length else 0
    println(d)

    // 엘비스 연산자
		// ?:의 왼쪽 객체가 non-null이면 그 객체의 값이 리턴되고, null이라면 ?:의 오른쪽 값을 리턴합니다.
    val e = c?.length?:0 // 해당 타입은 null 이기 때문에 0 리턴
    println(e)

    val f: String? = null

    // !! null이 발생하지 않는다고 컴파일러에게 알리는 단언자
    // null을 없애주지는 않는다.
    val g = f!!.length
}

예외처리

import java.lang.Exception
import java.lang.IllegalArgumentException

fun main(){
    // 코틀린은 checkedException을 강제하지 않음
    Thread.sleep(1)
    // 그래도 try - catch가 필요한 경우 사용가능
    try{
        Thread.sleep(1)
        throw Exception()
    }catch (e: Exception){
        println("경고")
    } finally {
        println("finally 실행!")
    }

    // try - catch 문 또한 값을 반환하는 것 이 가능
    val a = try{
        "1234".toInt()
    }catch (e: Exception){
        println("예외 발생")
    }
    println(a)

    //throw Exception("예외 발생!")
    val b: String? = null
    // Nothing 타입과 ?: 엘비스 연산자를 같이 사용하는 경우는 null이 나올 수 없음
    val c: String = b?:failFast("예외 발생!")

    println(c.length)
    println("앞서 Nothing 타입이 반환된다면 이후 함수는 작동을 보장할 수 없음")
}

// throw를 return 하게 된다면 Nothing 타입을 자동으로 반환 함
fun failFast(message: String): Nothing{
    throw IllegalArgumentException(message)
}

Class

// 특정 애너테이션과 같이 쓸 경우 해당 키워드 사용 권장
class Drink constructor(val name:String){

}
// 기본 생성자에 constructor 키워드 생략가능
class Coffee (
    // 코드 리뷰시 가독성을 위해 후행 ,를 항상 붙일 것을 권장
    var name:String = "",
    var price: Int = 0,
){
    val brand: String
        // 커스텀 getter
        get() = "스타벅스"

    val origin: String
        // 커스텀 getter2
        get() {
            return "콩고"
        }

    var quantity : Int = 0
        // 커스텀 setter
        set(value){
            if(value > 0){ // 수량이 0 이상일 경우에만
                // quantity = value 로 직접 할당하지 않는 이유
                // quantity가 다시 set을 호출하는 무한 재귀 현상이 일어나기 때문
                field = value
            }
        }
}

class EmptyClass
fun main(){
    // getter setter 코드를 따로 작성해주지 않아도 컴파일러가 자동으로 생성 후 실행
    val coffee = Coffee()
    // 보이지 않는 setter 함수 실행
    coffee.name = "아이스 아메리카노"
    coffee.price = 2000
    coffee.quantity = 5
    // 보이지 않는 getter 함수 실행
    println("${coffee.brand} ${coffee.name} 가격은 ${coffee.price}")
}

상속

  • Kotlin의 모든 클래스의 조상은 Any class
open class Dog{
    open var age: Int = 0

    open fun bark(){
        println("walwal")
    }
}

// 코틀린에서 기본생성자 사용시 프로퍼티를 좀 더 쉽게 재정의 가능
// 상속을 받아 구현한 클래스는 프로퍼티나 함수가 자동으로 open 상태가 됨
// 이를 막고자 한다면 final 키워드를 사용해야 함
open class Bulldog(final override var age : Int = 0) : Dog(){

    override fun bark(){
        // 상위 클래스의 함수 호출시
        super.bark()
        println("kungkung")
    }
}

class ChildBullDog():Bulldog(){
    //override var age: Int = 0
    override fun bark(){
        super.bark()
    }
}

// 추상 클래스
abstract class Developer{
    abstract  var age: Int
    abstract  fun code(language: String)
}
// 추상 클래스 상속시 하위 클래스에서 abstract 키워드가 붙은 것을 구현해야 함
class BackendDeveloper(override var age: Int):Developer(){
    override fun code(language: String){
        println("I code with $language")
    }
}

fun main(){
    val dog = Bulldog(age = 2)
    println(dog.age)
    dog.bark()

    val backendDeveloper = BackendDeveloper(age=28)
    println(backendDeveloper.age)
    backendDeveloper.code("Kotlin")
}

인터페이스

  • 하나 이상의 인터페이스 구현이 가능
class Product(val name:String, val price:Int)

interface Wheel{
    // 추상 함수
    fun roll()

}
// 코틀린에서는 인터페이스 에서도 프로퍼티가 존재 가능
interface Cart:Wheel{

    var coin: Int
    // 인터페이스 경우에는 클래스와 다르게 backing field를 사용하면 컴파일 오류 발생
    val weight : String
        get() = "20KG"

    fun add(product: Product)
    // 디폴트 함수
    fun rent(){
        if(coin>0){
            println("카트를 대여합니다")
        }
    }

    override fun roll() {
        println("카트가 굴러갑니다")
    }

    fun printId() = println("1234")
}

// 복수개의 인터페이스에서 같은 이름의 함수가 있는 경우 호출이 안되는 문제점 존재
interface Order{
    fun add(product: Product){
        println("${product.name} 주문이 완료되었습니다")
    }

    fun printId() = println("5678")
}

// 상속과 달리 : 생성자가 아니라 인터페이스 명을 적어준다.
class MyCart(override var coin: Int): Cart,Order{

    override fun add(product: Product) {
        if(coin<=0) println("코인을 넣어주세요")
        else println("${product.name}이(가) 카트에 추가됐습니다")

        // 주문하기 super<인터페이스>를 통해 접근 하는 것으로 해결 가능
        super<Order>.add(product)
    }

    // 두 개의 인터페이스에서 동일한 시그니처의 디폴트 함수를 제공한 상황에서는
    // 각각을 override 해서 재정의 할 필요가 있다
    override fun printId() {
        super<Cart>.printId()
        super<Order>.printId()
    }

}

fun main(){
    val cart = MyCart(coin = 100)
    cart.rent()
    cart.roll()
    cart.printId()
    cart.add(Product(name="장난감",price = 1000))
}

열거형

  • 서로 연관된 상수들의 집합을 enum 클래스를 사용하여 표현
  • 동등성 비교에 자주 이용
enum class PaymentStatus(val label:String):Payable{
    UNPAID("미지급"){
        override fun isPayable(): Boolean = true
    },
    PAID("지급완료"){
        override fun isPayable(): Boolean = false
    },
    FAILED("지급실패") {
        override fun isPayable(): Boolean = false
    },
    REFUNDED("환불") {
        override fun isPayable(): Boolean = false
    };
    // 내부에서 추상 메서드 선언 후 구현 하는 것도 가능
    // abstract fun isPayable(): Boolean
}

// 분리 작업
interface Payable{
    fun isPayable(): Boolean
}

fun main(){

    if(PaymentStatus.UNPAID.isPayable()){
        println("결제 가능 상태")
    }

    // 인자로 들어온 String 을 가지고 일치하는 값을 가지고와 인스턴스 화
    val paymentStatus = PaymentStatus.valueOf("PAID")
    println(paymentStatus.label)

    // 동등성 비교
    if(paymentStatus==PaymentStatus.PAID){
        println("결제 완료 상태")
    }
    
    for(status in PaymentStatus.values()){
        println("[$status](${status.label})")
    }
    // String, 쌍, 순서
    for(status in PaymentStatus.values()){
        println("[${status.name}](${status.label}) : ${status.ordinal}")
    }
}

'Development > Kotlin&Android' 카테고리의 다른 글

Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 2  (0) 2023.03.02
스프링의 코틀린 지원  (0) 2023.02.27
Java와 Kotlin의 차이  (0) 2023.02.27

1. 공식 문서

https://kotlinlang.org/docs/home.html

2. Spring initializr

  • Kotlin DSL 기반으로 생성해준다
  • 코틀린 스프링 프로젝트에서 필수적인 플러그인
    • kotlin(”plugin.spring”)
  • 코틀린 스프링 프로젝트에서 필수적인 의존성
    • org.jetbrains.kotlin:kotlin-reflect
    • org.jetbrains.kotlin-stdlib
  • 이외에도 plugin.jpa, jackson-module-kotlin 등 프로젝트를 구성하면서 필요한 플러그인과 코틀린 의존성이 있고 Spring initialzr에서 프로젝트를 구성할 경우 자동으로 세팅해준다.

3. 스프링 부트

  • @SpringBootApplication 애너테이션을 통해 사용

4. @ConfigurationProperties

  • 스프링 애플리케이션에 지정한 설정을 기반으로 설정 클래스를 만들때 @ConstructorBinding 을 사용하면 setter가 아닌 생성자를 통해 바인딩 하므로 불변 객체를 쉽게 생성할 수 있다.

5. 테스트 지원

  • 기본 제공되는 Junit5 기반의 테스트를 특별한 설정 없이 그대로 사용이 가능하다
  • 모의 객체를 만들어 테스트하려면 Mockito 대신 MockK를 사용할 수 있다
  • Mockito에서 제공하는 @MockBean, @SpyBean을 대체하는 SpringMockK의 @MockkBean, @SpykBean

6. 확장함수

  • 스프링에서 지원하는 코틀린 API의 대부분은 이러한 확장 기능을 사용해 기존 API에 코틀린 API를 추가
  • 스프링 프로젝트에선 확장함수를 통해 기존 자바 API를 건드리지 않고 쉽게 코틀린 확장 기능을 추가하고 있다.

7. 코루틴

  • 비동기-논블로킹 방식을 선언형으로 구현하기 위한 코틀린 기능
  • 스플링 MVC, 스프링 WebFlux 모두 코루틴을 지원하여 의존성만 추가하면 바로 사용 가능
    • implementation(”org.jetbrains.kotlinx:kotlinx-coroutines-core”)
    • implementation(”org.jetbrains.kotlinx:kotlinx-coroutines-reactor”)
  • 코루틴이 지원되어 비동기-논블로킹 스타일의 구현을 쉽게 할 수 있다.

8 스프링 Fu

  • 스프링 부트 설정을 Java DSL 혹은 Kotlin DSL 방식으로 작성 가능 (아직은 실험적 프로젝트)
  • Java DSL은 JaFu, Kotlin DSL은 KoFu로 부른다

'Development > Kotlin&Android' 카테고리의 다른 글

Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 2  (0) 2023.03.02
Kotlin 문법 Part 1  (0) 2023.02.28
Java와 Kotlin의 차이  (0) 2023.02.27

체크드 익셉션

  • 자바에서는 체크드 익셉션을 try catch, throw를 통해 해결하지 않으면 컴파일 에러가 발생
  • 코틀린은 체크드 익셉션을 강제하지 않는다. (생략가능)

기본 자료형

  • 자바는 원시 자료형을 지원하며 객체로된 레퍼런스 타입도 지원한다
  • 코틀린은 레퍼런스 타입만 지원한다
    • 코틀린의 레퍼런스 타입은 최적화된 방식으로 자바로 컴파일된다.

정적 맴버

  • 자바는 static 키워드로 정적 맴버를 선언한다.
  • 코틀린은 companion object로 대체

삼항 연산자

  • 자바는 삼항 연산자가 존재한다.
  • 코틀린은 if-else로 대신한다 (if else 문이 값을 반환하는 것이 가능)

세미콜론(;)

  • 자바에서 라인의 끝은 무조건 세미콜론으로 끝나야한다.
  • kotlin은 세미클론을 생략 가능하다.

확장

  • 코틀린은 개발자가 임의로 객체의 함수나 프로퍼티를 확장해서 사용할 수 있다.

데이터 클래스

  • 데이터를 보관하거나 전달하는 목적을 가진 불변 객체로 사용
  • 기존 자바에선 주로 Lombok을 사용 (@Data)
  • JDK 15에서 record 라는 이름으로 추가됨

문자열 템플릿

  • 문자열에 변수를 사용하거나 여러 행으로 된 텍스트 블록을 만들 수 있다
    • “”” 으로 감싸 텍스트 블록 생성 가능
  • 자바에서는 해당 기능이 프리뷰 상태

NULL 안정성

  • 자바에서 가장 많이 발생하는 예외는 NullPointerException
  • 자바의 옵셔널(Optional)은 값을 래핑하기 때문에 객체 생성에 따른 오버헤드가 발생하고, 컴파일 단계에서 Null 가능성을 검사하지 않음
  • Kotlin은 언어적 차원에서 NullPointException이 발생할 가능성을 제거한다.
  • Nullable 참조는 컴파일 단계에서 NULL 안정성을 제공
    • 변수~? 해당 변수가 null이 아니라면 실행
    • 변수~!! 해당 변수가 null이 아니라고 확신하는 경우

기타

  • 스마트 캐스트
  • 실드 클래스
  • 위임
  • 중위 표현식
  • 연산자 오버로딩
  • 코루틴
  • etc

'Development > Kotlin&Android' 카테고리의 다른 글

Kotlin 문법 Part 4  (0) 2023.03.23
Kotlin 문법 Part 3  (0) 2023.03.16
Kotlin 문법 Part 2  (0) 2023.03.02
Kotlin 문법 Part 1  (0) 2023.02.28
스프링의 코틀린 지원  (0) 2023.02.27

+ Recent posts