기타/AOS, Kotlin

[코틀린] 자바와 비교하며 기초 문법의 모든 것

kyxxn 2023. 7. 29. 01:00
728x90

자바와 크게 다른점

1. 세미콜론(;)을 쓰지 않는다.

2. 변수 선언 및 함수의 머릿줄이 다르다.

-> int a = 10;   ==   var a : Int = 10

-> void swap() || int add()가 아닌, fun swap() : Unit {}  || fun add() : Int {}

3. 타입 추론이 가능하다. var a = 10 가능

4. 조건식에 if뿐만이 아닌, when이 추가되었다는 점

5. Nullable, 자바의 NPE가 개선이 되었다는 점

 

..

나머지는 코드를 보며 알아보도록 하자

 

Hello World

fun helloworld() : Unit {
	println("Hello World")
}

이렇게 fun을 쓰고 함수 명()를 함.
콜론을 붙이고 Unit을 쓰면, 리턴 값이 없다는 의미가 됨.
즉, 이 함수는 void helloworld()와 같다.
마찬가지로 : Unit은 생략 가능
자바처럼 System.out.println()을 안 해도 됨

함수선언   함수명 (변수명 : 변수타입, 변수명 : 변수타입) : 반환타입{
	return a+b
}

fun add(a : Int, b : Int) : Int{
	return a + b
}
자료형의 앞 글자는 대문자여야 함

 

변수의 선언 ( val vs var )

val = value ( 변할 수 없는 수 = const = 상수)

var = variable (변할 수 있는 수 = 변수)

fun hi(){
	val a : Int = 10
	var b : Int = 10
	a = 100   // 에러	상수를 바꾸는 건 ㅂㄱㄴ
	b = 100
	
	var c = 1000 // 이렇게 : Int를 안 써도 알아서 int로 인식함
	var d = 1000 // 마찬가지.
	var e // 이건 에러임. 자료형이 타입 추론에 의해 정해지지 않았음
	var e = 100 //을 하던,
	var e : Int //를 하던 변수의 자료형을 정해줘야 함.
}

파이썬은 동적 타이핑 언어이다. 코틀린은 정적 타이핑 언어이고, 근데 코틀린은 var a = 100;이 가능하다.

이러한 이유는 ‘타입 추론’ 기능을 포함하기 때문인데, 이는 컴파일 시점에 이루어진다.

R_value의 타입을 보고 변수의 자료형을 추론한다는 의미이다.

 

파이썬은 변수의 타입이 프로그램이 실행되는 동안에도 변화될 수 있으나,

코틀린은 컴파일 때 타입 추론에 의해 정해진 자료형이 실행 도중에는 바뀌지 않는다.

 

문자열 (String Template)

fun main(args: Array<String>){
    val name = "Kyxxn"
    val lastName = "Park"
    println("내 이름은 ${name + lastName}입니다.")  => 내 이름은 KyxxnPark입니다.
		// 변수명을 문장 내에 넣고 싶으면 ${} 달러 사용
		// 지금 KyxxnPark가 붙어있는데, Kyxxn Park 할거면
		println("내 이름은 ${name + " " + lastName}입니다.") // ${}안에 문장 드가도 됨
}

 

조건식 (if와 when)

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

fun maxBy2(a : Int, b : Int) = if(a>b) a else b
// 위 두 함수는 같음
// 함수의 본문이 단일 표현식인 경우, 중괄호와 return 없이 '='을 사용하여
// 바로 작성이 가능함. 이를 "표현식 본문"이라고 함
// a와 b가 Int니까 알아서 반환 타입도 Int로 타입추론 됨

// if가 코틀린에선 표현식이므로, 값을 가질 수 있음. a와 b 중에 값을 가짐.

fun main(args: Array<String>){
    checkNum(3)
}

fun checkNum(score : Int){
    when(score){
        0 -> println("0 입니다") // score 변수가 0 이면 0입니다
        1 -> println("1 입니다") // 1이면 1입니다
        2, 3 -> println("2 or 3 입니다") // 2나 3이면 2 or 3입니다
        else -> println("0 1 2 3이 아닙니다") // 다른 수면 이렇게.
    }
    var b = when(score){
        3 -> 10 // score가 3이면 b = 10
        else -> 1 // 필수적인 문장. 3이 아니면 1로
    }
    println("b : ${b}")  => 10 나옴.

		when(score){
        in 1..2-> println("1, 2등급이면 공부 잘하네") // 1 <= .. <= 2
        in 3..4 -> println("3, 4등급은 놀진 않았네") // 3 <= .. <= 4
        else -> println("공부 안 했네")
    }
}

 

Expression (표현식) vs Statement (문장)

Expression : 값을 만들어 낸다

Statement : 값을 만들어내지 않음. 동작만 하는 느낌?

코틀린의 모든 함수는 Expression이다.

fun a() {

~~

}

이것 마저도 : Unit을 리턴하므로, Expression임.

Unit은 코틀린에서 반환 타입이 없음을 나타내는 타입인데, 이는 객체이며 표현식 ㄱㄴ.

자바, C, C++처럼 void는 Statement임. 그래서 값을 갖지 않음.

아래 두 문장은 Expression 임.

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

var b = when(score) 처럼 변수에 선언을 해줄 때는 항상 else문을 적어야 함.

나머지는 else 안 적어도 됨

 

 

Array와 List (Collection)

Array : 고정된 사이즈 (클래스 임. 정적)

List : 읽기 전용, 수정 불가능한 리스트 (인터페이스, get은 있으나 set은 없음)

MutableList : 수정 가능한 리스트, 대표적으로 ArrayList가 있음 (인터페이스)

→ ArrayList : MutableList 인터페이스를 정의하여 만든 클래스

fun array() {
    val array = arrayOf(1, 2, 3) // 정수형 배열
    val list = listOf(1, 2, 3) // 정수형 리스트

    array[0] = 1 // ㅆㄱㄴ
    list[0] = 10 // 불가능. list는 수정 불가능
    var result = list.get(0) // list 0번쨰 인덱스가 변수에 저장됨

    val array2 = arrayOf(1, 2.0, "삼") // Any 타입 배열
    val list2 = listOf(1, 2.0, "삼") // Any 타입 리스트
    array2[0] = "일" // 0번째 인덱스는 정수지만, Any 타입이므로 문자도 ㄱㄴ

		var arraylist = arrayListOf<Int>()
    // ArrayList는 MutableList 인터페이스를 구현하는 클래스
    // arrayListOf 함수는 ArrayList의 객체를 생성하고 초기화 하는 문장
    arraylist.add(10)
    arraylist.add(20)

    val arraylist2 = arrayListOf<Int>()
    arraylist2.add(100) // val이어도 요소 추가는 가능.
    // 동일한 객체(100)을 참조함.
    arraylist2 = arrayListOf() // 이건 오류
}

 

반복문 (For / While)

step, downTo, until

fun main(args: Array<String>){
		// for문
    val actor = arrayListOf("A", "B", "C", "D")
    for(i in actor){ // 변수명 in List or Array
        print("${i} ")
    }

    var sum = 0;
    for(i in 1..10){ // 1<= .. <= 10
        sum += i
    }
    println(sum)

    for(i in 1..10 step 2){ // 1<= .. <= 10, 2칸씩
        sum += i
    }
    println(sum) // 1 3 5 7 9 => 25

    for(i in 10 downTo 1){ // 10 >= downTo >= 1
        sum += i
    }
    println(sum) // 10 9 8 7 6 ... => 55

    // 1부터 99까지
    for(i in 1 until 10){ // 1 <= until < 100
        sum += i
    }
    println(sum) // 1 2 3 .. 9 => 46

		//While
		var index = 0
    while(index < 10){
        print("${index} ")
        index++;
    }

    val student = arrayListOf("박효준", "강민기", "이상진")
    for((i, name) in student.withIndex()){
        println("${i+1}번째 학생의 이름 : ${name}")
    }
}

 

Nullable / NonNull (엘비스 연산자)

NPE : Null Pointer Exception

⇒ 자바에서 런타임 에러로 잡아주는 예외인데, 돌려봐야 이 코드가 오류가 있는 지 암.

코틀린에선 컴파일 때 오류를 잡아줌

fun main(args: Array<String>){
    var name : String = "Kyxxn"
    var nullName : String = null // 에러
    var nullName2 : String ? = null 
		// 자료형을 지정했을 경우, null을 쓰려면 ? 를 붙여야함
    // NULL을 넣고 싶으면 ? 를 넣어줘야 함 + 타입 추론때매 자료형 생략 X
		// ? 를 붙인 걸 Nullable 타입.

		var nameCheck = name.toUpperCase() // 가능
    var nullNameCheck = nullName2.toUpperCase() // 에러
    var nullNameCheck2 = nullName2?.toUpperCase()
    // nullName2가 null인지 아닌지 모르기에 nullName2?. ~~()를 한다.
    // null이 아니라면 대문자로 바꾸고, 널이면 null을 넣어줌
		// 즉, nullNameCheck2 : String ?이 된 거임.
		

		// 엘비스 연산자 ?:
    // ?는 null일 경우 null을 반환
    // ?:는 null일 경우 디폴트 값을 반환
    val lastName : String ? = null
    val fullName = name + (lastName?: "No lastName")
    println(fullName)
		// lastName이 널이 아니면 그대로 출력
		// 널이면 ?: 연산자 뒤에 문구가 출력됨
}

fun ignoreNull(str : String?){ // Nullable 타입으로 들어올 때,
    // str에 절대 null이 안 들어온다면, 개발자가 컴파일에게 null이 아님을 명시 가능
    val mNotNull : String = str // 에러 Nullable 타입이니 ?가 붙어야함
    val mNotNUll : String = str!! // !!를 String? 객체에 붙이면 사용 가능, 잘 안씀
		val Upper = mNotNUll.toUpperCase() // mNotNUll이 String이므로 이렇게 가능.
}

 

코틀린에서 모든 타입은 기본적으로 null 값을 가질 수 없다.

var nonNullableString: String = "Hello World" // 이 변수는 null을 가질 수 없습니다.
var nullableString: String? = "Hello World"   // 이 변수는 null을 가질 수 있습니다.

let 함수

fun main(args: Array<String>) {
    ignoreNulls("gywns626")
}

fun ignoreNulls(str : String?){
    val email : String? = str
    
		// let 함수는 객체가 null이 아닐 경우, it으로 객체 값을 대체할 수 있음
		email?.let{ // null이 아닌 경우에만 람다 식 진행
        println("my email is : ${it}")
    }
}

만약 위 코드에서 email.let을 했다면, null이 email에 할당 됐을 때, NPE가 발생함

Nullable 타입이 아닌 Non-Nullable 타입에 null이 들어가서 let을 호출하려 했기 때문.

그래서 email?.let을 써야함

 

Class 클래스

코틀린의 클래스는 public 클래스가 파일 명과 같을 필요가 없음

+) 코틀린은 클래스 및 메소드는 모두 final 수식어를 갖고 있음. (그냥 상속이 안됨)

 

생성자에 대하여

class Human constructor(str : String){
    val str = str
    fun eating(){
        println("가나다")
    }
}

이렇게 해도 되지만,

class Human constructor(val str : String){} // 이렇게도 가능

class Human(val str : String){} // construct 생략도 가능

// construct 키워드 생략
// 디폴트 초기화해두기 = main에서 Human()도 되고 ("~~")도 됨
class Human(val str : String = "디폴트값 주기"){}

class Human (val str : String = "디폴트 값"){
    init{
        println("init 함수는 생성자가 동작할 때, 동작하는 함수")
    }
}

// 오버로딩 하고 싶으면 부생성자 construct를 클래스 내부에 명시
class Human (val str : String = "디폴트 값"){
		// 부 생성자는 주 생성자를 받아와야 함 : this(str)
    constructor(str : String, age : Int) : this(str){
        println("이건 부생성자, 오버로딩 발생될 때 호출됨")
    }
    init{ // 주 생성자의 일부
        println("init 함수는 생성자가 동작할 때, 동작하는 함수")
    }
}

 

상속

  1. 코틀린의 클래스는 모두 final 이므로, 부모 클래스 앞에 “open” 키워드를 붙인다.
  2. 코틀린의 클래스 내부의 메소드는 모두 final 이므로 오버라이딩 하려면 각각의 메소드 머릿말에 “open”을 붙인다.
  3. extends 키워드가 아닌 변수 선언하듯 콜론 클래스이름() 을 사용한다
open class Human (val str : String = "부모 생성자 변수"){
    var abc = "부모 클래스 지역변수"
    init{
        println("부모 생성자")
    }
    open fun singAsong() {
        println("hahaha")
    }
}

// Human()로 부모 생성자 호출 함.
class derived(val str2 : String? = "자식 생성자 변수") : Human() {
    init{
        println("자식 생성자")
    }
    override fun singAsong(){
        super.singAsong() // 부모 클래스의 singAsong 함수 호출
        println("하하하")
        println("부모 클래스 변수도 가져올 수 있다 : ${str}")
    }
}

fun main(args: Array<String>) {
    val obj = derived()
    println("${obj.abc}") // 자식에서 부모 클래스 지역변수 호출 가능
    println("${obj.str2}")
    obj.singAsong()
}