[Do it! 깡샘의 안드로이드 앱 프로그래밍 with 코틀린] 코틀린 객체지향 프로그래밍
클래스와 생성자, 상속, 접근 제한자, 데이터 클래스, 오브젝트 클래스, 그리고 컴패니언 클래스를 정리했단. 클래스는 class 키워드로 선언하며, 생성자는 주 생성자와 보조 생성자로 구분된다. 상속은 open 키워드로 허용되며, override 키워드로 오버라이딩이 가능하다. 접근 제한자는 public, internal, protected, private가 있으며, 데이터 클래스는 data 키워드로 선언된다. 오브젝트 클래스는 익명 클래스를 만들기 위해 사용되고, 컴패니언 클래스는 객체를 생성하지 않고도 클래스 이름으로 멤버에 접근할 수 있게 한다.
Jun 20, 2024
🌼클래스와 생성자
💡클래스 선언
- 코틀린에서 클래스는 class 키워드로 선언한다.
class User {}
- 클래스의 멤버는 생성자, 변수, 함수, 클래스로 구성된다.
- 생성자는
constructor
키워드로 선언한다. - 클래스 안에 다른 클래스를 선언할 수 있다.
class User { var name = "kkang" constructor(name: String){ this.name = name } class SomeClass{} }
- 하지만 코틀린에서는 객체를 생성할 때 new 키워드를 사용하지 않고, 클래스 이름과 같은 함수로 객체를 생성한다.
- 객체를 생성시에는 클래스에 선언된 생성자의 매개변수와 들어맞는 인자를 전달해야 한다.
val user = User("kim")
💡주 생성자
- 코틀린 클래스는 생성자를 주 생성자와 보조 생성자로 구분한다.
- 주 생성자는 constructor 키워드로 클래스 선언부에 선언한다.
- 주 생성자는 한 클래스에 하나만 가능하다.
class User constructor(){}
class User() {}
class User {}
class User(name: String, count: Int) {}
- 주 생성자를 이용해 객체를 생성할 때
init
키워드로 특정한 로직을 수행할 수 있다. - 보조 생성자로 객체를 선언할 때도 실행 되지만, 보조 생성자는 클래스 안에 선언하므로 {}를 이용해 본문을 지정할 수 있다.
class User(name: String, count: Int) { init{ println("i am init...") } } fun main(){ val user = User("kkang", 10) }
- 생성자의 매개변수는 기본적으로 생성자에서만 사용할 수 있는 지역변수다.
- 만약 생성자의 매개변수를 클래스의 멤버 변수처럼 다른 함수에서 사용해야 한다면 다음처럼 클래스의 멤버 변수를 선언하고 주 생성자의 본문인 init 영역에서 매개변숫값을 클래스의 멤벼 변수에 대입할 수 있다.
// 생성자의 매개변수를 다른 함수에서 사용하는 예 class User(name: String, count: Int) { // 클래스 멤버 변수 선언 var name: String var count: Int init{ // 클래스 멤버 변수에 생성자 매개변숫값 대입 this.name = name this.count = count } fun someFun(){ println("name: $name, count: $count") } } fun main(){ val user = User("kkang", 10) user.someFun() }
- 주 생성자의 매개변수는 생성자 안에서만 사용할 수 있는 지역 변수지만 매개변수를
var
나val
키워드로 선언하면 클래스의 멤버 변수가 된다. - 주 생성자만 유일하게 var나 val 키워드로 매개변수를 선언할 수 있다.
// 생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법 class User(val name: String, val count: Int) { fun someFun(){ println("name: $name, count: $count") } } fun main(){ val user = User("kkang", 10) user.someFun() }
- 보조 생성자는 클래스의 본문에 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") val user2 = User("kkang", 10) }
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") }
🌼클래스를 재사용하는 상속
💡상속과 생성자
- 클래스를 선언할 때 다른 클래스를 참조해서 선언하는 것을 상속이라고 한다.
- 어떤 클래스를 상속받으려면 선언부에
콜론(:)
과 함께 상속받을 클래스 이름을 입력한다.
open class Super { // 다른 클래스에서 상속할 수 있게 선언하려면 open 키워드를 사용한다. } class Sub: Super() { }
open class Super(name: String) { } class Sub(name: String): Super(name) { }
open class Super(name: String) { } class Sub: Super { constructor(name: String): super(name){ } }
💡오버라이딩 - 재정의
- 상속이 주는 최고의 이점은 상위 클래스에 정의된 멤버를 하위 클래스에서 자신의 멤버처럼 사용할 수 있다는 것이다.
- 상위 클래스에 선언된 변수나 함수를 같은 이름으로 하위 클래스에서 다시 선언하는 것을 오버라이딩이라고 한다.
- 코틀린에서 오버라이딩 큐칙은 먼저 상위 클래스에서 오버라이딩을 허용할 변수나 함수 선언 앞에
open
키워드를 추가하는 것이다. - 그리고
open
키워드로 선언한 멤버를 하위 클래스에서 재정의할때는 반드시 선언문 앞에override
키워드를 추가한다.
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() }
💡접근 제한자
- 접근 제한자란 클래스의 멤버를 외부의 어느 범위까지 이용하게 할 것인지를 결정하는 키워드다.
- 코틀린에서 제공하는 접근 제한자는 다음과 같다.
접근 제한자 | 최상위에서 이용 | 클래스 멤버에서 이용 | 비고 |
public | 모든 파일에서 가능 | 모든 클래스에서 가능 | ㅤ |
internal | 같은 모듈 내에서 가능 | 같은 모듈 내에서 가능 | 모듈: 그래들이나 메이븐같은 빌드 도구에서 프로젝트 단위 또는 같은 세트 단위 |
protected | 사용 불가 | 상속 관계의 하위 클래스에서만 가능 | ㅤ |
private | 파일 내부에서만 이용 | 클래스 내부에서만 이용 | ㅤ |
🌼코틀린의 클래스 종류
💡데이터 클래스
- 데이터 클래스는
data
키워드로 선언하며 자주 사용하는 데이터를 객체로 묶어준다.
- 데이터 클래스는
VO 클래스
를 편리하게 이용할 수 있다. - 데이터 클래스를 사용하면 객체 자체가 아닌 객체의 데이터를 비교하므로 데이터를 비교할 때 편리하다.
class NonDataClass(val name: String, val email: String, val age: Int) data class DataClass(val name: String, val email: String, val age: Int) fun main(){ val non1 = NonDataClass("kkang", "a@a.com", 10) val non2 = NonDataClass("kkang", "a@a.com", 10) val data1 = DataClass("kkang", "a@a.com", 10) val data2 = DataClass("kkang", "a@a.com", 10) println("non data class equals : ${non1.equals(non2)}") // false println("data class equals : ${data1.equals(data2)}") // true }
equals()
함수는 주 생성자에 선언한 멤버 변수의 데이터만 비교 대상으로 삼는다.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) : ${obj1.equals(obj2)}") // true }
toString()
함수는 반환값이 다르다.- 일반 클래스로 생성한 객체의
toString()
함수가 출력하는 값은 의미 있는 데이터가 아니다. 하지만 데이터 클래스의toString()
함수는 객체가 포함하는 멤버 변수의 데이터를 출력한다.
class NonDataClass(val name: String, val email: String, val age: Int) data class DataClass(val name: String, val email: String, val age: Int) val non = NonDataClass("kkang", "a@a.com", 10) val data = DataClass("kkang", "a@a.com", 10) println("non data class toString : ${non.toString()}") // com.example.test4.ch2.Test2Kt$main$NonDataClass@61bbe9ba println("data class toString : ${non.toString()}") // DataClass(name=kkang, email=a@a.com, age=10)
💡오브젝트 클래스
- 오브젝트 클래스는 익명 클래스를 만들 목적으로 사용한다.
- 익명 클래스는 이름이 없으므로 클래스 선언과 동시에 객체를 만들어야 한다.
- 오브젝트 클래스는 선언과 동시에 객체를 생성한다는 의미에서
object
라는 키워드를 사용한다.
val obj = object { var data = 10 fun some(){ println("data: $data") } } fun main(){ obj.data = 20 // 오류 obj.some() // 오류 }
- 위의 코드에서
object
키워드로 클래스를 선언했지만 타입을 명시하지 않았으므로Any
로 취급된다. - 그런데
Any
타입의 객체에는 data, some()이라는 멤버가 없어서 오류가 발생한다. - 따라서 다음과 같이
object: A { }
형태로 선언하면 A 인터페이스를 구현한 익명 클래스를 선언한 것이고,object
객체를 A 타입으로 이용할 수 있게 된다.
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 some() { println("i am object some() : $data") } } fun main(){ obj.data = 30 // 성공 obj.some() // 성공 }
💡컴패니언 클래스
- 컴패니언 클래스는 멤버 변수나 함수를 클래스 이름으로 접근하고자 할 때 사용한다.
- 컴패니언 클래스는 객체를 생성하지 않고도 클래스 이름으로 특정 멤버를 이용할 수 있다.
class MyClass{ var data = 10 fun some(){ println(data) } } fun main(){ val obj = MyClass() obj.data = 20 // 성공 obj.some() // 성공 MyClass.data = 20 // 오류 MyClass.some() // 오류 }
- 위 소스에선 객체를 생성한 후 객체명으로 멤버에 접근하는 데는 문제가 없다.
- 하지만 클래스 이름으로 접근하면 오류가 발생한다.
- 이처럼 클래스 이름으로 멤버에 접근할 수 있게 하려면
companion
이라는 키워드로 선언해야 한다.
class MyClass{ companion object{ var data = 10 fun some(){ println(data) } } } fun main(){ MyClass.data = 20 // 성공 MyClass.some() // 성공 }
🏁결론
클래스와 생성자, 상속, 접근 제한자, 데이터 클래스, 오브젝트 클래스, 그리고 컴패니언 클래스를 정리했단. 클래스는
class
키워드로 선언하며, 생성자는 주 생성자와 보조 생성자로 구분된다. 상속은 open
키워드로 허용되며, override
키워드로 오버라이딩이 가능하다. 접근 제한자는 public
, internal
, protected
, private
가 있으며, 데이터 클래스는 data
키워드로 선언된다. 오브젝트 클래스는 익명 클래스를 만들기 위해 사용되고, 컴패니언 클래스는 객체를 생성하지 않고도 클래스 이름으로 멤버에 접근할 수 있게 한다.Share article