Kotlin을 배워보자 #1-기본문법

이영준·2022년 6월 6일
0

코틀린 문법

목록 보기
1/4

📌Hello World

자바와 달리 코틀린은 클래스 내부에서 main을 선언하지 않고도 main 함수를 실행할 수 있다.

fun main() {
    println("Hello Kotlin!")
}
fun main(args: Array<String>) {
    println(args[0])
    println(args[1])
    println(args[2])
}

args를 넣는 경우에는 configurations에서 매개변수 값들을 넣어준다.

📌자료형

Byte, Short, Int, Long
Char, String,
Double, Float,
t/f 표현 Boolean이 있고
코틀린은 primitive data type이 아닌 모두 참조형으로 형을 선언하므로 대문자로 형을 써준다.

🔑타입 간 변환

코틀린은 자바보다 엄격한 타입체크를 한다.
자바처럼 Double에 Int 형을 넣을 수 없다.
따라서 toDouble()과 같은 메소드를 이용해서 명시해야 한다.

    val a = 5.toByte() //Byte
    val b = 65.toChar() //Char
    val c = 10.toShort() //Short
    val d = 10 //Int
    val e = 10L //Long

    val f = 10.0f //Float
    var g = 10.0 //Double

또한 val result = 1L + 3과 같이 Long+Int는 가능한데 이 때의 result 타입은 Long이다.

🔑string """

string을 삼중 따옴표로 감싸면 키보드로 입력한 줄바꿈이 그대로 반영된다.

🔑trimindent - 들여쓰기 제거

var ch = '\uAC00'
    println(ch) //가

    val str = """
        여러분
        모두 화이팅!
        """.trimIndent()
    println(str)

결과

여러분
모두 화이팅!

trimindent 미사용시

      여러분
      모두 화이팅!

🔑String 비교 -> equals 대신 ==

    val test1 = "ff"
    val test2 = "ff"
    if(test1 == test2){
        println("true")
    }
    else
        println("false")

자바와 달리 true가 나온다. 코틀린은 연산자 오버로딩을 통해 ==에 equals 메소드가 구현되어 있다.

🔑{$} -> 문자열 템플릿

val i = 100
    val test1 = "ff + $i.toString()"
    val test2 = "ff + ${i.toString()}"
    println(test1)
    println(test2)

ff + 100.toString()
ff + 100

🔑substring -> 문자열 자르기

val stringSequence = "문자열"
    val temp = stringSequence
if(temp is String){
        println(temp.substring(1 , endIndex (선택사항)))

자열

🔑typealias

typealias Num = Int

와 같이 타입 별명 붙이기가 가능하다.

🔑Any , Number

Any 타입에는 모든 타입을 다 할당할 수 있고, 수치형은 Number타입에 할당할 수 있다.

Number는 할당하는 수치형의 자료형을 자동으로 스마트 캐스팅을 해주어 편리하다.

    var test1:Number = 6.2 // test1 은 Double형으로 스마트캐스팅됨.
    println("value : $test1 , data type: ${test1.javaClass}")
    test1 = 12 //Integer 형으로 스마트캐스트
    println("value : $test1 , data type: ${test1.javaClass}")
    test1 = 12L //Long 형으로 스마트캐스트
    println("value : $test1 , data type: ${test1.javaClass}")
    test1 += 12.0f //Float형으로 스마트캐스트
    println("value : $test1 , data type: ${test1.javaClass}")

value : 6.2 , data type: class java.lang.Double
value : 12 , data type: class java.lang.Integer
value : 12 , data type: class java.lang.Long
value : 24.0 , data type: class java.lang.Float

🔑is -> 타입 체크

val number = 256
    if (number is Int) { // num이 Int형일 때
        print(number)
    } else if (number !is Int) { // number 이 Int형이 아닐 때, !(number is Int) 와 동일
        print("Not a Int")
    }
    println()

is를 통해 타입 체크가 가능하다.

🔑Unit, Nothing

Unit 타입은 함수의 반환값이 없음을 명시적으로 나타낼 때 쓰인다.
Noting은 null만 반환하는 함수나 예외를 던지는 함수의 반환타입을 나타내기 위해 쓰인다.

fun some(): Unit {
	print("no return")
}

fun some2(): Nothing {
	return null
}

fun some3(): Nothing {
	throw Exception()
}

📌변수 var, 상수 val

fun hi(){
    val ten : Int = 10
    var b : Int = 9
    val hundred = 100
    var name = "Lee"
    b = 10
    println(hundred)
    println(ten)
    println(b)
}

val은 값을 바꿀 수 없는 상수,
var은 값이 바뀔 수 있는 변수를 저장하는 형이다.

코틀린은 컴파일 타임에서 타입 체크를 한다. 변수를 선언할 때 val ten : Int = 10처럼 형을 명시해주거나 val hundred = 100처럼 형을 따로 명시안해줄 수도 있으나 val이나 var을 꼭 써서 선언해야 한다.

🔑변수를 선언만 하고 할당 안할 때

var ten

은 안되고

var ten : Int

와 같이 형을 선언해준다면 할당없이 선언이 가능하다. 이는 실행되는 함수 내에서만 해당하며 클래스 내부 변수 등에서는 불가능하다.

🔑초기화 미루기 - lateinit

lateinit var data1 : Int //오류
lateinit val data2 : String // 오류
lateinit var data3 : String // 성공

lateinit은 선언만 하고 초기화는 하지 않고 싶을 때 선언할 수 있으며 var키워드로 선언한 데이터여야 하고, Int,Long,Short,Boolean,Byte 등의 자료형에는 사용 못한다.

뷰 바인딩을 할 때
private lateinit var binding: ActivityMainBinding으로 변수 초기화를 미루고 oncreate에서 초기화 할 때 등 사용한다.

🔑초기화 미루기 - lazy

val data4 : Int by lazy {
    println("Hello WOrld")
    10
}
fun main(){
    println(data4 + 10)
}

20

by lazy{}로 중괄호 안에 실행문을 넣어주면 마지막 줄의 실행 결과가 그 변수의 초깃값이 된다. lazy로 되어있는 변수는 호출이 될 때 실행문이 실행된다.

lazy는 변수 초기화시 코드 블럭을 수행하는 것이므로, 멀티쓰레드 동작 시 고려가 있어야 한다.

SYCHRONIZED: 락을 사용해 단일 스레드만이 사용하는 것을 보장(기본값)
PUBLICATION: 여러 군데서 호출될 수 있으나 처음 초기화된 후 반환 값을 사용
NONE: 락을 사용하지 않기 때문에 빠르지만 다중 스레드가 접근할 수 있음

`private val model by lazy(mode = LazyThreadSafetyMode.SYNCHORNIZED)

🔑초기화 여부 판단 - ::.isInitialized

class Person {
    lateinit var name: String // lateinte을 위한 선언
    fun test() {
        if(! ::name.isInitialized) { // 프로퍼티의 초기화 여부 판단
            println("not initialized")
        } else {
            println("initialized")
        }
    }
}

함수 스코프 내에서 말고 클래스 멤버변수에서만 isInitialized를 사용할 수 있다.
참고로 단순히 name이 아니라 ::name이라고 선언하는 이유는 name이라는 String의 메소드에 접근하는 것이 아닌 property로서의 name의 메소드에 접근하려는 것이기 때문이다.

🔑변수를 string형태로 출력(템플릿 문자열)

fun main() {
    val name = "Lee"
    val age = 25
    println("my name is $name .Nice to meet you.")
    println("my age is ${age}years old")
}

$표시와 중괄호로 변수를 묶어 string형태로 변수를 출력할 수 있다.
중괄호로 묶지 않고도 가능한데, 이 때 $뒤에 나오는 변수명 뒤에 다른 말을 쓰려면 공백으로 구분되어야 한다.

$를 문자 형태로 직접 출력하려면 백슬래시 \를 앞에 써주면 된다.

📌연산자

=== 두 개 항의 참조가 같으면 true, 아니면 false (!==)
나머지는 자바와 동일

📌함수

🔑void 함수

package com.example.myapplication

fun main() {
    helloWorld()
    println(add(1,3))
}

//1.함수

fun helloWorld(): Unit {
    println("Hello World!")
}

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

C, Java와 마찬가지로 kotlin도 main함수가 실행된다.
함수를 정의할 때는 리턴값에 상관없이 fun을 붙여주며, void 함수로 리턴값이 없는 경우에 위처럼 중괄호 이전에 : Unit 을 일반적으로 써준다. 써주지 않아도 실행에 문제는 없다.

🔑매개변수, 반환값이 있는 함수

fun add(a: Int, b: Int = 10): Int {
    return a+b
}

위 함수를 보자. a,b를 정수형으로 매개변수로 받으며 위와 같이 a: Int의 형식으로 형의 첫글자를 대문자로 적어주며, 마지막에 return 하는 형을 : Int 로 선언전에 적어준다. 매개변수의 기본값도 줄 수 있어 선언하지 않을 시 기본값으로 적용된다.

fun add1(x1:Int, x2:Int):Int = x1 + x2

📌조건문

🔑if

fun maxBy(a : Int, b: Int) : Int{
    if(a>b){
        return a
    }
    else
        return b
}

대체로 C와 사용 방식이 유사하다.

C에서 삼항 연산자를 사용하는 것과 유사하게

fun maxBy2(a : Int, b: Int) = if(a>b) a else b

와 같이 간결한 함수 작성도 가능하다.

🔑when

fun checkNum(score : Int){
    when(score){
        0 -> println("this is 0")
        1 -> println("this is 1")
        2,3 -> println("this is 2 or 3")
        else -> println("I don't know")
    }

    var b = when(score){
        1->1
        2->2
        3->3
        else -> 3
    }
}

switch 문과 유사하게 변수의 여러가지 값에 따라 출력이나 리턴값을 지정해 줄 수 있다.
이 때 var b와 같이 변수에 리턴값을 저장할 때 when을 사용한다면 꼭 else문이 포함되어야 한다.

    when (x) {
        1 -> println("x == 1")
        2, 3 -> println("x == 2 or x == 3")
        in 4..7 -> println("4부터 7사이")
        !in 8..10 -> println("8부터 10사이 아님!!")
        else -> println("x는 1이나 2가 아님")
    }
    // x == 1

또한 조건 여러개에 해당하는 값이어도 앞의 조건만 만족하면 바로 return을 한다.
위 예시에서 x = 1이라면 "8부터 10사이 아님!!"이 출력되지 않는다.

🔑in

    when(score){
        in 90..100-> println("You are genious")
        in 10..80-> println("You are not bad")
        else -> println("I don't know")
    }
    if(score in 90..100){
        println("You are genious")
    }

in 90..100이라는 것은 90<=score<=100과 같은 의미이다.

📌expression / statement

	코틀린에서 statement 와 expression 를 구분하는 방법은 값을 만들어 내는 차이에 있습니다.
    expression 은 값을 만들어내고 또 다른 expression 의 하위 요소로 계산에 참여할 수 있습니다. 
    반면에 statement 는 최상위 요소로 존재하며 어떠한 값도 만들어내지 않습니다. 

예를 들어 Java에서는 if문은 값을 리턴하는 것이 아니므로 statement 이지만 코틀린에서는

if(a>b) a else b

이와 같이 반환값이 있는 expression이 될 수 있다.

📌Array(배열)와 list(리스트)

array는 값을 바꿀 수 있는 자료구조로

val array = arrayOf(1,"2",3.4f)
val num : Array<Int> = arrayOf(1,2,3,4)

와 같이 정의한다.

자료 인덱스에 대한 접근은 println(num[0]), println(num.get(0))와 같이 한다. 또한 위처럼 다양한 타입의 값들을 array에 넣어줄 수 있다.

다만 Array<Int>와 같이 구체적으로 타입을 명시해주면 int만 넣어야 한다. 타입을 명시 안하면 자동으로 Array<Any>로 인식한다.

반면 list는 mutable list가 아닌 이상 값을 추가하거나 변경할 수 없으며

val list = listOf(1,2,11L)

와 같이 정의한다.

값을 바꿀 수 있는 mutableList로 리스트를 만들기 위하여

val mutablelist = arrayListOf<Int>(1,2,3)
mutableList.add(4)
mutableList.add(5)

와 같이 arrayListOf 함수를 사용하여 만들어주며, 이때 값을 바꿀 수 있다.

여기서 값을 바꾸는 데도 val로 리스트 변수를 선언할 수 있는 이유는 리스트에 값을 추가하는 것이 변수의 주소값에는 영향을 주지 않기 때문이다.
물론 mutableList = arrayListOf(4,5,6)과 같이 재할당은 할 수 없다.

🔑indices

val items = listOf("a","b","c")
        println(items.indices)

인덱스 접근메서드 이다. 0..2를 출력한다.

📌반복문

🔑for

fun forAndWhile(){
    val students = arrayListOf<String>("joyce", "Lee" , "Kim")
    for(name in students){
        println("${name}")
    }
}

기존의 C 사용법과 유사하다.

    for(i in 1..10){
        println(i)
    }

in 을 사용하여 1~10까지의 range로 반복문을 표현할 수도 있다.

step

    for(i in 1..10 step 2){
        println(i)
    }

1부터 10까지 2의 간격씩 i를 더해가며 반복할 수도 있다.

    for(i in 10 downTo 1){
        println(i)
    }

downTo

큰 수에서부터 작은 수로 가면서 반복할 때는 위와 같이 downTo를 사용하면 된다.

    for(i in 10 downTo 1){
        println(i)
    }

until

    for(i in 1 until 100){
        println(i)
    }

1..100과 달리 100이 포함되지 않는다.

withIndex()

    //val students = arrayListOf<String>("joyce", "Lee" , "Kim")
    for((index : Int ,name : String) in students.withIndex()){
        println("index = ${index} name = ${name}")
    }

결과:
index = 0 name = joyce
index = 1 name = Lee
index = 2 name = Kim

withIndex() 메서드로 인덱스와 해당값을 함께 반환할 수도 있다.

🔑while

    var index : Int = 0
    while(index<10){
        println("Index = ${index}")
        index++
    }

while문도 크게 C와 다르지 않다.

🔑label

중첩된 반복문에서 완전히 탈출하고 싶을 때 사용하면 유용하다.

📌Nullable / NonNull

Java에서 흔히 발생하는 런타임 에러인 NPE = Null Pointer Exception, 즉 널값을 가리키는 포인터 문제를 컴파일 단계에서 바로잡아 개발하기 수월하기 위하여
Kotlin에서는 ?를 통해 이를 해결하고 있다.

🔑? (선언)

    var name = "Lee"
    //var name : String = null
    var nullName : String? = null
    var nameInUpperCase = name.toUpperCase()
    var nullNameInUpperCase = nullName?.toUpperCase()

일반적인 변수에는 null을 저장할 수 없으나, 형 뒤에 (형 선언을 꼭 해줘야 ? 사용 가능) ?를 붙여줌으로서 null지정을 할 수 있다.

쉽게 생각하면 nullName에 올 수 있는 타입은 String과 null이 있는 것이다.

🔑?. (호출)

null값에는 적용할 수 없는 함수인 toUpperCase메서드의 경우에도 메서드 사용 앞에 ?를 써주면, null 값일 때는 그대로 null 을 반환하고 오류가 출력되지 않도록 해줄 수 있다.

var temp: String? = null
    val size2 = if (temp != null) temp.length else -1

참고로 위 코드와 같이 코틀린에서는 if가 return 값을 가지므로 이를 바로 할당할 수 있다. (따라서 자바와 같이 삼항연산자가 필요없음)

🔑?: (엘비스프레슬리 연산자)

변수 뒤에 붙어 이 변수가 null인 경우에 사용할 기본값을 지정해주는 연산자이다.

    val lastName : String? = null
    val fullName = name + (lastName?: " No lastName")
    println(fullName)

결과
Lee No lastName

fun getName(str: String?) : String {
    val name = str ?: "Unknown"
    return name
}

위 함수는 엘비스 프레슬리 연산자를 사용해 null 값이 반환 될 일이 없으므로 반환 타입을 String?이 아닌 String이라고 할 수 있다.

🔑!! -> nonNull 단정기호

변수가 절대 null값이 오지 않을 것임을 명시해주는 연산자이다

fun ignoreNulls(str : String?){
    //val NotNull : String = str
    val NotNull : String = str!!
    val upperNotNull = NotNull.uppercase()
    println(upperNotNull)
}

!!없이 val NotNull : String = str만을 쓰면 nullable한 변수를 nullable하지 않은 변수에 넣으려 했으므로 오류가 나지만, !!를 붙여줌으로써 str은 null값이 절대 아닐 것임을 프로그램에게 알려준다. 따라서 uppercase()와 같은 함수를 사용하더라도 null 값이 절대 안올 것이기에 ?를 안 붙여줘도 된다.

위 방식은 매개변수로 null 이 안올 것임을 명시하고 있기 때문에 null값이 실제로 들어오면 nullPointerException이 나게 된다.

fun ignoreNulls(s: String?) {
    val sNotNull: String = s!!
    println(sNotNull.length)
}

ignoreNulls(null)

오류
Exception in thread "main" java.lang.NullPointerException

🔑let

    val email : String? = "james_22@naver.com"
    email?.let{
        println("my email is ${email}")
    }

let은 let이 쓰인 해당 변수가 null이 아닌 경우 그 변수를 식 안으로 들여와 쓸 수 있다.

profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글