[Do it! 깡샘의 안드로이드 앱 프로그래밍 with 코틀린] 코틀린 객체지향 프로그래밍

클래스와 생성자, 상속, 접근 제한자, 데이터 클래스, 오브젝트 클래스, 그리고 컴패니언 클래스를 정리했단. 클래스는 class 키워드로 선언하며, 생성자는 주 생성자와 보조 생성자로 구분된다. 상속은 open 키워드로 허용되며, override 키워드로 오버라이딩이 가능하다. 접근 제한자는 public, internal, protected, private가 있으며, 데이터 클래스는 data 키워드로 선언된다. 오브젝트 클래스는 익명 클래스를 만들기 위해 사용되고, 컴패니언 클래스는 객체를 생성하지 않고도 클래스 이름으로 멤버에 접근할 수 있게 한다.
DriedPollack's avatar
Jun 20, 2024
[Do it! 깡샘의 안드로이드 앱 프로그래밍 with 코틀린] 코틀린 객체지향 프로그래밍

🌼클래스와 생성자

💡클래스 선언

  • 코틀린에서 클래스는 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() }
    • 이런 방법 말고 생성자의 매개변수를 클래스의 멤버 변수로 선언하는 방법이 있다.
      • 주 생성자의 매개변수는 생성자 안에서만 사용할 수 있는 지역 변수지만 매개변수를 varval 키워드로 선언하면 클래스의 멤버 변수가 된다.
      • 주 생성자만 유일하게 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

More articles

See more posts

👨🏻‍💻DriedPollack's Blog