- 코틀린 객체지향 프로그래밍
안드로이드 앱은 SDK가 제공하는 클래스를 상속받아 개발하므로 개발자는 주로 클래스를 다룹니다.
코틀린은 객체지향 프로그래밍을 제공하며 다른 언어와 차이가 있는 코틀린만의 기법이 있으므로 잘 기억해둡니다.
코틀린은 생성자를 주 생성자와 보조 생성자로 구분하는데 각각의 정의 방법과 어떻게 활용해야 하는지 알아야 합니다.
04-1 클래스와 생성자
클래스 선언
클래스는 class로 선언합니다. 클래스의 본문에 입력하는 내용이 없다면 {}를 생략할 수 있습니다.
(코틀린에서는 클래스의 생성자를 본문이 아닌 선언부에 작성할 수 있어서 본문이 없는 클래스도 의미가 있습니다.)
class User(val name: String, val age: Int)
(선언부에 생성자와 멤버변수를 함께 적고 내용을 생략한 예시)
클래스의 멤버는 생성자, 변수, 함수, 클래스로 구성됩니다. 생성자는 constructor라고 키워드로 선언하는 함수입니다.
class User{
var name = "kkang"
constructor(name: String){
this.name = name
}
fun someFun(){
println("name: $name")
}
class SomeClass{}
}
클래스는 객체를 생성해 사용하며 객체로 클래스의 멤버에 접근합니다. 그런데 코틀린에서는 객체를 생성할 때 new 키워드를 사용하지 않습니다.
객체를 생성할 때 생성자가 자동으로 호출되므로 소괄호 안에 전달한 인자는 클래스에 선언된 생성자의 매개변수와 들어맞아야 합니다.
주 생성자
코틀린 클래스의 생성자는 주 생성자와 보조 생성자로 구분됩니다. 어느 하나만 선언할 수도 있고 둘다 선언할 수도 있습니다.
주 생성자는 constructor키워드로 클래스 선언부에 선언합니다. 필수는 아니지만 한 클래스에 하나만 가능합니다.
(constructor키워드는 생략될 수 있습니다.)
주 생성자를 선언하지 않으면 컴파일러가 매개변수 없는 주 생성자를 자동으로 추가합니다.
주 생성자의 본문 - init 영역
주 생성자를 이용해 객체를 생성할 때 특정한 로직을 수행할 수 있습니다. 그런데 주 생성자의 실행 역역인 본문을 다음처럼 추가하면 오류가 발생합니다.
class User(name: String, age: Int){
// 주 생성자 본문
} { // 오류 !
// 클래스 본문
}
보통 클래스나 함수의 본문은 중괄호({})로 감싸지만 주 생성자에는 {}를 추가할 수 없습니다. 주 생성자는 클래스 선언부에 있기 때문입니다. 이럴 때 init 키워드를 이용해 주 생성자의 본문을 구현할 수 있습니다.
init 키워드로 지정한 영역: 객체를 생성할 때 자동으로 실행됩니다. 꼭 선언할 필요는 없으므로 주 생성자의 본문을 구현하고 싶을 때 사용합니다.
class User(name: String, age: Int){
init{
println("I am Init")
}
}
fun main(){
val user = User("kkang", 10)
}
// 실행결과: I am Init 출력
생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법
생성자의 매개변수는 기본적으로 생성자에서만 사용할 수 있는 지역 변수입니다.
그런데 이 변수를 init영역이나 특정 함수에서 실행하고 싶은데, 생성자를 호출할 때 init영역이 실행되므로 이곳에서 생성자의 매개변수에 접근할 수 있습니다.
그러나 이는 지역변수이므로 다른 함수에 사용할 수는 없습니다. 만약 다른 함수에서 사용한다면 다음처럼 작성해야 합니다.
class User(name: String, age: Int){
// 클래스 멤버 변수 선언
var name: String
var age: Int
init{
// 클래스 멤버 변수에 생성자 매개변숫값 대입
this.name = name
this.age = age
}
fun someFun(){
println("name: $name, count: $count")
}
}
fun main(){
var user = User("kkang", 10)
user.someFun
}
그런데 이런 방법 말고 생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법이 있습니다.
class User(val name: String, val count: Int){
fun someFun(){
println("name: $name, count: $count")
}
}
fun main(){
val user = User("kkang", 10)
user.someFun()
}
매개변수를 선언할 때 var이나 val 키워드를 추가할 수 없으나 주 생성자에서 유일하게 이를 사용해 멤버변수로 동시에 선언할 수 있습니다.
보조 생성자
보조 생성자는 클래스 본문에 constructor 키워드로 선언하는 함수입니다. 클래스 본문에 선언하므로 여러개를 추가할 수 있습니다.
보조 생성자도 생성자이므로 객체를 생성할 때 자동으로 호출됩니다. 그리고 클래스 본문에 선언되므로 생성자 본문을 중괄호로 묶어서 객체 생성과 동시에 실행할 영역을 지정할 수 있습니다.
class User{
constructor(name: String){
println("constructor(name: String) call...")
}
constructor(name: String, count: Int){
println("constructor(name: String, count: Int) call...")
}
}
fun main(){
val user1 = User("kkang") // 실행결과: constructor(name: String) call...
val user2 = User("kkang", 10) // 실행결과: constructor(name: String, count: Int) call...
}
보조 생성자에 주 생성자 연결
둘 중 하나만 선언하면 문제가 없지만, 모두 선언한다면 반드시 생성자끼리 연결해주어야 합니다.
보조 생성자는 객체를 생성할 때 호출되며, 이때 클래스 내에 주 생성자가 있다면 this()구문으로 주 생성자를 호출해야 합니다.
class User(name: String){
constructor(name: String, count: Int) :this(name){
...
}
}
fun main(){
val user = User("kkang", 10)
}
이렇게 하면 보조 생성자로 객체를 생성할 때, 주 생성자가 함께 호출됩니다.
만약 주 생성자가 있는 상태에서 보조생성자를 여러 개 선언한다면 보조 생성자에서 this()로 다른 보조 생성자를 호출할 수도 있습니다.
그런데 이때애도 보조 생성자로 객체를 생성한다면 어떤 식으로든 주 생성자가 호출되게 해야 합니다.
class User(name: String){
constructor(name: String, count: Int): this(name){
}
constructor(name: String, count: Int, email:String): this(name, count){
}
}
fun main(){
val user = User("kkang", 10, "a@a.com")
}
주 생성자와 보조 생성자 구분 이유는 객체를 여러가지 형태로 생성할 수 있도록 하기 위함입니다.
04-2 클래스를 재사용하는 상속
상속과 생성자
클래스를 선언할 때 다른 클래스를 참조해서 선언하는 것을 상속이라고 합니다. 코틀린에서는 선언부에 콜론(:)과 함께 상속받을 클래스 이름을 입력합니다.
open class Super{
}
class Sub: Super(){
}
상속 대상이 되는 클래스는 상위클래스, 상속받는 클래스를 하위 클래스라고 합니다.
코틀린의 클래스는 기본적으로 다른 클래스가 상속할 수 없습니다. class Super{}처럼 클래스를 선언하면 다른 클래스에서 Super클래스를 상속할 수 없습니다. 따라서 상속이 가능하게 open 키워드를 사용해 클래스의 상속을 허용합니다.
상위 클래스를 상속받은 하위 클래스의 생성자에서는 상위 클래스의 생성자를 호출해야 합니다.
: Supser() 구문의 괄호 안에서 매개변수가 없는 생성자를 호출합니다. 만약 매개변수가 있는 상위 클래스의 생성자를 호출할 때는 매개변수 구성에 맞게 인자를 전달해야 합니다.
오버라이딩-재정의
상위 클래스에 정의된 멤버(변수, 함수)를 하위클래서에서 자신의 멤버처럼 사용할 수 있습니다.
그런데 이를 재정의 해야할 경우 같은 이름으로 하위 클래스에 다시 선언하는것을 오버라이딩이라고 합니다.
(주로 함수를 재정의 하기 위해 사용됩니다.)
open class Super{
open var someData = 10
open fun someFun(){
println("i am super class function: $someData")
}
}
class Sub: Super(){
override var someData = 20
override fun someFun(){
println("i am sub class function: $someData")
}
}
fun main(){
val obj = Sub()
obj.someFun() // 실행결과: i am sub class function: 20
}
오버라이딩 규칙은 허용할 변수나 함수 선언 앞에 open키워드를 추가하는 것입니다.
또, 하위 클래스에서 재정의 할 때는 반드시 선언문 앞에 override키워드를 추가해야 합니다.
접근 제한자
클래스의 멤버를 외부의 어느 범위까지 이용하게 할 것인지를 결정하는 키워드입니다.
| 접근 제한자 | 최상위에서 이용 | 클래스 멤버에서 이용 |
| public | 모든 파일에서 가능 | 모든 클래스에서 가능 |
| internal | 같은 모듈 내에서 가능 | 같은 모듈 내에서 가능 |
| protected | 사용 불가 | 상속 관계의 하위 클래스에서만 가능 |
| private | 파일 내부에서만 이용 | 클래스 내부에서만 이용 |
(같은 모듈: 그래들이나 메이븐같은 빌드 도구에서 프로젝트 단위 또는 같은 세트 단위를 가리킵니다.)
04-3 코틀린 클래스의 종류
데이터 클래스
data 키워드로 선언하며 자주 사용하는 데이터를 객체로 묶어 줍니다. VO(Value-Object)클래스를 편리하게 이용할 수 있게 해줍니다.
equals()함수로 두 객체를 비교하는 경우 data키워드로 선언한 클래스는 객체의 데이터를 비교하게 됩니다.
따라서 데이터 클래스의 경우 주 생성자에 val, var 키워드로 매개변수를 선언해 클래스의 멤버 변수로 활용하는 것이 일반적입니다.
data class DataClass(val name: String, val email: String, val age: Int){
lateinit var address: String
constructor(name: String, email: String, age: Int, address: String)
: this(name, email, age){
this.address = address
}
}
fun main(){
val obj1 = DataClass("kkang", "a@a.com", 10, "seoul")
val obj2 = DataClass("kkang", "a@a.com", 10, "busan")
println("obj1.equals(obj2): ${obj.equals(obj2)}")
// 실행결과: obj1.equals(obj2): true
}
주 생성자의 멤버변수만 비교하므로 위 코드의 실행결과는 true가 출력됩니다.
객체의 데이터를 반환하는 toString()함수
객체가 가지는 값을 확인하기 위해 사용하는 toString()함수는 데이터 클래스와 일반 클래스일때의 반환값이 다릅니다.
데이터 클래스의 toString()함수만 객체가 포함하는 멤버 변수의 데이터들을 출력합니다.
오브젝트 클래스
오브젝트 클래스는 익명 클래스를 만들 목적으로 사용합니다.
익명 클래스는 말 그대로 이름이 없는 클래스로 선언과 동시에 객체를 생성해야 합니다.
object키워드를 사용합니다.
val obj = object {
var data = 10
fun some(){
println("data: $data")
}
}
fun main(){
obj.data = 20
obj.some()
// 실행결과: 둘 다 오류 !
}
object키워드로 선언했지만 타입을 명시하지 않았으므로 이 객체는 코틀린의 최상위 타입인 Any로 취급합니다.
그런데 Any타입 객체는 data, some()이라는 멤버가 없어서 오류가 발생합니다.
따라서 익명 클래스를 선언할때는 보통 타입까지 뒤에 입력해서 선언합니다.
뒤에 콜론(:)을 입력하고 그 뒤에 클래스의 상위 또는 인터페이스를 입력합니다.
open class Super{
open var data = 10
open fun some(){
println("i am super some(): $data")
}
}
val obj = object: Super(){
override var data = 20
override fun som(){
println("i am object some(): $data")
}
}
fun main(){
obj.data = 30
obj.some()
// 실행결과: i am object some: 30
}
컴패니언 클래스
컴패니언 클래스는 멤버 변수나 함수를 클래스 이름으로 접근하고자 할 때 사용합니다.
일반적으로 클래스의 멤버는 객체를 생성해서 접근해야 합니다. 그런데 컴패니언 클래스는 객체를 생성하지 않고서도 클래스 이름으로 특정 멤버를 이용할 수 있습니다.
class MyClass{
companion object{
var data = 10
fun some(){
println(data)
}
}
}
fun main(){
MyClass.data = 20 // 성공
MyClass.some() // 성공
}
클래스 내부에 companion object {}형태로 선언하면 이 클래스를 감싸는 클래스 이름으로 멤버에 접근할 수 있습니다.
'Java > Kotlin' 카테고리의 다른 글
| Kotlin Programming Study - 07 (0) | 2025.12.16 |
|---|---|
| Kotlin Programming Study - 06 (0) | 2025.12.15 |
| Kotlin Programming Study - 03 (0) | 2025.12.09 |
| Kotlin Programming Study - 02 (0) | 2025.12.09 |
| Kotlin Programming Study - 01 (0) | 2025.12.05 |