GDSC HUFS 4기/Kotlin Team #4

[4팀] 코틀린 객체 지향 프로그래밍 기초

Yoon-Min 2022. 10. 10. 18:00

이 글은  Android12 및 코틀린 완전 정복 참고하여 작성하였습니다.

작성자 : 윤승민

개발환경은 Mac, Android Studio입니다.

 

클래스

클래스는 객체를 생성하기 위한 설계도라고 할 수 있다.

클래스와 관련된 개념을 정리해보면 다음과 같다.

 

💡 클래스 - 객체를 생성하기 위한 설계도

     객체 - 클래스의 내용이 구현돼 생성된 것

     생성자 - 클래스 객체를 생성할 때 클래스 내의 멤버 변수 초기화를 돕는 역할

     멤버 변수 - 클래스 내에 만들어진 변수

     멤버 메소드 - 클래스 내에 만들어진 함수

클래스 객체의 생성

1. 클래스를 정의했다면 이를 사용하기 위해서 클래스 객체를 생성해야 한다.

class Person() {
}

2. 정의한 내용에 따라서 클래스를 생성할 때 클래스에서 요구하는 정보들이 있을 수 있다.

  • 생성자를 통해서 이러한 정보들을 받을 수 있다.
  • 사람(person)과 관련된 클래스를 만든다면 생각해볼 수 있는 관련 정보는 이름, 나이, 성별 등이 있다.
class Person(name: String, age: Int, sex: String) {
	// 클래스 내용
}

3. 만약 필요한 정보들이 있다면 객체 생성 때 이 값들을 넘겨줘야 한다. 없다면 넘겨주는 값 없이 바로 생성한다.

fun main() {
    val yoonMin = Person("YoonSeungMin", 24, "남") // 필요한 정보가 있을 경우
    val nonePerson = Person() // 필요한 정보가 없을 경우
}

class Person(name: String, age: Int, sex: String) {
    // yoonMin 생성을 위한 클래스
}

class Person() {
    // nonePerson 생성을 위한 클래스
}

4. 생성자를 통해서 넘겨준 값들을 통해 멤버 변수를 초기화해주고 사용할 준비를 마친다.

class Person(name: String, age: Int, sex: String) {
    // yoonMin 생성을 위한 클래스
    val name : String = name
    val age : Int = age
    val sex : String = sex

    fun getName() : String {
            return name
    }

    fun getAge(): Int {
            return age
    }

    fun getSex(): String {
            return sex
    }
}

생성자

생성자는 다음과 같이 만든다.

constructor키워드는 어노테이션이나 접근 제어를 걸어주지 않는다면 생략이 가능하다.

class Person constructor(name: String, age: Int, sex: String){
    val name : String = name
    val age : Int = age
    val sex : String = sex
}
class Person(name: String, age: Int, sex: String){
    val name : String = name
    val age : Int = age
    val sex : String = sex
}

위와 같은 경우에는 클래스 내부에 따로 멤버 변수를 만들어서 초기화 해준다.

괄호 안에서 초기화까지 해주는 것도 가능하다.

class Person(val name: String, val age: Int, val sex: String){
}

생성자를 통해서 값을 받았으나 받은 값들이 적절하지 않을수도 있다.

그래서 값을 받아서 초기화하기 전에 값에 이상이 없는지 검사를 해줄 필요가 있을수도 있다.

이 때 init을 이용한다. init은 클래스가 생성될 때 자동으로 실행된다.

class Person(name: String){
    val name : String

    init{
        if(name.length > 0) {
            this.name = name // 클래스 내 멤버 변수 name의 값을 초기화한다.
        }
        else{
            //부적절한 값임을 알린다.
        }
    }
}

데이터 클래스

  • 데이터들을 객체로 묶어서 관리할 수 있다.
    • 배열에 데이터를 담는 경우 데이터 클래스를 이용할 수 있음
    • 서버 통신을 통해 받은 데이터를 데이터 클래스에 저장할 수 있음 → json 형식을 인식함
  • 일반 클래스와 달리 생성하면 메소드가 자동으로 생성되는데 이 메소드들이 굉장히 유용하다.
    • hashCode()
    • copy()
    • equals()
    • toString()
    • comonentsN()
  • 생성시 무조건 1개 이상의 파라미터가 있어야 한다.
  • 괄호 내에서 val 혹은 var 키워드를 이용해 만들어야 한다.
  • abstract, open, sealed, inner 불가능
data class Person(
    val name: String = "YoonSeungMin",
    val age: Int = 24,
    val sex: String = "남"
)

상속

💡 기존의 클래스를 재사용하여 클래스를 새롭게 만든다.

  • 코드의 재사용을 높여서 코드 중복을 방지
  • 유지보수가 용이하고 생산성이 높아짐

코틀린에서의 상속은 불가능하다. 클래스를 생성하면 타입이 final이기 때문이다.

상속을 가능하게 하려면 class 앞에 open을 붙여줘야 한다.

open class Person(name: String, age: Int, sex: String) {
		
}

이제 부모 클래스를 만들었으니 자식 클래스를 만들어야 한다. 자식 클래스가 부모 클래스를 상속 받을 때 다음과 같이 작성한다.

class Student(name: String, age: Int, sex: String, id: String) : Person(name, age, sex) {
		
}

부모 클래스의 함수를 사용하려면 마찬가지로 부모 클래스 내의 멤버 메소드에 open을 붙여주면 된다.

open class Person(name: String, age: Int, sex: String) {
        val name : String = name
        val age : Int = age
        val sex : String = sex

        open fun printInfo() {
                println("${this.name} ${this.age} ${this.sex}")
        }
}

자식 클래스는 printInfo를 재정의하여 사용할 수 있다.

class Student(name: String, age: Int, sex: String) : Person(name, age, sex) {
        override fun printInfo() {
                super.printInfo() // 부모 클래스의 printInfo()를 사용할 수 있음
                println("정보 출력이 완료됐습니다.")
        }
}

인터페이스

 💡 클래스가 자세하게 완성된 설계도라 한다면 인터페이스는 두루뭉실한 설계도라 할 수 있다. → 추상 클래스와 유사

  • 클래스의 기능을 확장하는데 도움을 줌
  • 나중에 구현하고 싶은 특정 함수나 프로퍼티를 대비해서 미리 밑그림만 그려놓는 느낌
    • 함수의 몸통이 정의되지 않은 추상 메소드를 가짐
  • 상속을 통해서 구체적인 정의를 하여 설계를 완성함
    • 인터페이스를 객체로 생성하는 것은 불가능
    • 특정 클래스 상속을 통해서 객체를 생성
  • 다중상속이 가능
interface Animal {
	fun name()

	fun color()

	fun age()
}
class WhiteDog : Animal {
	// 

	override fun name() {
			println("백구")
	}

	override fun color() {
			println("white")
	}

	override fun age() {
			println("3")
	}
}

코틀린은 인터페이스 사용시 꼭 override를 붙여야 한다. → 추상 메소드는 반드시 재정의가 필요

추상 클래스

인터페이스와 유사하지만 차이가 있다.

  • 추상 클래스는 프로퍼티의 초기화가 가능하지만 인터페이스는 불가능
  • 추상클래스는 클래스나 메소드 앞에 abstract를 붙여야 함
  • 인터페이스는 다중 상속이 가능하지만 추상 클래스는 다중 상속이 불가능하다.
abstract class Animal {
        val year = "2022" // 프로퍼티 초기화가 가능

        abstract fun name()

        abstract fun color()

        abstract fun age()
}
class WhiteDog : Animal {
        override fun name() {
            println("백구")
        }

        override fun color() {
            println("white")
        }

        override fun age() {
            println("3")
        }
}

형 변환

  • 코틀린은 변수를 생성할 때 타입으로 Any를 사용할 수 있다.
    • 변수를 생성할 때 Any를 타입으로 지정하면 초기화 한 값에 따라서 자동으로 값에 해당하는 타입을 지정해줌
    • 예를 들어 문자열을 값으로 설정하면 문자열에 관련된 메소드인 length를 사용할 수 있다.
  • is 연산자로 타입 확인이 가능
val name: Any = "YoonMin"
if(name is String) {
        println(name.length) // name : String 으로 자동 캐스팅
}
  • as 연산자를 이용해 형 변환이 가능
    • 형 변환이 불가능하면 에러 발생 → as? 는 형 변환이 불가능하면 null값 반환
val nameBox: String = name as String
val nameBox: String? = name as? String // as? 는 null값을 반환할 수 있기 때문에 값을 받는 변수도 nullable 설정이 필요