본문 바로가기
언어와 프레임워크/Java

빠르게 살펴보는 코틀린 문법

by Unagi_zoso 2023. 9. 25.

클래스

생성자 constructor 키워드

class Person constructor(name: String, age: Int) {}

클래스 선언부에 적지 안하도 알아서 생성해준다. 기본 생성자의 인자가 외부 주입이거나, 여러개가 필요할 땐 내부에서 선언한다.

class Person() {
    // 기본 생성자의 인자 없는 경우
    constructor(name: String, age: Int): this()
}

class Person(name: String) {
    // 기본 생성자의 인자가 있는 경우
    constructor(name: String, age: Int): this()
}

기본 생성자 외 생성자는 this()를 꼭 호출해야한다

클래스 생성할 때 new 키워드 사용하지 않는다.

디폴트 생성자에 기본값 넣기

class Person (name: String = "No Name", age: Int = 0) {}

코틀린 생성자 함수 바디 가지기

class Person (var name: String?, var age: Int = 0) {
    init {
        if (name.isNullOrBlank()) name = "No Name"
    }
}

코틀린 변수 키워드 var, val

변수 선언 시 둘 중 하나 꼭 사용

val = final
var = 일반 변수

기본 생성자의 인자는 기본으로 val 선언

접근 제한자

코틀린엔 private, protected, internal, public 자바와 같은 것도 있지만
internal은 색다른 녀석. 클래스나 모듈에 한해 접근이 가능하도록 해주는 인자

코틀린과 자바 사이의 모듈은 차이 존재. 자바의 모듈은 기능이 비슷한 클래스들의 집합체, 코틀린은 동일한 컴파일의 집단 의미

코틀린에서의 모듈은 인텔리J로 컴파일되는 모듈이나 메이븐, 그레이들을 묶어서 같이 컴파일되는 파일 전체를 이른다.

private은 클래스 내부에서만 접근이 가능하다.
protected는 상속 받은 클래스에서만 접근 가능하다
internal 같은 모듈 안에 클래스에서 접근 가능하다
접근 제한자가 선언되어 있지 않으면 public으로 간주된다. 프로그램 어디서든 접근이 가능하다.

컴파일되는 집단을 모듈이라 하면 public이랑 뭐가 다른겨

클래스 안에 open 키워드는 상속이 가능하다는 의미

클래스의 벰버 변수 선언

코틀린에서는 변수 선언만으로 자동으로 get/set 생성해준다.

get/ set 오버라이드

class Person () {
    var name: String = ""
    get() = "Name : "+field

    var age: Int = 0
    set(age) { field =+ 1 }
}

멤버변수 바로 밑에 get(), set() 정의하면 된다. 람다로도 가능하고
변수의 이름을 사용하지 않고 field라는 키워드를 이용해서 접근한다.

게터세터는 변수의 접근 제한자 따라가지만 따로 설정해줄 수 있다.

class Person (name: String, age: Int) {
    va name: String = ""
    private set

    var age: Int = 0
    private set
}

보통은 캡슐화한다해서 변수를 private에 두고 public 게터 세터로 접근하게 하지 않나

상속

: 기호 사용. 부모 클래스는 open이나 abstract 키워드를 사용해야 상속 가능
부모클랫에 대해서는 open, 추상 클래스를 만들고 시다면 abstract 키워드를 사용한다. 코틀린에서 자바와 비슷하게 Object가 아닌 Any를 상속하고 있따.

Any 에는 equals(), hashCode(), toString() 함수가 있다.

부모클래스의 final 함수는 oeerride할 수 없다. 오버라이드 허용하려면 open, 아니면 final

자식 클래스는 부모 클래스의 생성자를 항상 초기화해야한다.

**클래스 선언부에 변수 넣으면 접근 제한자가 어떻게 되는거지?

interface 클래스

코틀린에서 인터페이스는 내부의 함수가 바디를 가질 수 있고 멤버 변수를 추상 변수로 만들어 사용할 수 있으며, 게터 세터를 통해 값을 지정할 수도 있다. 그래서 공통으로 수행되는 부분은 인터페이스에서 구현할 수도 있다.

구현 시 : 사용

인터페이스에서 변수에 게터를 지정해주지 않으면 자식 클래스에서 무조건 오버라이드 ㅐ야한다.

상속에서 중복 함수에 대해선 부모클래스를 선언하고 사용한다.

override fun f() {
    super<A>.f()
    super<B>.f()
}

그 밖의 클래스 타입

data 클래스

클래스 중 데이터만 갖는 녀석 vo인가. 반드시 디폴트 생성자를 선언해줘야 한다. 인자는 var, val 키워드를 꼭 써야한다. data 키워드로 클래스 생성하면 컴파일러가 자동으로 equeal, toString을 생성해준다.

내부에 함수를 가지지 않는다...

data class Person(var name: String, var age: Int)

.copy라는 함수도 만들어주나봐. 딥카피일까

enum 클래스

타입을 담는 크ㅡㄹ래스ㅡ.

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEn(0x00FF00),
    BLUE(0x0000FF)
}

sealed 클래스

상속을 제한하기 위해서 사용하는 클래스. 자바에서 final 사용하거나 private으로 생성자 만들거나 하는데 프로그램 내부에서는 상속을 할 수 있게 하고 외부 모듈에서는 상속을 못하게 하는 클래스

외부 모듈에서 어떻게 현재 프로그램에 접근할 수 있다는거지

sealed class Expression {}

다른 파일에서 접근 시 private이라 상속할 수 없다하고 같은 프로젝트 안에 있다면 어느 파일에 위치하든 상관없다. 디렉터리와 프로젝트가 다른 가봐

object 타입

클래스 객체를 선언하지 않고 anonymous 클래스를 선언해서 해야할 때가 있다. 자바에서는 anonymous inner 클래스를 사용했고 코틀린에선 object가 받는다.

webView.setWebViewClient(object : WebViewClient() {
    override fun onPageStrated(view: WebView?, url: String?, favicon: Bitmap?) {
        super.onPageStarted(view, url, favicon)
    }
})

object는 익명 클래스에게도 쓰이지만 한 번 쓰고 다시 사용되질 않을 클래스에게도 쓰인다.

interface Shape {
    fun onDraw()
}

val triangle = object:Shape{
    override fun onDraw() {
    }
}

함수의 리턴타입으로도 사용할 수 있다. 그래서 함수 다음 .을 이용해 함수 안에서 사용하는 변수에 접근이 가능하다.
private함수, 퍼블릭 함수에 따라 다른 결과 나온다. 퍼블릭에서는 리턴타입으로 object를 선언하면 Any로 변경되면서 리턴값을 선언하지 않은 것과 똑같이 동작. 이렇게 설정된건 클래스 내부에서 선언되어 있는 값을 외부에서 건들 수 없게 하기 위함.

함수

함수 선언

fun name (name: type, ...) : return type
fun add(x: Int, y: Int): Int {
    return x+y
}

간단한 수식으로 처리되는 함수에는 리턴 인자형에 =을 ㅣ용하여 한 줄로 표현 가능

fun add(x: Int, y: Int): Int = x+y

리턴값과 인자의 형이 같으면 리턴 밸류 생략 가능

fun add(x: Int, y: Int) = x+y

코틀린에서 리턴값 적지 않으면 기본적으로 Unit 리턴 이는 void와 같다.

함수 기본값 가능한가? 가능하다.

변수 타입

기본적ㅇ로 object 타입으로 처리. 기본적인 타입변수는 같다. 자바와. 코틀린은 모두 대문자 시작.

차이점

1) 자동변환기능 허용 X
2) Int에서 Char로 타입 변환 자동 X 아스키코드 매직 불가
val i = 32
val char2Int: Char = i.toChar()
3) |, & 대신 or, and 표현

비트 연산시 이거 쓴다. int, long에서만 가능

shl(bits)
ushr: unsigned shift right
xor(bits) ~
inv() !

리터럴 같이 사용하면 변수형 선언 안해도 ㄱㅊ 차피 정적으로 들어가는 데이터들은 다 컴파일러에서 지정해주는거 아닌가?

varagr

다양한 변수 값을 하나의 변수로 전달 바을 때 사용

fun add(vararg numbers: Int): Int {
    var total: Int = 0
    for (n in numbers)
        total += n
    return total
}

list 선언하지 않고 받고싶으면 사용. 오버로드 할일 좀 줄어든다.

흐름 제어 연산자

for 문의 범위 설정

코틀린에서 범위 다루기 위해 in, ..., step, downTo 연산자 존재한다.

오름차순

for (i in 2..9) {}

내림차순

for (i in 9 downTo 2) {}

step 설정도 가능

for (i in 2..9 step 2) {}

iterator로 데이터 접근도 가능

for (name: String in nameList) {}

switch 대신 when

override fun onClick(v: View?) {
    when(v?.id) {
        R.id.search_button -> {
        }
        R.id.search_go_btn -> {
        }
    }
}
when {
    array.size < 0 -> {
    }
    (0 < array.size) and (array.size < 10) -> {
    }
}

else 필요 없는겨?

콜렉션

자바 자료구조인 JCF. 고틀린에선 수정이 가능한 녀석과 그렇지 않은 녀석으로 구분했다. 코틀린에선 클래스를 선언하여 쓰지 않아도 리스트를 만들 수 있도록 함수 제공한다.

자료형 뒤 Of 붙고. 이 함수들은 수정이 불가능한 함수로 읽기 전용 수정 가능한건 mutable가 앞에 붙는다.

val nameList = listOf("Mike", "Victoria", "Bill")

수정불가한 애들한텐 val 붙어주는거 잊지 말고!

mutable로 만들면 자바의 API다 사용 가능하다.

null인 변수를 필터해서 사용할 수 있는 함수 filterrNotNull를 제공한다.

for (name in nameList.filterNotNull()) {}

+, - 로 원소추가 제거 가능

fun printMap() {
    val map = mapOf(1 to "one", 2 to "two")
    var newMap = map - 1
    newMap += 3 to "three"

    val list = listOf("one", "two","three")
    var newList = list - "one"
    newList += "four"
}

immutable 객체임에도 불구하고 +, - 해서 새로 만ㄷㄹ어지는 결과 변수는 mutable하다.

타입 체크와 비교연산

코틀린 타입체크는 is, 타입캐스팅은 as, is는 instanceof와 같고 as는 (type)과 같다.

NPE에 안전한 변수 선언 방법

변수명 뒤 ? 붙여야 null 값 넣을 수 있다. 널 가능한 모든 변수에 널 값 참조 이쓸 수 있으니 처리하라고 메시지를 출력한다.

val len: Int? = a?.length

len은 널값 참조할 수 있다, a 널 아닌 경우 length 가져와 처리하라. 여전히 널 들어갈 가능성 있다. 널이면 기본값을 설정하는 방법이 있다.
elvis ?: 엘비스가 누군데

var a: String? = "abc"
a = null
var len: Int = a?.length?:0
var a: String? = "abc"
a = null
var len : Int = a?.length?:0

명시적으로 변수가 절대 null 참조할 수 없도록 해주는 기호 !!. 널값 참조하면 바로 주거버려. 완전악질 아니냐.. nullsafty 하랬더니 있을때랑 없을 때 똑같은거 아니야?

var a:String? = "abc"
a = null
var l: Intt = a!!.length

비교 연산자 == 와 ===

==은 값 같은지 === 포인터 같은지

==의 경우 a가 null이 아니면 equals 실행. 수행하고 null이면 b가 null인지 확인. a, b 모두-- 연산에 대해 null 값이 여도 예외는 발생하지 않아. a, b가 둘 다 null인 경우 참값이 발생한다.

람다의 형태

anonymous inner class는 익명 크래스로 코드 중간에 클래스를 선언해 바로 만들어 쓰는 클래스다.

람다는 고차 함수 인자로 받아 처리하는 방법. inline으로 함수 선언해 중첩적 코드 생략하고 실행되는 코드에 집 중 가능.

inner 클래스에서 받게 되는 인자를 적고 -> 기호, 다음에는 함수 이름 없이 바로 실행문

(타입 매개 변수) -> {실행문...}

코틀린은 람다 표현 부분응ㄹ 반드시 {}로 감싸야한다.

코틀린에서 함수의 인자가 여러 개일 때 마지막 인자로 함수를 받는다면 람다표현식을 괄호 안에 넣지 않고 괄호 밖에서 선어날 수 있다.

val adapter = MyListAdapter(this, arrayListOf("bill", "mike")) {view -> Toast.makeText(this, "Button Clicked", Toast.LENGTH_SHORT).show()}

class MyListAdapter(val context: Context, val itemArray: ArrayList<String>, val itemClick: (view?)->Unit): BaseAdapter()

인자가 람다 형식을 가진다. 실행문 바디에 Unit 사용한 것은 기본적인 코틀린 모든 리턴값이 그거라서. 람다 함수 중 인자 값이 여러 개이면, 사용하지 않는 인자에 대해서 언더스코어 처리 가능

fun printMap() {
    val map = mapOF(1 to "one", 2 to "two")
    map.forEach { _, value -> println("$value!") }
}

1.1에 추가된거다 언더스코어

inline 키워드

함수 호출하지 않고 그대로 프록램 중간에 삽입해 컴파일된다. 함수 호출 비용 줄이고 메모리 줄인다. inline 너무 길면 함수를 호출하는거보다 비용 클 수 있어 최대한 간결한 코드에서 사용

inline fun supportsLollipop(code: () 0> Unit) {}

제네릭

컴파일 타임에 타입 정해주는 거

자바에선 부모자식관계 여도 대입이 안됐다.
코틀린에선 가능

GenericClass<Objects> obj = new GenericClass<>();
obj.set("aaaa");

val obj = Generic<Any>()
obj.set("Hello")

와일드 카드

모든 객체 대입이 가능하다는 와일드 카드 심볼 사용. 범위 제한이 자바랑 달라.

먼저 상위 제한을 설정 <? extends T> T를 상속한 애들만 사용 가능. 상속하는 애들 중 한 타입으로만 고정 가능. 뭐가 올지 모르니 쓰기 불가

코틀린 에서는 out을 사용한다. 읽기만 가능하다.

하위에잔 <? super T> 그 조상만 사용가능. 객체는 부모 클래스가 되어 쓰기 가능. 부모 클래스가 구현이 안 된 부분이 있을 수 있어 읽기 불가능. 쓰기 전용 동장이라 input의 in tkdyd

ㅣㅑㄴㅅ<ㅑㅜ ㅆ>

임의

제네릭 클래스 선언 시 타입 생략 가능 경우와 못하는 경우 나뉘는데. 제네릭 클래스 서언 시 기본 생성자를 통해 선언한 타입 받는 경우 자동으로 타입 유추 가능해 생략, 아닌 경우 에러

class GenericNoCon<G> {}
class Generic<T>(cal t: T) {}

class UserGeneric {
    fun use() {
        val gen = Generic(10)
        val noCon = GenericNoCon<Int>()
    }
}

클래스 선언된거에 인자의 타입을 받는다 되어있어서 생략 가능한데 저건 아니야

그 밖 유 함

apply()

apply 함수는 block으로 정의된 구간을 수행하고 apply를 사용한 객체를 다시 반환

프록시 로 추가 기능 넣듯 하네..

fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

window.attributes = WindowManager. LayoutParams().apply [
    flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
    dimAmount = 0.8f
}

run()

두 가지 형태 사용. apply 처럼 객체에서 호출할 수 있고, run 자체 홀로 사용할 수 있다. 블럭 수행결과 리턴

fun <R> run(block: () -> R): R = block()
fun <T, R> T.run(block: T.() -> R): R = block()

convertView?.run {}

return 기존 객체가 아닌 결과

let()

let은 객체를 통해서만 실행 가능. run()과 유사. this로 넘겨 it을 통해 객체의 변수에 접근

fun <T, R> T.let(block: (T) -> R): R = block(this)

convertView.let{
    it.findViewById(R.id.add).setOnClickListener {
        Toast.makeText(context, "Clicked", Toast.LENGTH_SHORT).show()
    }
}

this로 넣어서 저렇게 왜 쓰는거지.. 객체가 주체가 되어 함수에 직접 들어가는 느낌이긴한데. 참 어렵네...ㅇ ㅓ디다 쓰는겨

with()

with 함수는 기존의 함수와 다르게 넘기려는 객체를 괄호 안에 넣고 {}를 수행한다. with()와 run()은 같다 봐야하지만 run은 with(), let을 혼합해 놓은거

with (convertView) {
    findViewById(R.id.add).setOnClickListener {
        Toast.makeText(context, "Clicked", Toast.LENGTH_SHORT).show()
    }
}

with는 객체로 념겨주기에 내부에서 null 체크 함 해야한다.

forEach()

for문 안 쓰고 바로 콜렉션 접근

Date().time(0..data.size -1).forEach { i -> 
    if () { return i}
}

onEach()

forEach와 비슷하지만 {} 안에 취한 행동의 결과값을 넘겨준다는 측면에서 더 편리하다
30대 이상인 사람에 list 넘겨줘 size 구하는 코드

fun getListSize(list: Array<Person>): Int = list.filter { it.age >= 30 }.onEach { Toast.makeText() }.size

filter()

콜렉션에서 필터에 선언한 부분만 발췌하여 결과값을 넘겨준다. filter 사용할 때 Boolean으로 결정할 수 있어야한다.

fun addOdd(): Int {
    var result = 0
    (1..50).filter { (it%2-1)==0 }.forEach { result += it}
    return result
}

lazy() lateinit()

코틀린에서 변수가 사용될 때 초기화 할 수 있게 lazy 함수 있다. 변수가 선언되는 시점에 초기화를 진행하지 않고 사용되는 시점에 생성되도록 한다. 사용되는 시점에 생성이 가능한 것은 lazy ㅎ마수는 {}안의 코드를 실행하고 실행된 결과값을 기억하고 있다가 돌려주기 때문이다.

class ManinActivity: AppCompactActivity() {
    val toolbar: Toolbar by lazy {
        findViewById(R.id.toolbar) as Toolbar
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        toolbar.setTitle("")
    }
}

lateinit은 var에 대해서만 사용 가능

클래스 변수, 메서드 만들기

class 안에서 companion object 만들기

class MyClass {
    companion object {
        val staticVariable: Int = 42

        fun staticFunction() {
            println("This is a static function")
        }
    }
}

최상위 변수, 함수 두기

val staticVariable: Int = 42

fun staticFunction() {
    println("This is a static function")
}

댓글