Kotlin, 어렵지 않게 사용하기 (1) - 기초

2022. 9. 10. 17:00Spring/Kotlin

kotlin의 기본 문법을 정리하고 용례를 확인하는 것이 해당 포스팅의 목표입니다.

🔗 Kotlin 시리즈 모아보기

 

 

안녕하세요. 이번 포스팅에서는 Java와 비교하여 kotlin 문법을 익히도록 합니다.

추석을 맞아 코틀린 도장깨기를 하려했는데 생각보다 시간이 많이 걸리네요 🥲

이번 달 내로 코프링 데모 프로젝트를 만들어보고 싶어 시작했습니다 💪🏻

 


 

fun

: 함수 및 메서드 선언 방식

 

- 최상위 수준 정의 가능

📌 vs.Java : 자바와 다르게 클래스 안에 넣어야 할 필요가 없음

 

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

 

 

 

 

if

코틀린 if는 값을 만들어내지 못하는 문장statement이 아니고 결과를 만드는 식 expression

삼항연산자와 비슷 (e.g. (a > b) ? a : b)

 

// 바로 Return 가능 (fyi. kotlin의 if 절은 식(expression)이기 때문에 값 return)
fun ex1_max(a: Int, b: Int): Int = if (a > b) a else b

// Return Type 생략 가능
fun ex2_max(a: Int, b: Int) = if (a > b) a else b

 

ex1: if 문에서 return a 혹은 return b가 아닌 a, b 를 직접 return하는데 코틀린에서 if 절은 식expression이기 때문

 

💡 Expression VS Statement
expression (식): 값을 만들어 내며 다른 식의 하위 요소로 계산에 참여할 수 있음
statement (문): 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로 존재하며 아무런 값을 만들어내지 않음

 

 

- 중괄호로 둘러싸인 함수    : 블록이 본문인 함수 expression body

- 등호와 식으로 이뤄진 함수: 식이 본문인 함수 block body

코틀린에서는 block body가 많이 사용됨

 

 

 

 

println

: 콘솔 출력 

 

📌 vs.Java: System.out.printin 대신 사용

 

코틀린 표준 라이브러리는 여러 가지 표준 자바 라이브러리 함수를 간결하게 사용할 수 있게 감싼 래퍼 wrapper를 제공하는데 println도 그 중 하나

kotlin - Console.kt

 

 

 

 

Variable

✔️ val 

value. 변경 불가능한 immutable 참조를 저장하는 변수

- val로 선언된 변수는 일단 초기화하고 나면 재할당이 불가능

📌 vs.Java: final 변수에 해당


✔️ var

variable. 변경 가능한 mutable 참조. 값이 바뀔 수 있음

📌 vs.Java: 일반 변수에 해당

 

// ==== val ====
val gngsn = "변수 지정"        // 타입 추론: String
val yearsToCompute = 7.5e6   // 타입 추론: Double (부동소수점floating point 상수)
val answer = 42              // 타입 추론: Int
// or 타입 직접 지정
val answer:Int = 42
// answert = 31  <-- ERROR: Val cannot be reassigned

// ==== var ====
var message = "코틀린을 언제 다 뗄 수 있을까 ~.~"
message = if (Random(3).nextInt() > 10) "아마 10일 뒤.." else "지금은 아님"

 

타입을 지정하지 않으면 컴파일러가 초기화 식을 분석해서 초기화 식의 타입을 변수 타입으로 지정

 

💡 기본적으로는 모든 변수를 val 키워드를 사용해 불변 변수로 선언하고, 나중에 꼭 필요 할 때에만 var로 변경하라
변경 불가능한 참조와 변경 불가능한 객체를 부수 효과가 없는 함수와 조합해 사용하면 코드가 함수형 코드에 가까워진다

 

 

 

 

 

String Template

: 문자열 템플릿. 문자열에 편리한 변수 출력

fun main(args: Array<String>) {
    val name = if (args.size > 0)
        printin("Hello, $name!") args [0] else "Kotlin"
}

 

$variable${variable} 은 동일한데, 확실한 명시를 위해 중괄호 ${} 로 둘러싼 형태를 쓰도록 하자

 

중괄호로 둘러싼 식 안에서 문자열 템플릿을 사용 가능

예를 들어 ${if (s.length > 2) short" else "normal string ${s}"}와 같은 문자열도 사용할 수 있다.

 

📌 vs.Java: 자바의 "Hello, " + name + "!" 와 코틀린의 "Hello, $name!" 과 동일

 

 

 

 

Class

Java Class
Kotlin Class

 

코드없이 데이터만 저장하는 클래스를 Value object, VO라 부르는데, 

코틀린을 비롯한 다양한 언어가 위와 같이 간결하게 기술할 수 있는 구문을 제공

 

- 프로퍼티를 소괄호 내에 정의하면서 name을 파라미터로 받는 생성자 생성

- 위의 정의로 자바의 Person(String name) {} 에 해당하는 기본적인 생성자가 됨 

- 추가적인 생성자 정의는 constructor(...) {} 를 따로 정의할 수 있음

 

접근제한자: 기본 가시성은 public이며 생략 가능

 

 

Property

📌 vs.Java
클래스의 목적은 데이터를 캡슐화encapsulate이며, 자바에서 이를 위해 프로퍼티property를 사용
프로퍼티property : 필드 + 접근자

1# 필드 field
: 자바에서는 데이터를 보통 비공개Private 필드field에 저장

2# 접근자 메소드 accessor method
 - 게터getter: 필드 읽기

 - 세터setter: 필드 변경

 

코틀린 프로퍼티는 자바의 필드와 접근자 메소드를 동일한 개념으로 사용

 

1# 필드 field

프로퍼티를 선언할 때는 변수 선언과 동일하게 val이나 var를 사용

 

2# 접근자 메소드 accessor method

기본적으로 자동 생성

 

val name : 읽기 전용 프로퍼티. 비공개 필드, 공개 게터 private field, public getter 생성

var isMarried: 쓸 수 있는 프로퍼티. 비공개 필드, 공개 게터, 공개 세터 private field, public getter, public setter 생성

 

 

코틀린에서는 getter와 setter를 필드에 직접 접근하는 것처럼 사용

val person = Person("gngsn", false)

println(person.name) 		// Getter: gngsn
println(person.isMarried) 	// Getter: fasle

person.isMarried = false;	// Setter

 

 

Custom

아래처럼 get() 으로 custom한 프로퍼티 생성 가능

 

Rectangle(val height: Int, val width:Int) {
    val isSquare: Boolean
        get() = height == width
}

fun test_properties() {
    val rect = Rectangle(200, 200)
    println(rect.isSquare)
    // rect.isSquare = true  !ERROR
}

 

 

 

enum

열거형

 

enum 은 소프트 키워드soft keyword

class 는 키워드 keyword

enum은 class 앞에 있을 때 특별한 의미(다른 곳에서는 이름으로 사용 가능)

 

코틀린에서 유일하게 세미콜론이 필수

 

// ex1
enum class Color { 
	RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET 
}

// ex2
enum class Color(
	val r: Int, val g: Int, val b: Int 			// 상수 프로퍼티 정의
) {
	RED(255, 0, 0), ORANGE(255, 165, 0),
	YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
	INDIGO(75, 0, 130), VIOLET(238, 130, 238);  		// 반드시 세미콜론 표시

	fun rgb() = (r * 256 + g) * 256 + b     		// enum 클래스 내 메소드를 정의
}

 

 

 

when

Java의 switch 문을 대치하지만 훨씬 강력

 

- if와 마찬가지로 when도 값 생성

- 식이 본문인 함수에 when 을 바로 사용할 수 있음

- 각 분기의 끝에 break를 넣지 않아도 됨

오른 쪽은 enum class Color 의 모든 필드를 모두 불러와서 코드를 깔끔하게 만들 수 있음

 

 

한 분기 안에서 여러 값을 콤마(,)로 연결해 매치 패턴으로 사용할 수도 있음

 

 

 

 

 

Smart Cast

타입 검사 이후 타입을 명시하지 않아도 컴파일러가 캐스팅을 수행

 

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

 

 

is는 자바의 instanceof와 비슷하지만 명시적으로 변수 타입을 캐스팅하지 않아도 스마트 캐스트 발생

 

fun eval(e: Expr): Int {
    if (e is Num) { 
        val n = e as Num		// 캐스트 타입을 직접 명시 (스마트 캐스트 사용 X)
        return n.value
    }
    if (e is Sum) {				// 변수 e에 대해 스마트 캐스트를 사용
        return eval(e.right) + eval(e.left)
    }
    throw IllegalArgumentException("Unknown expression")
}

 

 

 

while

코틀린 while 루프는 자바와 동일

 

while (condition) {
   /* ... */
}

do {
   /* ... */ 
} while (condition)

 

 

 

for

for <item> in <elements>

 

for는 자바의 for-each 루프에 해당하는 형태만 존재

초깃값, 증가 값, 최종 값을 사용한 루프를 대신하기 위해 코틀린에서는 범위를 사용해서 수열을 생성

수열progression: 일정한 순서로 이터레이션하는 경우

 

val oneToTen = 1. . 10

 

// 순차적인 수를 세면서 3으로 나눠떨어지는 수는 Fizz, 5로 나눠떨어지는 수는 Buzz라고 말해야 하며
// 어떤 수가 3과 5로 모두 나눠떨어지면 'FizzBuzz'라고 말하는 게임
fun fizzBuzz(i: Int) = when {
        i % 15 == 0 -> "FizzBuzz "
        i % 3 == 0 -> "Fizz "
        i % 5 == 0 -> "Buzz "
        else -> "$i "
}

>> for (i in 1..100) println(fizzBuzz(i))
// 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz ...

// 100 downTo 1은 역방향 수열을 만든다 역방향 수열의 기본 증가 값은 -1
>> for (i in 100 downTo 1 step 2) print(fizzBuzz(i))
// Buzz 98 Fizz 94 92 FizzBuzz 88 86 Fizz 82 Buzz Fizz 76 74 Fizz Buzz 68 Fizz 64 62 FizzBuzz ...

 

downTo : 역방향 수열 생성. 기본 증가 값은 -1

 

 

 

Map iteration

val binaryReps = TreeMap<Char, String>()

for (c in 'A'..'F') {
  val binary = Integer.toBinaryString(c.code)
  binaryReps[c] = binary	// Java에서 binary.put(c, binary) 와 동일
}
        
for ((letter, binary) in binaryReps) {
  println("$letter = $binary")
}
// A = 1000001 | B = 1000010 | C = 1000011 | D = 1000100 | E = 1000101 | F = 1000110 |

 

 

withIndex

인덱스와 함께 컬렉션을 이터레이션

val list = arrayListOf ("10", "11", "1001")

for ((index, element) in list.withIndex()) {
  print("[$index] $element, ")
}
// [0] 10, [1] 11, [2] 1001,

 

 

 

 

in

: 어떤 값이 범위에 속하는지 검사

 

fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
isLetter('q')	// true

 

!in

어떤 값이 범위에 속하지 않는지 검사

 

fun isNotDigit(c: Char) = c !in '0'..'9'  // '0'<= c && x<= '9' 로 변환
isNotDigit('x')	// true

 

when과 함께 사용 가능

fun recognize(c: Char) = when (c) {
	in '0'..'9' -> "It's a digit!"
	in 'a'..'z', in 'A'..'Z' -> "It's a letter!"
	else -> "I don't know..."
}

recognize('8')	// "It's a digit!"
recognize('x')	// "It's a letter!"

 

 

java.lang.Comparable 인터페이스를 구현한 클래스라면 비교가 가능하기 때문에

그 클래스의 인스턴스 객체를 사용해 범위를 만들 수 있음

 

println("Kotlin" in "Java".."Scala")		// true
println("Kotlin" in setOf("Java", "Scala")	// false

 

 

"Kotlin" in "Java".."Scala"은 코틀린이 "Java" <= "Kotlin" && "Kotlin" <= "Scala"로 변경

 

 

 

Exception

코틀린의 예외exception 처리는 자바나 다른 언어의 예외 처리와 비슷
함수는 정상적으로 종료할 수 있지만 오류가 발생하면 예외를 던질throw 수 있음

 

- 자바 코드와 가장 큰 차이는 throws 절이 코드에 없다는 점

: 자바에서는 체크 예외를 명시적으로 처리하지만,

코틀린은 체크 예외와 언체크 예외unchecked exception를 구별하지 않음

코틀린에서는 함수가 던지는 예외를 지정하지 않고 발생한 예외를 잡아내도 되고 잡아내지 않아도 됨

 

 

try, catch, finally

 

- 발생한 예외를 함수 호출 단에서 처리catch하지 않으면 함수 호출 스택을 거슬러 올라가면서

   예외를 처리하는 부분이 나올 때까지 예외를 다시 던짐

 

fun readNumber(reader:BufferedReader): Int? {
	return try {
		Integer.parseInt(reader.readLine())  // return 값
	} catch (e: NumberFormatException) {
		null
	} finally {
		reader.close()
	}
}

 

위의 코드처럼 try도 식expression이 될 수 있음

 

 

 

 

 

그럼 지금까지 Kotlin 기본 문법에 대해 알아보았습니다.

오타나 잘못된 내용을 댓글로 남겨주세요!

감사합니다 ☺️ 

 

 

| Ref |

Kotlin in action