Kotlin 기본문법 - 3
코틀린 기본문법 - 3
클래스
클래스란 객체를 정의하는 설계도라고 저자는 설명한다. 붕어빵 기계에서 붕어빵 틀은 클래스, 그곳에서 찍어내는 각각의 개별적인 붕어빵들 - 즉 슈크림, 팥, 잡채 붕어빵들 - 은 하나의 틀로써 만들어진 여러개의 객체이다.
자바에서는 아래와같이 클래스 선언에 여러 문자를 작성해야 했지만 다행히 코틀린은 자바보단 훨 깔끔하다.
-- [참고] 자바의 클래스 선언 방법 --
public class Java {
public static void main(String[] args) {
// 소스 작성
}
}
코틀린 클래스 선언 및 객체 생성
class 클래스 명 --> 단지 이렇게 하는 것 만으로도 클래스는 생성가능하지만, 아무런 기능을 하지 못한다
class 클래스 명(프로퍼티 : 형식) -- 프로퍼티는 클래스의 속성이다.
예)
class Car(val color : String) // 클래스 생성
val car = Car("red") // 객체 생성 후 클래스 color 프로퍼티 값에 "red" 지정
println("My car color is ${car.color}") // 결과값 : My car color is red
클래스 생성자
생성자란 객체를 생성할 때 항상 실행되는 특수한 함수로 객체 초기화 목적으로 사용한다. 코틀린의 생성자로는 주 생성자와 보조 생성자가 있다.
주생성자
클래스 이름 옆에 괄호로 둘러쌓인 코드를 주 생성자라고 한다. 다음 코드의 색칠한 부분을 보자.
class Person constructor(name : String) {} 여기서, 키워드인 constructor을 생략할 수 있다 -- 생성자라고 밝혀주는 키워드일 뿐
class Person(name : String) {}
여기서, var이나 val을 이용하면 프로퍼티 선언과 초기화를 한 번에 할 수도 있다.
예)
class Person(val name : String) {}
-> 예제와 같이 name을 프로퍼티로 선언하고, 생성시 입력된 값을 name 프로퍼티에 할당해준다
아까 클래스 생성 예제와 같이,
예)
class Car(val color : String) // 클래스 생성
val car = Car("red") // 객체 생성 후 클래스 color 프로퍼티 값에 "red" 지정
class 클래스명(val(var) 프로퍼티)
val 클래스 명 = 클래스명(프로퍼티에 들어갈 값 할당)
보조생성자
보조생성자는 주생성자와 다르게 클래스 내부에서 constructor 키워드를 이용하여 만들며, 객체 생성 시 실행할 코드를 작성해 넣을 수 있다. 보조생성자는 constructor 키워드를 생략하지 못한다.
예 - 1)
class Person {
contructor(age : Int){
println("I'm $age years old")
}
}
예 - 2)
class Person{
constructor(age : Int){
println("I'm $age years old") // 결과값 : I'm 5 years old
}
}
val person = Person(5)
그리고 주생성자가 존재할 때는 반드시 this키워드를 통해 주생성자를 호출해야한다
class Person(name : String){
constructor(name : String, age : Int) : this(name) { // this(name)으로 주생성자를 호출하지 않는다면 오류발생
println("I'm $age years old")
}
}
초기화블록
객체 생성 시 필요한 작업을 하는 것을 초기화블록이라 한다. init{}안의 코드들은 객체 생성 시 가장 먼저 실행되고 주 생성자의 매개변수를 사용할 수 있다. 주로 주 생성자와 함께 쓰인다.
class Person(name : String){
val name : String
init {
if (name.isEmpty()){
throw IllegalAccessException("이름이 없어요") // 문자열이 비어있는 경우 에러발생
}
this.name = name // 문자열이 안비어있으면 이름 저장
}
}
val person = Person("Seunghwan")
println(person.name)
// 결과값 : Seunghwan
아래 코드에서, Person의 매개변수를 비운다면? -- val preson = Person()
val person = Person("Seunghwan")
println(person.name)
결과값 : No value passed for parameter 'name' -- name 변수에 값이 없음을 알리는 에러발생
다르게, 문자열을 비워놓는다면? -- val person = Person("")
결과값은 안나오지만, 하단 메뉴 중 Output에서 아래와 같은 에러를 볼 수 있다
- [에러내용] -
Exception in thread "main" java.lang.IllegalAccessException: 이름이 없어요
at org.jetbrains.kotlin.idea.scratch.generated.ScratchFileRunnerGenerated$ScratchFileRunnerGenerated$Person.<init>(tmp.kt:12)
at org.jetbrains.kotlin.idea.scratch.generated.ScratchFileRunnerGenerated$ScratchFileRunnerGenerated.<init>(tmp.kt:17)
at org.jetbrains.kotlin.idea.scratch.generated.ScratchFileRunnerGenerated.main(tmp.kt:23)
- [에러내용] -
-- 즉, 초기화블록을 통해 객체 생성시 필요한 작업인 문자열의 공백유무, 에러발생 알림, 변수 저장을 할 수 있다.
클래스의 상속
코틀린에서 클래스를 상속받으려면 부모 클래스에 open키워드를 추가해야한다. 메서드(클래스 하에서 지정된 함수)도 자식클래스에서 오버라이드(재정의, 가져오기)해서 사용하려면 부모 클래스의 메서드에 open 키워드를 추가해야 한다.
아래 예시에서, Flower 클래스를 상속받는 Rose 클래스를 보자, 콜론 : 을 통해서 상속을 나타낸다.
open class Flower{ // 상속받으려하는 Rose를 위해 open 키워드 해주기
open fun waterFlower(){ // 상속받으려하는 Rose를 위해 open 키워드 해주기
println("water flower")
}
}
class Rose : Flower(){ // Flower 클래스를 상속받기 위해 클래스명 : 상속하려는 클래스명 으로 상속표현
override fun waterFlower() { // Flower 클래스의 메서드를 오버라이드 해오는 과정
super.waterFlower() // Flower 클래스의 메서드 먼저 실
println("Rose is happy now")
}
}
val rose = Rose()
rose.waterFlower()
// Rose 클래스 객체 만듦 -> Rose 클래스가 Flower 클래스 상속시행
// -> 상속 이후 Flower 클래스의 메서드 실행 -> 이후 Rose 클래스 시행
결과값 :
water flower
Rose is happy now
부모 클래스 생성자를 실행시키려면 자식 클래스에서 반드시 부모 클래스의 생성자를 명시적으로 호출해야한다
open class Flower(val name : String){}
class Rose(name : String, color : String) : Flower(name)
접근 제한자
접근 제한자란 누구에게 클래스 메서드와 변수를 공개할 지 정하는 것이다
자바와 유사한데, 코틀린 클래스의 기본 속성은 public으로 설정되어 있다. 이외에도 private, protected, internal 등의 제한자가 있다.
public : 코틀린 기본접근 제한자로 어디서나 접근 가능
internal : 같은 모듈 내에서 접근 가능 -- 안드로이드 개발 시 한 프로젝트 안에 있으면 같은 모듈이라 생각하면 된다, 만약 한 프로젝트에 여러 모듈을 만든다면 모듈간 접근 제한된다
protected : 자식 클래스에서는 접근 가능
private : 해당 클래스 내부에서만 접근 가능
선언은 변수나 메서드 앞에 써주면 된다
예)
private val b = 2
컴패니언 객체
컴패니언(Companion : 친구, 동료)의 뜻을 알면 더 이해가 쉬울 듯 하다. 자바에서 public static void,,, 하던 것에서 static은 정적, 고정된의 의미를 가지고 static키워드를 사용하면 정의된 변수나 메서드들은 객체를 만들지 않고도 접근을 가능하게 한다. 코틀린에서는 컴패니언 키워드가 그 역할을 한다.
class Dinner{
companion object{ // object 키워드
val MENU = "pasta" // 정적 변수 생성
fun eatDinner(){ // 정적 메서드 생성
println("$MENU is yummy!")
}
}
}
println(Dinner.Companion.MENU) // 결과 : pasta
println(Dinner.MENU) // 결과 : pasta
Dinner.eatDinner() // 결과 : pasta is yummy!
위와 같이 따로 Dinner클래스의 객체를 생성하지 않고도 companion으로 정의하였기에 MENU변수와 eatDinner() 함수를 사용하는데 성공했다.
object 키워드를 사용해서 만들어진 객체는 여려 번 호출되더라도 딱 하나의 객체만 생성되어 재사용된다.
추상 클래스
추상 클래스란, 그 자체로는 객체를 만들 수 없는 클래스를 말한다. 일반적으로 추상 메서드가 포함된 클래스이다. 추상 메서드란 아직 구현되지 않고 추상적으로만 존재하는 메서드이며 추상 클래스와 추상 메서드 앞에는 abstract(추상적인) 키워드를 붙인다. 추상 클래스는 보통 상속받는 자식 클래스에 특정 메서드 구현을 강제하고 싶을 때 쓰는데, 추상 클래스 자체로는 직접 객체를 만들 수 없고 자식 클래스에서 추상 메서드를 구현한 다음 자식 클래스의 객체를 생성하는 것이다.
저자는 예컨대 게임은 실체(객체)가 없지만, 오버워치는 실체(객체)가 있다는 것으로 추상 클래스를 게임에 비유하고있다.
이러한 예시와 정의만 들으면 잘 안와닿을 수 있다. 예제를 보며 알아보자.
abstract class Game{
fun startGame(){
println("게임을 시작했습니다.")
}
// 추상 메서드
abstract fun printName()
}
class Overwatch : Game(){ // Game 클래스 상속
override fun printName() { // 추상 메서드 구현
println("오버워치 입니다.")
}
}
val overwatch = Overwatch()
overwatch.startGame() // Game 클래스 메서드 --> 결과값 : 게임을 시작했습니다
overwatch.printName() // Overwatch 클래스에서 구현한 메서드 --> 결과값 : 오버워치 입니다.
근데 여기서, 추상 메서드를 왜 쓰는지 모르겠다는 의문이 날 수 있지만 해당 클래스를 상속받는 입장에서 꼭 어떠한 메서드를 구현했으면 바라는 어머니의 마음에서 만들었다는 점 알아두면 되겠다.
abstract class Game{
fun startGame(){
println("게임을 시작했습니다.")
}
}
class Overwatch : Game(){ // Game 클래스 상속
fun printName() { // 추상 메서드 구현
println("오버워치 입니다.")
}
}
val overwatch = Overwatch()
overwatch.startGame() // Game 클래스 메서드 --> 결과값 : 게임을 시작했습니다
overwatch.printName() // Overwatch 클래스에서 구현한 메서드 --> 결과값 : 오버워치 입니다.
실제로 추상메서드는 없애도 실행은 잘 되지만, 저렇게 구현 안할 수 있으니까 어머니의 마음으로 알려준다 생각하자.
데이터 클래스
코틀린의 데이터클래스는 데이터 전달을 하려는 목적으로 존재한다. 코틀린은 데이터 전달용 객체를 간편히 생성하도록 data class라는 키워드를 제공한다.
주 생성자에는 반드시 val이나 var을 사용한 프로퍼티 정의가 적어도 하나 이상 필요하며 val, var가 아닌 매개변수는 사용할 수 없다.
기본문법은 아래와 같다
data class 데이터클래스이름(val 첫번째 값 이름 : 자료형, var 두번째 값 이름 : 자료형 ,,,)
예)
data class Memo(val title : String, val content : String, var isDone : Boolean)
var memo1 = Memo("마트가기", "계란, 우유, 빵..은너무 비싸", false)
println(memo1) // 결과값 : Memo(title=마트가기, content=계란, 우유, 빵..은너무 비싸, isDone=false)
참고로, 데이터 클래스는 각각의 프로퍼티에 대한 toString(), copy()와 같은 메서드를 자동으로 만들어 준다.
그 역할은 아래와 같다
toString() : 객체에 포함된 데이터를 출력하여 볼 수 있다. 생성자에 포함된 프로퍼티만 출력된다
copy() : 객체의 속성들을 복사하여 반환하는 메서드이며 인수로 받은 프로퍼티만 해당 값으로 바뀌어 복사해준다
data class Memo(val title : String, val content : String, var isDone : Boolean)
var memo1 = Memo("마트가기", "계란, 우유, 빵..은너무 비싸", false)
var memo2 = memo1.copy(content = "칫솔, 과자")
println(memo1.toString())
// 결과값 : Memo(title=마트가기, content=계란, 우유, 빵..은너무 비싸, isDone=false)
println(memo2.toString())
// 결과값 : Memo(title=마트가기, content=칫솔, 과자, isDone=false)