Programming Language/Kotlin
코틀린 뽀개기
수연초이
2022. 10. 8. 19:49
코틀린이란?
- 자바 플랫폼에서 돌아가는 새로운 프로그래밍 언어
- 간결하고 실용적
- 자바 코드와의 상호운용성을 중요시
- 코틀린 컴파일러가 생성한 바이트 코드는 일반적인 자바 코드와 똑같이 효율적으로 실행되어 성능 측에서 손해가 없다. (요 부분은 나중에 더 공부해보자) 참고로 코틀린이 자바 문법으로 컴파일하는 것보단 둘다 JVM 언어이므로 코틀린으로 만들어낸 바이트 코드나 자바로 만들어낸 바이트 코드 결과물이 같다.
정적 타입 지정 언어
- 굉장히 똑똑한 컴파일러. x = 1인 경우 int형으로 타입을 확정짓는다.
- 자바와 달리 타입을 뒤에 명시한다. 타입을 생략 가능. 변수를 앞에 선언한다.
- 세미콜론(;)을 붙이지 않아도 된다.
var x: Int = 1
var x = 1
함수
- 기본적으로 자바의 메서드는 클래스가 있기 때문에 메서드가 존재한다.
- 코틀린은 함수가 일급 객체이므로 함수를 최상위 수준에 정의 가능하다. 꼭 클래스안에 넣을 필요가 없다.
- 코틀린은 함수를 선언할 때 `fun` 키워드를 사용한다.
- main(String[] args)도 필요 없이 main() 으로 가능하다.
fun main() {
println("I'm SuyeonChoi.")
}
함수와 메서드의 차이?
- 함수: 단일 객체로도 존재 가능
- 메서드: 기본적으로 클래스에 있는 함수. 클래스에 포함된다.
변수 선언
- val: 값이 변경되지 않는 변수. 값 재할당 불가. final이 생략되어있다고 생각하면 된다.
- var: 값이 변경될 수 있는 변수
자바에서는 기본적으로 함수는 무조건 블록(중괄호)가 있어야한다. 그러나 코틀린에서는 한줄로 표현할 수 있는 경우 중괄호를 생략할 수 있다!(리턴 대신 등호 사용 가능)
블록이 본문인 함수
fun max(a: Int, b: Int) : Int {
return if (a > b) a else b
}
식이 본문인 함수
fun max(a: Int, b: Int) : Int = if (a > b) a else b
Person 학습 테스트
Person.java
public class Person {
private final String name;
private final int age;
private String nickname;
public Person(final String name, final int age, final String nickname) {
this.name = name;
this.age = age;
this.nickname = nickname;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getNickname() {
return nickname;
}
}
해당 자바 코드를 코틀린으로 변환하면?
Person.kt
class Person(val name: String, val age:Int, val nickname: String)
- 클래스는 public이 기본이고 생략 가능
- name, age, nickname은 var 타입인 경우 getter와 setter가 제공되고, val 타입인 경우 getter만 제공
- 자바에서는 name, age, nickname을 '필드'라고 하고, getter와 setter를 포함하여 '프로퍼티'로, 클래스 및 필드 기반이다. 코틀린은 프로퍼티 기반이다. 필드라는 개념이 없다.
- name, age, nickname은 현재 모두 public으로 외부에서 접근 가능. name을 외부에서 접근하지 못하도록 하려면? private 키워드를 붙여주면 된다.
class Person(private val name: String, val age:Int, val nickname: String)
하지만 여기서 조금 더 코틀린 다운 코드를 만들어야한다...
Person 클래스 테스트
@Test
fun constructor() {
assertDoesNotThrow {
Person("최수연", 2, "페퍼")
}
}
- Person 생성 시 new 키워드가 필요 없다.
- (val name: String, val age:Int, val nickname: String) ⬅️ 이 부분이 주 생성자이다(Primary Constructor). 자바에서는 주 생성자가 개념적으로 있지만, 코틀린은 문법적으로 지원한다.
- assertDoesNotThrow의 { } 중괄호는 람다. 자바에서 람다는 무조건 소괄호로 시작되는 부분이 있어야하지만, 코틀린은 생략 가능
닉네임 없이 생성할 때, 이름을 닉네임의 값으로 설정하고 싶다면?
class Person(val name: String, val age:Int, val nickname: String) {
constructor(name: String, age: Int) : this(name, age, name)
}
class PersonTest {
@Test
fun constructor() {
assertDoesNotThrow {
Person("최수연", 2)
}
}
}
- 프로퍼티는 기본적으로 주 생성자에서 정의한다. 주 생성자의 역할은 생성자 + 프로퍼티 선언이다. 따라서 부 생성자에서는 var, val이 붙어있지 않다. 즉, var과 val는 프로퍼티에만 붙는다.
- constructor() 로 부 생성자 생성 가능
- 주 생성자에서도 constructor를 붙일 수 있지만 생략 가능. 주 생성자를 감추고 싶은 경우 private constructor로 사용 가능
class Person private constructor(val name: String, val age:Int, val nickname: String)
해당 코드를 더욱 코틀린스럽게 바꿀 수 있다. 코틀린은 기본 값인 default value가 존재한다.
class Person(val name: String, val age:Int, val nickname: String = name)
이렇게 두 개의 생성자가 한 줄의 문장으로 표현 가능하다.
코틀린은 파라미터에 이름을 지정해서 명시할 수 있다.
class PersonTest {
@Test
fun constructor() {
assertDoesNotThrow {
Person("최수연", nickname = "페퍼", age = 2)
}
}
}
생성자의 순서대로 파라미터를 넣지 않아도 된다. 빌더 패턴이 필요가 없다.
Null
코틀린은 기본적으로 타입만 쓰는 경우, null이 들어갈 수 없다.
@Test
fun nullable() {
Person("최수연", 2, null) // 컴파일 오류!
}
null이 들어갈 수 있게 하려면, 타입에 ?를 붙이면 된다.
class Person(val name: String, val age:Int, val nickname: String? = name)
@Test
fun nullable() {
Person("최수연", 2, null) // OK
}
Data Class
@Test
fun `data class`() {
val person1 = Person("최수연", 2, "페퍼")
val person2 = Person("최수연", 2, "페퍼")
assertThat(person1).isEqualTo(person2) // 실패. 주소값이 다르다
}
- 백틱(`)을 사용하면 언더스코어(_) 없이 띄어쓰기 가능
자바와 같이 equals & hashCode를 구현하면 테스트가 통과될 것이다.
class Person(val name: String, val age:Int, val nickname: String? = name) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Person
if (name != other.name) return false
if (age != other.age) return false
if (nickname != other.nickname) return false
return true
}
override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
result = 31 * result + (nickname?.hashCode() ?: 0)
return result
}
}
class PersonTest {
@Test
fun `data class`() {
val person1 = Person("최수연", 2, "페퍼")
val person2 = Person("최수연", 2, "페퍼")
assertThat(person1).isEqualTo(person2) // success
}
}
여기서 더 쉬운 방법이 있다. Person 클래스 앞에 data 키워드를 붙여보자.
data class Person(val name: String, val age:Int, val nickname: String? = name)
class PersonTest {
@Test
fun `data class`() {
val person1 = Person("최수연", 2, "페퍼")
val person2 = Person("최수연", 2, "페퍼")
assertThat(person1).isEqualTo(person2) // success
}
}
- 데이터 클래스는 기본적으로 equals, hashCode를 오버라이딩. copy, toString까지 모두 지원한다.
- 해당 메서드들은 생성자에 있는 프로퍼티를 기준으로 구현된다.
- 단, 특정 값만 가지고 equals, hashCode를 구현하고 싶으면 오버라이딩을 해야한다.
프로퍼티 추가하기
data class Person(val name: String, val age:Int, val nickname: String? = name) {
var phoneNumber: String // 컴파일 에러. 코틀린은 기본적으로 클래스가 인스턴스화 될 때 기본값이 필요하다.
}
기본값을 만들기 위해서 가장 쉬운 방법은 생성자를 사용하는 것이다. (자연스럽게 언어에서 객체지향적인 코드를 만들도록 한다!)
일단은 빈 문자열로 생성해보자.
data class Person(val name: String, val age:Int, val nickname: String? = name) {
var phoneNumber: String = "" // 기본값으로 빈 문자열
}
@Test
fun setter() {
val person = Person("최수연", 2, "페퍼")
assertThat(person.phoneNumber).isEmpty() // getter로 값 접근
person.phoneNumber = "01012345678" // setter로 값 지정
assertThat(person.phoneNumber).isEqualTo("01012345678") // getter로 값 접근
}
- 테스트 코드를 보면 필드 접근이 없다.
- phoneNumber가 일치하지 않더라도 다른 값이 일치하면 isEqualTo가 통과한다. phoneNumber는 주 생성자에 없는 프로퍼티이기 때문이다.
함수 생성
data class Person(val name: String, val age: Int, val nickname: String? = name) {
var phoneNumber: String = ""
fun greeting() { // 함수
println("Hello$name") // 문자열 템플릿
}
}
IntelliJ 코틀린 컨벤션 설정하기
Gradle > help > ktlintApplyToIdea
코틀린 컨벤션을 틀리면 커밋이 되지 않는다.