ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2. 코틀린 언어 기초 : 기본 문법
    Study(종료)/Kotlin 22.09.13 ~ 12.18 2022. 9. 21. 22:32

    언어 기본 문법을 보니 신기한 내용(자바와 다른)이 많이 보인다.

    막연히 val, var 두가지 자료형을 이용해 모든 자료형을 표현하는 것만 알고 있었는데,

    2장을 공부하며 차이점을 좀 더 자세히 알아봐야겠다.


    2-1. 기본 문법

    1) 주석

    코틀린은 세 가지 주석을 지원하며 코드를 문서화 할 때 사용한다

    - 한 줄짜리 주석

    - 여러 줄 주석

    - KDoc 여러 줄 주석

    // 1. 한 줄 짜리 주석
    
    /* 2. 
    여러
    줄
    주석
    */    
          
    /** 3.
     *  KDoc
     *  여러
     *  줄
     *  주석
     */

    2) 변수 정의하기

    코틀린에서 변수를 선언하는 형태는 아래와 같다.

    val a = 0 // Kotlin
    val b = 3.14F // Kotlin
    val c = 'A' //Kotlin
    val s1 = "sample" // Kotlin
    val s2: String 
        s2  = "Sample" // Kotlin

    코틀린 변수 선언 시 변수 타입을 작성할 경우의 정규 표현식(Regular Expression)은 위의 s2 변수를 선언할 때와 같다.

    여기서 말하는 정규 표현식이란 원래는 띄어쓰기를 이상하게 넣어도 컴파일 에러가 나지 않는다면 실행하는데 문제가 없지만

    협업을 할 때 문법이 너무 중구난방이 되는것을 막기 위해 하나의 규칙으로 정해둔 것 이라고 생각하면 된다

    다른 정규 표현식은 밑의 사이트에서 확인할 수 있다.

    https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/

     

    Regex - Kotlin Programming Language

     

    kotlinlang.org

    변수 선언시 변수에 커서를 올리고,  ctrl + shift + P 를 누르면 컴파일러가 추론한 변수의 타입이 나온다.

    b의 타입 확인


    var a : Int = 3 // x
    var b: Int = 2 // regular Expression
    var c :Int = 4 // x

    자바와 비교를 위해 아래는 자바의 변수 선언 방법을 작성해 보았다.

    int a = 0; // Java
    float b = 3.14; // Java
    char c = 'A'; // Java
    String s = "sample"; // Java

    코틀린은 자바와 다르게 변수 정의 뒤에 세미콜론(;)이 붙지 않는것을 확인할 수 있다.

    나아가서 코틀린은 줄 끝에 세미콜론을 생략해도 된다.

     

    val 키워드는 value에서 유래한 것으로, 불변(immutable)변수이다.

    불변 변수는 한번 초기화하면 다시는 값을 대입할 수 없는 변수이며, 자바의 final변수와 유사하다.

     

    사용자에게 정수 두 개의 값을 입력받아 그 두 수의 곱을 표현하는 프로그램을 작성해보았다.

    val a = readLine()!!.toInt()
    val b = readLine()!!.toInt()
    
    println(a * b)

    위 코드 내용을 하나씩 설명해보자면, 

    readLine() 함수는 사용자에게 한 줄의 입력값을 받고, 이를 문자열로 반환해주는 코틀린 함수이다

     

    !!는 널 아님 단언 선언으로 readLine()의 결과가 null일 경우 예외를 발생시킨다.

    null은 아무 값도 가지지 않는다는 의미로 위 예시에서는 사용자가 아무 값을 입력하지 않았을 경우

    readLine() 함수를 통해 null값이 들어오는데, 이를 방지하기 위해 !! 를 사용한다.

     

    toInt() 함수는 String 클래스가 제공하는 메서드로, 메서드가 호출한 대상 문자열을 정숫값으로 변환한다.

    쉽게 설명하자면, 사용자의 입력값은 문자열 형식(String)으로 저장되는데, 이를 정수형(Int)으로 바꾸는 함수이다.

    그리고 입력받은 문자열 값을 정수 형로 바꾼 값을 변수 a에 저장한다

     

    println() 함수는 인자로 받은 값을 출력하는 함수이다.


    3) 가변 변수

    위에서 val 키워드를 사용한 변수는 불변 변수이다.

    var 키워드를 사용하면 가변(mutable) 변수를 정의할 수 있다.

    가변 변수는 원할 때 변수 값을 = 연산을 이용해 언제든지 바꿀 수 있으며 = 연산을 대입 연산자라고 부른다.

     

    하지만 처음 변수에 값을 대입할 때 추론된 변수 타입은 가변/불변 둘 다 계속 유지된다.

    코드를 통해 자세한 예시를 확인해보자

    var sum = 1 // sum = Int
    sum = 200 // 정상 동작
    sum = "Hello" // Error

    위와 같이 정수형(Int)로 선언된 가변 변수에 정수 타입인 200을 대입할 수 있지만,

    문자열(String) 값인 "Hello"는 대입할 수 없다.

     

    추가로 코틀린은 복합 대입 연산 이라는 대입과, +(더하기) -(빼기) *(곱하기) /(나누기) %(나머지 연산) 등의 이항 연산을 조합한 연산도 제공한다

    var res = 3
    res += 2 // res = 5
    res *= 10 // res = 50

    자바와 다르게 코틀린 대입은 아무 값도 돌려주지 않는다.

    이로 인해 코틀린은 자바의 a = b = c 같은 대입문 연쇄를 쓸 수 없다.

     

    코틀린은 증가(++) 연산과 감소(--)도 있다. 해당 변수의 값을 1 증가시키거나 감소시키는 연산인데,

    증가, 감소 연산자의 위치에 따라 출력하는 값에 유의해야 한다.

    var res = 3
     
    println(res++) // 3
    println(++res) // 5
    println(--res) // 4
    println(res--) // 4

    3번쨰 줄의 res++의 출력값이 3인 이유는 증가 연산자가 매개변수 뒤에 있으므로,

    먼저 println() 함수로 출력을 한 수, res값에 1을 더하는 연산이 된다.

    그래서 저 코드를 실행하면 res값은 4이지만, 출력값은 3이 나오게 된다.

    4번쨰 줄의 ++res는 println() 함수보다 res + 1 연산이 먼저 실행되서

    실제 res값과 출력되는 값 모두 5이다.

    5, 6번째 줄 역시 위의 설명과 비슷하게 이해를 할 수 있다.

     

    4) 식과 연산자

    단항과 이항 연산마다 연산 순서를 결정하는 우선순위가 정해져 있다.

    예를들면 우리가 3 + 4 * 7 이 식을 계산할 때 덧셈보다 곱셈 연산을 먼저 하는 방식을 떠올리면 편하다.

    연산자 우선순위는 아래의 표와 같다

     

    단항 연산

    분류 연산자 예제 우선순위를 감안한 해석
    후위 ++ -- a * b ++ a * (b++)
    ++ b -- ++ (b --)
    a * b.foo() a * (b.foo())
    전위 + - ++ -- ! +a * b (+a) * b
    ++ a * b (++ a) * b
    !a || b (!a) || b

    이항 연산

    분류 연산자 예제 우선순위를 감안한 해석
    곱셈 *, /, % a * b + c (a * b) + c
    a - b % c a - (b * c)
    덧셈 +, - a + b and c (a + b) and c
    중위 이름이 붙은 중위 연산자들 a < b or b < c (a < (b or b) ) < c 
    a == b and b == c (a == b) and (b == c)
    비교 <, > , <=, >= a < b == b < c (a < b) == (b < c)
    a < b && b < c (a < b) && (b < c)

    이항 연산

    분류 연산자 예제 우선순위를 감안한 해석
    동등 ==, != a == b | | b != c (a == b) | | (b != c)
    논리곱 && a | | b && c a | | (b && c)
    논리합 | | a && b | | c (a && b) | | c
    대입 =, +=, -=, *=, /=, %= a = b * c a = (b * c)
    a * = a + b a * = (a + b)

    우선순위가 같은 이항 연산자는 왼쪽에서 오른쪽으로 순서대로 계산된다

    2-2. 기본 타입

    자바와 달리 코틀린 타입은 근본적으로 어떤 클래스 정의를 기반으로 만들어 진다.

    이 말은 Int와 같이 원시 타입과 비슷한 타입들도 메서드와 프로퍼티를 제공한다는 뜻 이다.

    변수가 다양한 메서드와 프로퍼티를 지원하는 모습

    타입은 하위 타입(sub type)이라는 개념으로 계층화할 수 있다. A 타입이 B 타입의 하위 타입이라는 말은

    근본적으로 B 타입의 값이 쓰일 수 있는 모든 문맥에 A 타입의 값을 넣어도 아무 문제가 없다는 뜻 이다.

    예를들어 널을 허용하지 않는 모든 코틀린 타입은 Any라는 내장 타입의 직/간접적인 하위 타입이다.

    따라서 다음 코드는 1이라는 값을 박싱하게 만든다.

    val n : Any = 1

    1) 정수 타입

    코틀린에는 정수를 표현하는 네 가지 기본 타입이 있다.

    이름 크기(바이트) 범위 대응하는 자바 타입
    Byte 1 -128 ~ 127 Byte
    Short 2 -32768 ~ 32767 Short
    Int 4 -2^31 ~ 2^31 - 1 Int
    Long 8 -2^63 ~ 2^63 - 1 Long

    코드를 작성할 때 L을 접두사로 붙이면 Long 타입이 된다.

    var a = 100L

    앞에 0b를 붙이면 2진수로, 0x를 붙이면 16진수로도 작성할 수 있다.

    var a = 0b10101 // Int
    var b = 0xFF21A // Int

    각 정수 타입에는 최소값과 최댓값을 포함하는 상수 정의가 들어있다.

    이런 상수를 사용하려면 앞에 타입 이름을 붙여야 한다.

    println(Byte.MAX_VALUE) // 127
    println(Byte.MIN_VALUE) // -128
    
    println(Short.MAX_VALUE) // 32767
    println(Short.MIN_VALUE) // -32768
    
    println(Int.MAX_VALUE) // 2147483647
    println(Int.MIN_VALUE) // -2147483648
    
    println(Long.MAX_VALUE) // 9223372036854775807
    println(Long.MIN_VALUE) // -9223372036854775808

    2) 부동소수점 수

    자바와 마찬가지로 코틀린도 IEEE 754 부동소수점 수를 따르는 Float와 Double을 제공한다.

    기본적인 형태는 정수 부분과 소수 부분을 나눠 소수점(.)을 찍어놓은 형태이다

    val pi = 3.14
    val x = 2.22

    정수 부분이 비어있는 경우 정수 부분을 0으로 간주한다.

    val x = .141592

    코틀린은 과학적 표기법을 허용한다.

    과학적 표기법이란 e나 E 뒤에 10을 몇번 거듭제곱하는지 알려주는 숫자가 온다.

    val pi = 0.314E1 // 3.14
    val pi100 = 0.314E3 // 314.0
    val piOver100 = 3.14E-2 // 0.0314

     

    3) 산술 연산

    모든 수 타입은 기본 산술 연산을 지원한다.

    연산
    + (단항) 원래 값과 같은 값
    - (단항) 원래 값의 부호를 반전한 값
    + 덧셈
    - 뺼셈
    * 곱셈
    / 나눗셈
    % 나머지

    4) 비트 연산

    아래 표를 통해 연산의 종류와 결과를 확인해보자

    연산 예제 결과 해당하는 자바 연산
    shl 왼쪽 시프트(shift) 13 shl 2 52 <<
    (-13) shl 2 -52
    shr 오른쪽 시프트 13 shr 2 3 >>
    (-13) shr 2 -4
    ushr 부호 없는 오른쪽 시프트 13 ushr 2 3 >>>
    (-13) ushr 2 1073741820
    and 비트 곱(AND) 13 and 19 1 &
    (-13) and 19 19
    or 비트 합(OR) 13 or 19 31 |
    -13 or 19 -13
    xor 비트 베타합(XOR) 13 xor 19 30 ^
    -13 xor 19 -32
    inv 비트 반전(inversion) 13.inv() -14 ~
    (-13).inv() 12

    5) 문자타입 Char

    Char타입은 유니코드 한 글자를 표현하며 16비트이다.

    작은따음표( ' ) 사이에 문자를 넣으면 된다.

     

    +, - 연산자를 통해 문자에 수를 더하거나 밸 수 있다. 더하거나 뺀 수 만큼 코드포인트가 이동한 새 문자를 반환한다.

    두 문자로 뺄셈을 하면 두 문자의 코드포인트 간 거리를 얻을 수 있다.

    문자를 ++ 또는 -- 로 증가/감소시킬 수 있다.

    var a = 'a'
    var h = 'h' 
    println(a + 5) // f
    println(h - 5) // c
    println(h - a) // 7
    println(--h) // g
    println(h) // g
    println(++a) // b

     

    6) 수 변환

    각 수 타입마다 값을 다르 수 타입으로 변환하는 연산이 정의돼 있다.

    예를들어 toByte(), toShort(), toInt(), .. 등이 있다

    Char 값에 대해서도 같은 범위의 연산을 제공한다.

     

    정수 타입 사이의 변환은 대상 타입이 더 큰 범위를 담는 타입인 경우 손실 없이 수행된다.

    var a = 35555
    
    println(a) // 35555
    println(a.toByte()) // -29
    println(a.toShort()) // -29981
    println(a.toChar()) // 諣
    println(a.toLong()) // 35555

    7) 불 타입과 논리 연산

    코틀린은 참(true)이나 거짓(false) 중 하나로 판명되는 불(Boolean) 타입의 논리 연산을 제공한다

    var isChecked = false
    val testPassed = true

    불 타입이 지원하는 연산은 다음과 같다

    -  ! : 논리 부정

    -  or, and, xor : 즉시 계산 방식의 논리합, 논리곱, 논리베타합

    - | |, && : 지연 계산 방식의 논리합, 논리곱

     

    지연 계산 방식에 대해 자세히 설명하자면,

    | | 연산의 경우, 왼쪽 연산이 참일 경우 오른쪽 연산을 계산하지 않고 true를 반환한다.

    && 연산의 경우, 왼쪽 연산이 거짓일 경우 오른쪽 연산을 계산하지 않고 false를 반환한다.  

    이런 계산은 왼쪽 피연산자에 부수 효과가 포함된 경우 유용할 수 있다.

     

    자바와 다르게 자바의 & 연산자는 코틀린의 and로

    자바의 | 은 코틀린의 or로 대체된다.

     

    의심스러운 경우에는 괄호를 써서 코드의 의미를 명확히 하는 것을 권장한다

     

    8) 비교와 동등성

    앞에서 언급한 모든 타입은 아래와 같은 비교연산을 제공한다.

    비교 연산자 의미
    == 같다
    != 같지 않다
    < ~보다 크다
    > ~보다 작다
    <= ~보다 크거나 같다
    >= ~보다 작거나 같다

    기본적으로 코틀린 타입은 두 인자가 모두 같은 타입일 때 에만 ==와 !=를 허용한다.

    하지만 모든 수 타입은 서로 < <= > >= 를 이용해 비교할 수 있다.

     

    NaN(Not a Number)은 어떤 값과도 같지 않다. 심지어 다른 NaN, 무한대와도 같지 않고 크거나 작지 않다

    그래서 어떤 식의 결과값이 NaN일 때 확인하기 위해서는 isNaN() 함수를 호출해야 한다.

    아래는 isNaN함수의 실제 코드이다

    public static boolean isNaN(double var0) {
            return var0 != var0;
    }

    위에서 말한 정의대로, NaN은 NaN과 비교가 불가능하므로 NaN인지 확인하기 위해

    호출한 값을 나 자신과 비교해 다르다면 true를 반환하는 것을 볼 수 있다.

    2-3. 문자열

    1) 문자열 템플릿

    $, { }를 이용해 어떤 식이든 문자열에 넣을 수 있다

    사실 C#에서 이와 비슷한 방식을 지원해서 보자마자 C#이 떠올랐다.

    아래 코드는 C#에서 문자열 보간을 이용해 문자열 사이에 변수를 추가한 코드이다

    double a = 3;
    double b = 4;
    Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5 * a * b}");
    // Area of the right triangle with legs of 3 and 4 is 6

    그리고 코틀린에서 문자열 템플릿을 이용한 코드이다 

    println("string template sample ${java.time.LocalTime.now()} end")
    // string template sample 22:43:43.976 end

    용어는 다르지만 두 방식이 유사한 것 같은데,

    코틀린이 좀 더 유연하게 문자열 사이에 변수를 넣을 수 있는 것 같다.

     

    2) 기본 문자열 연산

    모든 String 인스턴스는 가지고 있는 문자 수를 표현하는 length와 문자열의 마지막 문자 인덱스를 표현하는 lastIndex 프로퍼티를 제공한다.

    또한 각괄호([ ])안에 넣는 연산자를 이용해 개별 문자에 접근할 수 있다.

    val s = "Hello"
    s.length // 6
    s.lastIndex // 5
    s[2] // l
    s[5] // o

     

    코틀린에서 ==를 사용해 문자열을 비교할 수 있으며 이는 자바에서 .equals() 메서드를 호출하는것과 동일한 기능이다

    그리고 자바의 == 연산자처럼 주소값(책에서는 참조 동등성을 비교한다고 설명)을 비교하려면 코틀린은 ===를 사용한다.

    var s1 = "sample"
    var s2 = "sample"
    
    println(s1 === s2) // 주소 비교, 참조 동등성 비교
    println(s1 == s2) // 실제 문자열 비교

    Java 코드

    String s1 = "sample";
    String s2 = "sample";
    if(s1 == s2) // false > 주소 비교(주소가 동일하면 true 반환)
    if(s1.equals(s2)) // true > 실제 문자열 비교

    자바에서는 == 연산자를 통해 비교하면 실제 내용은 같아도, false를 반환하는 경우가 많다.

    그래서 실제 문자열을 비교할 때에는 위와같이 .equals()메서드를 호출해 비교한다

     

    나는 문자열 비교를 할 때 주소를 비교하는 경우보다 실제 문자열을 비교하는 경우가 훨씬 많기도 하고

    자바에서 문자열 비교를 할 때 신경쓰지 않으면 == 를 통해 비교하고 결과값이 이상해서 고친적이 종종 있어서

    코틀린이 문자열 비교 방식은 훨씬 편한 것 같다.

    2-4. 배열

    1) 배열 정의하기

    일반적으로 사용하는 코틀린의 배열은 Array<T> 이지만, 모든 수를 박싱하므로 실용적이지 않고, 

    타입에 따라 IntArray<T>, ShortArray<T> 와 같은 특화된 배열을 사용하는게 실용적인 방법이라고 한다

     

    또한 코틀린에는 new 연산자가 없어 배열 원소를 명시적으로 초기화가 필요하다.

     

    그리고 개인적으로는 배열을 초기화 할 때 인덱스를 표현하는 변수인 it 이게 신기했다

    아래 코드는 it을 이용한 배열 초기화 방법이다

    val size = 5
    val arr = IntArray(size){it * (it+1)}
    // 0 2 6 12 20

    2) 배열 사용하기

    배열 타입은 문자열 타입과 size, lastIndex 프로퍼티가 있다는 점과 인덱스 연산으로 원소에 접근할 수 있다는 점이 비슷하지만,

    문자열과 달리 배열에서는 원소를 변경할 수 있다

    var s = arrayOf(1, 2, 3 ,4)
    s[1] = 10
    s[3]++
    // s = 1, 10, 3, 5

    배열을 사용할 때 주의할 점

    -  배열 타입의 변수가 실제 데이터에 대한 참조(주소)를 저장하므로

    배열 변수에 다른 배열을 대입하면 같은 데이터 집합을 공유한다

    (원본과 다른 배열을 만드려면 copyOf() 함수를 사용)

    val arr1 = IntArray(5){it * (it+1)}
    val arr2 = arr1;
    arr1[2] = Integer.MAX_VALUE
    println(arr2[2])
    // 2147483647 (= Integer.MAX_VALUE)

    val arr1 = IntArray(5){it * (it+1)}
    val arr2 = arr1.copyOf()
    arr1[2] = Integer.MAX_VALUE
    println(arr2[2])
    // 6

    - 배열의 원소를 서로 비교할 때 == 를 사용하면 안된다

    문자열 비교와 다르게, 배열의 원소를 비교할 때는 contentEquals() 함수를 사용

    == , !=연산자는 원소 자체를 비교하지 않고 참조를 비교

    val stringArr1 = arrayOf("a", "b", "c", "d", "e")
    val stringArr2 = arrayOf("x", "y", "c", "z", "w")
    println(stringArr1[1] == stringArr2[2]) // 참조를 서로 비교
    println(stringArr1[2].contentEquals(stringArr2[2])) // "c" 와 "c"를 비교

    - 상위 타입의 배열에 하위 타입의 배열을 대입할 수 없다

    val stringArr = arrayOf("a", "b", "c", "d", "e")
    val objArr : Array<Any> = stringArr // 컴파일 에러

    코틀린의 Any, 자바의 Object 는 둘 다 최상위 타입이다.

    반응형

    댓글

Designed by Tistory.