GDSC HUFS 4기/Kotlin Team #7

[7팀] 코틀린 객체지향 프로그래밍 기초(2)

나영수 2022. 10. 9. 13:45

이 글은  유데미 강의 Android 12 및 Kotlin 개발 완전 정복를 참고하여 작성하였습니다.

작성자 : 나영수

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

 

-상속


* 서브클래스(=자식 클래스= 파생 클래스), 슈퍼클래스(=부모 클래스=기본 클래스)로 크게 나뉜다.

* open class만이 상속이 가능하다.

fun main() {
    var a = Animal("흰둥이", 5, "개")  //a변수는 Animal 객체 변수
    var b = Dog("흰둥이", 5) //b변수는 Dog 객체 변수(Dog는 Animal을 상속받음)

    a.introduce()  //Animal class 내의 내장 함수

    b.introduce()  //Dog class 내의 내장 함수가 아니지만 Animal을 상속 받았으므로 사용 가능
    b.bark()  //Dog class 내의 내장 함수

    var c = Cat("루이", 1)  //c변수는 Cat 객체 변수
    c.introduce()
    c.meow()
}

//코틀린은 default가 상속금지(java의 경우 아무것도 선언하지 않으면 상속가능) -> open 사용해야함.

open class Animal(var name: String, var age: Int, var type: String) {
    
    fun introduce() {
       
        println("저는 ${this.type} ${this.name}이고 ${this.age}살 입니다.")
        //해당 클래스 내의 변수를 선언할때는 생성자인 this를 사용한다.(사실 여기서는 사용안해도 무방)
    }
}

//var, val 등으로 붙이면 속성으로 선언되기 때문에 안됨

class Dog(name: String, age: Int) : Animal(name, age, "개") {
   
   //interface와는 다르게 상속에서는 여러가지 기능을 확장시킬 수 있다.
    fun bark() { 
        println("멍멍")
    }
}

class Cat(name: String, age: Int) : Animal(name, age, "고양이") {
    fun meow() {
        println("야옹 야옹")
    }
}

 

- 다형성

다형성이란 비슷한 특성을 가진 객체들이 공통된 방법으로 여겨지는 것이다.
(하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미)

-> 결론적으로 봤을때 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있도록 한 것

ex ) Parent class 와 Child class가 있다고 가정해보자.

class Parent{}

class Child extends Parent{}


Parent cp = new Child(); //가능

Parent ch = new Parent();  //가능

Child ck = new Child(); //가능

위의 3가지 경우는 모두 성립가능한 객체선언이다.

(이 3가지의 경우만 보더라도 하나의 객체가 여러 타입을 가질 수 있다.) -> child 타입, parent 타입



Child cl = new Parent(); //불가능

하지만 위의 경우 자식 클래스 타입의 참조 변수로는 부모 클래스 타입의 인스턴스를 참조할 수 없다.

단순히 말하면 부모는 자식의 인스턴스를 참조할 수 있지만 자식은 부모의 인스턴스를 참조할 수 없다.
(약간 상속하고 반대되는 느낌으로 이해하면 괜찮을 것 같다. 상속의 경우 자식이 부모의 모든 것을 
쓸수 있지만 자식에서 수정한 것을 부모가 알 수 없는 것과 반대로 다형성은 부모는 자식을 알지만 
자식은 부모를 모르기 때문이다.)

 

- 인터페이스

인터페이스는 다른 인터페이스에 상속받을 수 있다.

그리고 인터페이스는 클래스들의 기능을 확장해준다는 점에서 의의를 갖는다.

그리고 일단 함수와 프로퍼티를 바로 구현하지 않고 선언만 하고 싶을 때 사용하기 좋다.
인터페이스 내부에서는 명세만 할 뿐이지 바디는 갖지 않는다. 즉 객체를 만들지 못한다.

 

- 추상 클래스(abstract class)

 

추상메소드를 가진 클래스는 무조건 추상클래스가 되어야 한다.

 

 --추상클래스와 인터페이스 공통점? --


추상클래스와 인터페이스는 상속받는 클래스 혹은 구현하는 인터페이스 안에 있는 추상 메소드를 구현하도록 강제한다. 인스턴스화가 불가능하다.

--추상클래스와 인터페이스 차이점?--

가장 큰 차이점은 추상클래스와 인터페이스가 갖는 목적성이다. 그리고 다중상속의 유무가 가장 큰 차이점이다.


상속 : 상속은 슈퍼클래스의 기능을 이용하거나 확장하기 위해서 사용되므로 다중 상속의 모호성 때문에 하나만 상속받을 수 있다.(만약 상속하는 두 곳에서 동일한 메소드가 있다면 그 메소드를 확장 시킬때 어떤 메소드를 확장시키는지 모호해진다.)


인터페이스: 하지만 인터페이스를 구현한 객체들은 동일한 동작을 약속하기 위해 존재하므로 다중 상속되어도 상관없다.(그냥 상속받는 class들의 함수를 다 쓰기만 하면 됨)


--둘중 하나만 있게되면 생기는 번거러움--

 

만약 모든 클래스가 인터페이스만 통해서 기본틀을 구성하면 공통으로 필요한 기능들도 모든 클래스에서 오버라이딩해서 다시 재정의 해야 하는 번거로움이 생긴다. 이렇게 공통된 기능이 필요한곳은 추상클래스를 이용해서 자식 클래스에서 사용할 수 있도록 하면 된다.(추상클래스에서는 필요한 메서드를 모두 정의해놓기 때문에 가져다 쓰기만 하면 되지만 인터페이스의 경우는 모두 다시 선언해야한다.)

 

그럼 모든 클래스를 추상클래스만 이용한다면? 이라는 생각을 할 수 있지만 차이점에서 설명했듯이 클래스는 다중상속이 제한되기 때문에 각각 다른 추상클래스를 상속해야하는데 공통된 기능이 필요하다면 인터페이스를 이용하는 것이 효율적이다.

 

https://myjamong.tistory.com/150

 

[JAVA] 추상클래스 VS 인터페이스 왜 사용할까? 차이점, 예제로 확인 :: 마이자몽

추상클래스 인터페이스 왜... 사용할까? 우리는 추상클래스와 인터페이스에 대해서 알고 있냐고 누가 물어본다면 알고 있다고 대답을 하고있습니다. 그런데 이론적인 내용 말고 정작 "왜 사용하

myjamong.tistory.com

 

-형 변환

 

//<unsafe 캐스팅>

val obj1: Any = "I have a dream"

val str1: String = obj1 as String
println(str1.length) 
//obj1이 String이므로 캐스팅이 성공하고 길이가 출력된다.(obj1이 Any -> String)
//단 obj1이 null일 경우 ?가 없어서 null exception에서 자유롭지 않아서 unsafe 캐스팅이다.


//<safe 캐스팅>

val obj3: Any = 1337
val str3: String? = obj3 as? String //이렇게 되면 obj3가 null일 경우에도 정상적으로 작동 가능하다.
println(str3)