2022. 3. 16. 15:44ㆍLet's Kotlin
코틀린 기본문법 - 4
인터페이스
인터페이스 정의
인터페이스를 한 마디로 표현하자면, 클래스들이 같은 기능을 수행하게끔 강제하는 것이다. 마치, 축구선수라면 뛰고 패스하는 방법은 알아야 축구선수가 될 수 있듯이, 자동차라면 가고 멈추는 기능은 있어야 하듯 그러한 최소 -- 기능을 수행했으면 하는 마음에서 만든 것이 인터페이스다. 그렇다면, 자동차 인터페이스를 구현하는 모든 클래스는 반드시 drive()와 stop()을 오버라이드 하여 구현을 해야한다. 이해가 잘 가지 않는다면 저자가 소개하는 아래 예제를 보자.
우선 Car 인터페이스를 정의한다. interface 키워드를 이용하면 인터페이스를 생성할 수 있다. 추상메서드임을 선언하는 키워드 abstract는 추상 클래스에선 필요하나 인터페이스에선 생략가능하다
interface Car{
abstract fun drive()
fun stop()
}
디폴트 메서드
자바 인터페이스에서 default 메서드가 있는 것 처럼 코틀린에도 있다. 디폴트 메서드는 인터페이스에서 기본적으로 구현하는 메서드이다. 해당 인터페이스를 구현하는 클래스들은 디폴트 메서드는 오버라이딩 하지 않아도 된다(물론 필요하면 할 수도 있다)
코틀린에서는 특별한 키워드 없이 디폴트 메서드를 구현해주면 된다. 아래와 같은 destroy() 함수가 디폴트 메서드이다.
interface Car{
abstract fun drive()
fun stop() // abstract 생략가능
fun destroy() = println("차가 파괴되었습니다.")
}
인터페이스의 구현
인터페이스를 구현할 때는 클래스명 다음 콜론 : 뒤에 인터페이스 명을 쓰면 된다
interface Car{
abstract fun drive()
fun stop() // abstract 생략가능
fun destroy() = println("차가 파괴되었습니다.")
}
class Ferrari : Car{
}
이 코드를 실행하면 오류가 나는데, 그 이유는 추상 메서드를 구현해주지 않았기 때문이다.
interface Car{
fun drive()
fun stop() // abstract 생략할 수 있어 생략
fun destroy() = println("차가 파괴되었습니다")
}
class Ferrari : Car{
override fun drive() {
println("자동차가 달립니다")
}
override fun stop() {
println("자동차 속도가 멈췄습니다")
}
}
val myFerrari = Ferrari() // Car 인터페이스를 구현한 Ferrari 클래스 객체 생성
myFerrari.drive() // 자동차가 달립니다
myFerrari.stop() // 자동차 속도가 멈췄습니
myFerrari.destroy() // 차가 파괴되었습니다
다중 인터페이스 구현
한 클래스에서 클래스는 단 한개만 상속받을 수 있다. 하지만 인터페이스는 2개 이상 구현할 수 있다.
interface Animal{
fun breate()
fun eat()
}
interface Human {
fun think()
}
class Korean : Animal, Human{
override fun breate() {
println("hoo - ha, hoo - ha")
}
override fun eat(){
println("eat kimchi with kimchijjigae")
}
override fun think() {
println("think about the son's goal of last day")
}
}
val seunghwan = Korean()
seunghwan.breate() // hoo - ha, hoo - ha
seunghwan.eat() // eat kimchi with kimchijjigae
seunghwan.think() // think about the son's goal of last day
클래스 상속과 인터페이스 구현
위 다중 인터페이스 구현 예제에 추가로 Name 클래스를 상속받아보자. 코틀린은 다중상속을 지원하지 않으므로 단 한개의 클래스만 상속가능하다는 걸 알고있어야 한다.
그리고 클래스 생성자에 필요한 인수 - Name 클래스에서의 name - 는 자식 클래스 생성자에서 전달해야한다
interface Animal{
fun breate()
fun eat()
}
interface Human {
fun think()
}
open class Name(val name : String){
fun printName(){
println("제 이름은 $name 입니다")
}
}
class Korean(name : String) : Name(name), Animal, Human{
override fun breate() {
println("hoo - ha, hoo - ha")
}
override fun eat(){
println("eat kimchi with kimchijjigae")
}
override fun think() {
println("think about the son's goal of last day")
}
}
val seunghwan = Korean("승환")
seunghwan.breate() // hoo - ha, hoo - ha
seunghwan.printName() // 제 이름은 승환 입니다
Null 처리하기
다른 언어와 마찬가지로, 코틀린도 null이 있다. 자바에서는 객체를 반환하는 함수가 반환할 객체가 없을 때 null을 반환한다. 이 방식은 null 체크가 필요한데, null 체크를 하지 않을 시, NullPointerException 오류가 나오기 때문이다. 다행히 코틀린에서는 이러한 에러가 없다. 그러나 왜냐하면 모든 객체를 생성함과 동시에 초기화해야 하기 때문이다.
var myName : String // 초기화 해주지 않아 에러
var myName : String = null // non-nullable 자료형에 null을 넣어서 에러
위의 코드를 보면 알 수 있듯, Stirng 형에 null을 넣는다고 에러가 안나는 건 아니다. String 자료형은 기본적으로 null값을 받지 않는다고 설정되어 있기 때문이다. 하지만 자료형 뒤에 '?'를 붙인다면 null값이 허용된다는 것을 알려줄 수 있다. 아래 코드를 보자.
var myName : String? = null // 자료형 뒤 ? 붙임으로써 명시적으로 null 올 수 있음을 밝힌다
위 사례의 자료형 String과 마찬가지로, Int, ArrayList<String>?, CustomClass? 등 null을 지정할 수 있도록 선언할 수 있다.
이제 myName은 null이 가능한 String형이 되었다. 문자열 순서를 반대로 하는 reversed()메서드를 사용해보자.
var myName : String? = null
myName = "Joyce"
println(myName.reversed())
해당 코드를 작성해보면, 에러가 뜬다. 왜냐하면 myName이 null이 될 수 있다고 ?을 붙여주었기 때문에 코틀린 컴파일러가 개발자에게 확인하라고 알려줘서 그렇기 때문이다. 그러므로 ?을 붙이면 실제 실행하고 난 후 런타임 에러가 나기 전 미리 에러를 예방할 수 있다. 코틀린이 방어적인 코드작성을 도와주는 것이다. 위와 같은 간단한 코드에서는 딱 봐도 null이 안들어갔다는 것을 알 수 있지만, 점점 코드가 복잡해지면 null이 들어갈 가능성이 있다.
그렇다면 어떻게 하면 변수에 null이 들어갈 수 있으며, 활용할 수도 있게 할 수 있을까?
var myName : String? = null
myName = "Joyce"
println(myName?.reversed()) // ecyoJ
그것은 변수명에 ? 를 붙여주고 메서드를 실행하는 것이다. 이렇게 한다면 myName의 값이 null일 시 null을 반환한다.
세이프 콜 연산자
fun reverseName(name : String?) : String?{
return name?.reversed()
}
println(reverseName("Joyce")) // ecyoJ
println(reverseName(null)) // null
위와 같이, reverseName 함수의 매개변수가 null이 될 수 있고, 반환값으로 String 혹은 null이 올수 있다고 메소드를 지정해주면 String이 오거나 null이 오는 경우 모두 출력해 줄 수 있다. 물론 null은 빈 값이기에 reverse해주지는 않는다. 이 ? 연산자를 세이프 콜 연산자라 한다.
자, 이제 null이 올 수 있는 변수, 메서드를 선언해보았다. 하지만 이와 반대로 반드시 null이 아닌 값을 받으려한다면 어떻게 해야할까?
이때 사용하는 것이, 엘비스 연산자이다.
엘비스 연산자 ?:
엘비스 연산자는 ? 연산자를 이용해 세이프 콜을 할 시 null을 반환하지 않고, 기본값을 반환한다. 사용법은 아래와 같다.
fun reverseName(name : String?) : String{
return name?.reversed() ?: "이름을 확인해주세요"
}
println(reverseName("Josh")) // hsoJ
println(reverseName(null)) // 이름을 확인해주세요
위와 같이 메서드 선언후 리턴값에 엘비스 연산자 ?:를 쓰면 해당 내용이 null일 시에 출력된다. 저자는 엘비스 연산자의 이름이 왜 엘비스 연산자인지를 밝혀주는데, 엘비스 프레슬리의 시그니처 헤어스타일을 닮아 그렇다고 한다. 오른쪽으로 한바퀴 돌려서 보면 좀 닮은 것 같다.
R.I.P Albis,,
확정 연산자
세이프 콜 연산자와 엘비스 연산자만 가지고도 null을 다루며 코딩할 수 있지만, 확정 연산자라는 것도 있다. 확정 연산자는 말 그대로, 컴파일러에게 자료형에 null이 들어올 수 있긴 한데, null은 확실히 아닐것이라 말해주는 연산자이다.
확정 연산자는 아래와 같이 사용한다
fun reverseName(name : String?) : String{ // 반환 자료형은 null 불가능하게
return name!!.reversed() // 절대 null 이 아님을 보증하는 확정 연산자
}
println(reverseName("SeungHwan")) // nawHgnueS
println(reverseName(null)) // 값 없음
느낌표 두 개로 컴파일러에게 협박하는 것 같다. 이렇게라도 개발하면서 스트레스를 풀라는 것 같기도 하고. 어쨌든 책에서는 null 값을 넣으면 NullPointException에러가 발생한다고 하는데 해본 결과 아무런 값도 출력되지 않고 에러메세지도 뜨지 않았다. 아무튼 null이 아님을 보증하는 것이기에 null이면 바로 무시하는 것 같다.
lateinit 키워드와 lazy 키워드
코틀린에서는 기본적으로 모든 변수는 null이 아니기에 반드시 선언과 동시에 초기화를 했어야 했다. 하지만 나중에 값을 넣고 싶다면 어찌해야할까? 그때 lateinit 키워드를 사용한다. 이후 나중에 값을 할당한다.
lateinit var lunch : String // lateinit 키워드로 나중에 값 할당할 것을 밝힘
lunch = "waffle"
println(lunch) // waffle
lateinit은 몇 가지 주의사항이 있다.
1. var 변수에서만 사용한다
2. nullable 자료형과 함께 사용할 수 있다
3. 초기화 전에 변수를 사용하면 에러가 발생한다
4. 원시 자료형 - Int, Double, Float 등에는 사용이 불가하다
5. ::변수명.isInitialized() 함수로 초기화되었는지 확인할 수 있다
lazy 키워드
lazy를 이용하면 변경할 수 없는 변수인 val의 늦은 초기화를 할 수 있다. 객체가 생성될 때 초기화하는게 아니라 처음 호출될 때 lazy{} 안 코드가 실행되며 초기화되는 것이다.
val lazyKitty : String by lazy{
println("보우가 일어났습니다")
"보우"
}
println(lazyKitty)
// 보우가 일어났습니다
// 보우
println(lazyKitty)
// 보우
자, 개념은 이정도다! 람다식을 소개를 안했는데 더 공부해보면서 필요하다 싶으면 블로그에 게재하도록 하겠다.
'Let's Kotlin' 카테고리의 다른 글
Kotlin 기본문법 - 3 (0) | 2022.03.16 |
---|---|
Kotlin 기본문법 - 2 (0) | 2022.03.15 |
Kotlin 기본문법 - 1 (0) | 2022.03.15 |
kotlin(시작) (0) | 2022.03.15 |