Kotlin (0) 소개 및 기본 문법

오늘은 Kotlin에 관해서 공부하는 시간을 가져보려고 한다!

학부생 시절 Java도 아닌 C++, Python만 주구장창 하다가 지금 와서 Kotlin을 하려니 자바 문법조차 가물가물해서 죽을맛이다.

Kotlin

☝ 왜 Kotlin인가?

몇 년 전까지만 해도 왜 Kotlin을 해야하냐는 의구심과, 질문들이 굉장히 많았었지만, 지금은 여러 지표와 발표들이 곧 Kotlin을 공부해야하는 확실한 이유가 된다.

간단하게 서술해보자면, 다음과 같다.

  1. 안드로이드 공식 개발자 가이드 1언어
    • 위 링크의 가이드에서 어떤 함수를 찾아보던, 이제는 제 1언어가 Kotlin, 두 번째가 Java다.
  2. 구글의 Kotlin 사랑
    • 이제는 유명한 구글의 Kotlin 사랑.
    • 2년 전 부터 구글은 안드로이드 개발에서 Kotlin을 지원했으며, 이후 배포하는 모든 라이브러리는 Kotlin으로 배포 할 예정이라고 한다.
  3. 대세 언어
    • 2019년 구글 I/O에서는 프로 안드로이드 개발자의 50%는 이미 Kotlin을 사용하고 있다고 선언하였다.

이 정도면 Kotlin을 공부해야 하는가에 대한 질문에 충분한 대답이 될 것이라고 생각한다!

🤞 기초 문법

후술할 Kotlin 함수들을 직접 작동시켜 보고 싶다면 Kotlin Playground를 온라인 ide로 이용하면 편하다.

2.1 변수 정의

Java

public int age = 17;

Kotlin

val age: Int = 17
val age = 17

Kotlin에서는 변수형을 지정하지 않고 변수를 정의하여도 컴파일러가 자동으로 추론하도록 되어있다.

val age = 17
age = 20	// 컴파일 에러 발생

Result

! Val cannot be reassigned

val은 java의 final과 같은 역할을 한다. 변경할 수 있는 형태로 선언하기 위해서는 var 형식으로 선언하면 된다.

var age = 17
age = 20	// 가능!

2.2 Single Expression return

Java

public int power(int x) {
	return x * x;
}  

Kotlin

fun pow(number: Int): Int {
	return number*number
}

fun main() {
    println(pow(4))
}

Result

16

Kotlin

fun double(number: Int) = number*2

fun main() {
    println(double(4))
}

Result

8

experssion으로 사용 할 때는 return 타입을 명시 하지 않아도 된다. (선택적으로 사용하면 된다!)

2.3 Null 연산자

2.3.1 Nullable Type (?)

Kotlin

fun double(number: Int) = number*2

fun main() {
    println(double(4))
    println(double(null))	// 컴파일 에러
}

Result

! Null can not be a value of a non-null type Int

Kotlin에서는 위처럼 선언된 함수의 생성자(number)에 null을 넣을 수 없기에, 위 함수를 실행하면 컴파일 에러가 발생한다.

만약 null을 넣는 시도를 하게 된다면, 컴파일 에러가 발생한다.

이를 방지하기 위한 함수는 아래와 같다.

Kotlin

fun doubleSafe(number: Int?): Int =
	if (number != null) number*2 else 0
	
fun main() {
    println(doubleSafe(null))
    println(doubleSafe(4))
}

Result

0
8

이처럼 ? 을 이용하여 함수를 정의한다면 number로 null이 입력 되면, 0을 return하게 된다.

2.3.2 Null Safe Operator (?.)

Java

if (str != null) str.length() else null

?. 을 Java로 표현하면 위와 같다.

이 문장을 Kotlin은 ‘?.’ 단 두 문자로 해결한다.

아래는 ?. 을 응용한 Kotlin 함수이다.

Kotlin

fun strLen(str: String?): Int? = str?.length

fun main() {
    println(strLen(null))
    println(strLen("hello, blog!"))
}

Result

null
12

?. 연산자를 사용하면 str이 null이 아니면 오른쪽의 함수를 수행하고, null이면 null을 반환한다.

str?.length() = null일 수 있기 때문에 len: Int? 로 nullable 하게 선언 한 것이다.

여기서, str == null이면 바로 null을 return한다는 점을 반드시 기억하자.

또한, property에 접근할 때도 ?. 을 사용할 수 있다.

Java

int double_major = if (student.majors.double_major != null) student.majors.double_major else null

Kotlin

val double_major = student.majors?.double_major

복수전공을 하는 학생일 경우 해당 과목 명을 가져오고, 아니라면 null이 된다.

데이터 형식은, 후술할 데이터 클래스 정의의 내용을 잠시 끌어와서 사용하였다.

2.3.3 Elvis Operator (?:)

고개를 좌로 90도 꺾고 바라보면 귀여운 캐릭터가 엘비스 프레슬리 머리를 하고있다.

그래서 Elvis operator이다!

?. 연산자는 좌항이 null일 때 null을 return 하지만, ?: 연산자는 좌항이 null이면 우항을 return한다.

Kotlin

fun doubleSafe(number: Int?): Int = (number ?: 0)*2

fun main() {
    println(doubleSafe(null))
    println(doubleSafe(4))
}

Result

0
8

Kotlin의 강력한 null 연산자를 잘 활용하면 이렇게 까지 함수를 압축할 수 있다!

아직은 처음이라 연산자들이 낯설고 헷갈리지만, 이 실용성에 익숙해지면 다시 자바로 돌아가기 싫을 것 같다.

2.4 데이터 클래스 정의, getter, settter

Java

public class Student {
	private String name;
	private String major;
	private int age;
	private int s_id;

	public Student(String name, String major, int age, int s_id) {
		this.name = name;
		this.major = major;
		this.age = age;
		this.s_id = s_id
	}
	
	public String getName() {
		return name;
	}
	
	public void setName(string name) {
		this.name = name;
	}

	..... // 추가로 getter, setter, toString..

Kotlin

data class Student (var name:String, var major:String, var age:Int, var s_id:Int)

data로 class를 정의하여 생성자에 매개변수만 정의하면 getter, setter, toString 등 모두 사용 가능하다.

사용 예시

Java

Student student = new Student("Donggi Hong", "mathmatics", 23, 201421150);
String name = student.getName();
String major = student.getMajor();
int age = student.getAge();
student.toString();

Kotlin으로 작성한 데이터 형식이라도 Java에서 정상적으로 호환이 가능하다.

Kotlin

var student = Student("Donggi Hong", "mathmatics", 23, 201421150);
var name = student.name
var major = student.major
var age = student.age
student.toString()

2.5 Nested Class

Kotlin

data class Student (
	var name:String,
	var majors:Majors,
	var age:Int,
	var s_id:Int
){
	data class Majors(
		var major: String,
		var double_major: String? = null,
		var minor: String? = null
	)
}

위 처럼 생성자 () 뒤에 {}을 붙이고, 그 안에 데이터 형식을 선언하면 끝이다.

중첩 클래스로의 접근은 다음과 같이 이루어진다.

var student = Student("Donggi Hong", Student.Majors("mathmatics","SW engineering", null), 23, 201421150);
var major = student.majors.major
var double_major = student.majors.double_major
var minor = student.majors.minor

2.6 String Interpolation

말이 장황하지만, 그냥 string에 변수 끼워넣는 것을 칭한다.

Kotlin

fun main() {
    val str = "Blog!"
    println("Hello, $str")
}
fun main() {
    val str = "Blog!"
    println("Hello, ${str}")
}

Result

Hello, Blog!

두 코드 모두 같은 실행 결과를 갖는다.

$뒤에 한 개의 변수만 입력된다면, {}를 쓰지 않아도 되지만, 아래와 같이 2개 이상의 변수를 사용 할 예정이라면 반드시 사용하여야한다.

Kotlin

fun main() {
    val str1 = "Hello, "
    val str2 = "Blog!"
    println("$str1+str2")
}

Result

Hello, +str2

Kotlin

fun main() {
    val str1 = "Hello, "
    val str2 = "Blog!"
    println("${str1+str2}")
}

Result

Hello, Blog!

2.7 For문

Kotlin

fun main() {
	val numbers = listOf('하나', 2, 'three', 4, '오') 
	for(number in numbers){
		print("$number ")
	}
}

Result

하나 2 three 4 오

Python의 for문과 비슷한 형태를 하고 있다.

Python은 for number in numbers인 반면,

Kotlin은 for(number in numbers)로, 괄호를 한번 더 사용하여야 한다.

index를 사용해 for문을 작동시키고 싶다면 다음과 같이 하면 된다.

Kotlin

fun main() {
	val numbers = listOf("하나", 2, "three", 4, "오") 
	for(i in numbers.indices){
		print("${numbers[i]} ")
	}
}

Result

하나 2 three 4 오

list 자체를 이용한 for문과의 차이점이라면, numbers[i]를 반드시 {}로 묶어주어야 한다.

묶어주지 않으면 한번의 시행 마다 numbers 리스트를 출력하고, 뒤에 [i]라는 문자열을 붙여서 출력한다.

잘못된 예제는 다음과 같다.

Kotlin

fun main() {
	val numbers = listOf("하나", 2, "three", 4, "오") 
	for(i in numbers.indices){
		print("$numbers[i] ")
	}
}

Result

[하나, 2, three, 4, 오][i] [하나, 2, three, 4, 오][i] [하나, 2, three, 4, 오][i] [하나, 2, three, 4, 오][i] [하나, 2, three, 4, 오][i]

2.8 When문

다른 언어의 switch문과 유사하다.

차이점은, 여러 타입을 사용하여도 알아서 컴파일러가 번역해서 잘 사용한다는 점이다.

Kotlin

fun checkNums(obj: Any) {
    when (obj) {
        "하나" -> println("$obj <- 이건 하나네!")
        2 -> println("$obj <- 이건 둘이고,")
        "three" -> println("$obj <- 얘는 세 번째.")
        4 -> println("$obj <- 네번째는?")
        else -> println("$obj <- 이건 모르겠다;;")
    }
}

fun main() {
	val numbers = listOf("하나", 2, "three", 4, "오") 
	for(number in numbers){
		checkNums(number)
	}
}

Result

하나 <- 이건 하나네!
2 <- 이건 둘이고,
three <- 얘는 세 번째.
4 <- 네번째는?
오 <- 이건 모르겠다;;

2.9 Collections

다른 언어처럼 List, Map, Set 등의 자료구조가 Kotlin에도 역시 존재한다.

listOf() 메소드를 사용해 배열을 선언하면 이는 변경할 수 없는 리스트가 된다. 대신, mutableListOf() 를 사용하여 배열을 선언하면 변경할 수 있는 리스트가 된다.

Kotlin

fun main() {
	val numbers = mutableListOf("하나", 2, "three", 4, "오") 
	print("$numbers \n")
	numbers.add("six")
	print("$numbers \n")
	numbers.remove("하나")
	print(numbers)
}

Result

[하나, 2, three, 4, 오]
[하나, 2, three, 4, 오, six]
[2, three, 4, 오, six]

이런 식으로, 배열에 원소를 넣고 빼는 것이 가능하다.

이 뿐 아니라, filter, map 등을 사용해 배열을 관리할 수 있다.

Kotlin

fun main() {
	val numbers = mutableListOf(1, 2, 3, 4, 5) 
	print(numbers.filter{it >= 3})
    print("\n")
	print(numbers.map{it * 2})
    print("\n")
	print(numbers.filter{it >= 3}.map{it * 2})
}

Result

[3, 4, 5]
[2, 4, 6, 8, 10]
[6, 8, 10]

it 대신에, num -> num같은 형식으로 객체를 전달하여 사용 할 수도 있다.

🤟 Kotlin 답게 사용하자.

위 같은 강력하고 좋은 기술들을 두고 Java처럼 짜지 않도록 스스로 경계해야한다.

Kotlin은 Java의 대부분의 문법들을 사용할 수 있지만, 그보다 간결하고 직관적으로 코딩하는 방법이 여러가지 존재한다.

하지만 Java가 익숙한 사람이라면 자신도 모르게 Java에서 사용하던 문법을 언어만 바꿔서 그대로 사용하는 문제가 발생 할 것이다.

Kotlin을 앞으로 공부하며 한동안은 계속 사용하게 될텐데, 이런 오류에 빠지지 않도록 스스로 조심하며 코딩하도록 노력해야겠다.

태그:

카테고리:

업데이트:

댓글남기기