ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kotlin] property, function 오버라이딩(with java)
    Kotlin/Overriding 2021. 7. 24. 04:38

    자바의 다형성과  참조변수 up casting, down casting에 대해서는 설명하지 않는다.

    기본적으로 property오버라이딩은 getter,setter까지 같이생각해야하니 property의 이해가 우선이다.

    property포스팅

    https://hellose7.tistory.com/23

     

    이번 포스팅의 가장 마지막 부분에 자바 다형성 테스트를 넣어놨다

    자바에서 조상 , 자손 간에 중복된 메서드(오버라이딩된 메서드) , 중복된 멤버변수에 따라 호출되는 것이 다르기 때문에 꼭 보고 이 글을 읽자.

     

    property가 오버라이딩 되어있을 때 decompile해서 자바로 확인해보니 자바의 조상과 자손 클래스에 동일한 멤버변수가 선언되었을 때로 보면 이해하기 쉬웠다.

    결과적으로 open, overriding문법만 잘쓰고 자바의 기본동작만 이해하면 쉬울 것이다.

     

    Parent클래스 - Drink(음료수)

    Child클래스- Cola(콜라)

    GrandChild클래스- Pepsi(펩시)


    • function오버라이딩
    class Drink(){
        fun drink(){
            println("음료수 꿀꺽")
        }
    }
    class Cola():Drink(){
        fun drink(){ 
            println("콜라 꿀꺽")
        }
    }

     

    컴파일 에러 발생

    코틀린에서 클래스와 메서드는 명시하지 않으면 final로 확장불가능하다.

    Drink클래스와 Drink클래스의 메서드는 open키워드를 사용하여 확장가능하게 만들어줘야한다.

     

    open class Drink(){
        open fun drink(){
            println("음료수 꿀꺽")
        }
    }
    class Cola():Drink(){
        fun drink(){ 
            println("콜라 꿀꺽")
        }
    }

    컴파일에러

    조상의 drink메서드를 오버라이딩을 잘했지만 이메서드가 오버라이딩한 메서드인지 별도로 존재하는 메서드인지 모른다. 그렇기 때문에 코틀린에서는 오버라이딩시 override키워드를 생략할 수 없다.

     

    open class Drink{ //public클래스
        open fun drink(){ //public메서드
            println("음료수 꿀꺽")
        }
    }
    class Cola():Drink(){ //public클래스
        override fun drink(){  //public메서드
            println("콜라 꿀꺽")
        }
    }

    ok


    The open modifier has no effect when added to members of a final class – a class without an open modifier.

    final클래스여도 멤버(프로퍼티, 메서드)에 open modifer를 붙여도 아무 지장이 없다.

    class Drink{
        open var str:String="" //ok
        open fun drink(){ //ok
            println("음료수 꿀꺽")
        }
    }

    어떤 블로그에서 final클래스에서는 open멤버가 금지된다고 써놔서 내용에 추가했다.

    그런데 왜 open을 사용가능하게 해놨을까?궁금하다... 굳이 final클래스라 확장불가능한데 멤버에 open을 붙일 필요는 없는데 코틀린에서 뭔가 의도가 있는 것일까....


    override로 선언된 멤버는 그 자체로 open이다.( open이 안적혀있다고 final로 착각할 수 있다.)

    그렇기 때문에 override키워드가 명시된 곳에서는 오버라이딩을 못하게 막으려면 final을 꼭 써줘야한다.

     

    open class Drink{
        open fun drink(){
            println("음료수 꿀꺽")
        }
    }
    open class Cola:Drink(){
        override fun drink(){ //override라서 open임( final메서드 아님)
            println("콜라 꿀꺽")
        }
    }
    class Pepsi:Cola(){
        override fun drink(){
            println("펨시 콜라 꿀꺽")
        }
    }

    코틀린 function 다형성 테스트

    open class Drink{
        open fun drink(){
            println("음료수 꿀꺽")
        }
    }
    open class Cola:Drink(){
        override fun drink(){
            println("콜라 꿀꺽")
        }
    }
    class Pepsi:Cola(){
        override fun drink(){
            println("펩시 콜라 꿀꺽")
        }
    }
    
    fun main(args: Array<String>) {
    
        var drink: Drink=Drink()
        var drink2: Drink=Cola()
        var drink3: Drink=Pepsi()
    
        drink.drink() //음료수 꿀꺽
        drink2.drink() //콜라 꿀꺽
        drink3.drink() //펩시 콜라 꿀꺽
        println()
    
        var cola: Cola=Cola()
        var cola2: Cola=Pepsi()
    
        cola.drink() //콜라 꿀꺽
        cola2.drink() //펩시 콜라 꿀꺽
        println()
    
        var pepsi: Pepsi= Pepsi()
    
        pepsi.drink() //펩시 콜라 꿀꺽
    }

    메서드는 자바와 똑같은 방식의 다형성으로 동작한다.

     


    코틀린에서는 바로 위 조상클래스들의 상속한 멤버가 겹치면 overriding을 생략할 수 없다고 한다.

    https://kotlinlang.org/docs/inheritance.html#overriding-rules

    In Kotlin, implementation inheritance is regulated by the following rule: if a class inherits multiple implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation (perhaps, using one of the inherited ones).

    코틀린에서는 바로 위 조상클래스들의 상속한 멤버가 겹치면 overriding을 생략할 수 없다고 한다.

     

    kotlin reference의 예제코드이다.

    클래스에서 하나의 클래스만 상속받을 수 있고 여러 인터페이스를 구현할 경우 , 으로 나열할 수 있다.

    현재 interface의 draw메서드는 default메서드로 구현할 필요가 없는 메서드이다.

    그래서 Square클래스는 빈 상태로 놔둬도 ok하지만 지금보면

    조상의 멤버인  draw메서드가 겹쳤다.

    위의 규칙으로 인해 draw메서드가 겹쳐서 무조건 draw메서드를 오버라이딩 해야한다.

    open class Rectangle {
        open fun draw() { /* ... */ }
    }
    
    interface Polygon {
        fun draw() { /* ... */ } // interface members are 'open' by default
    }
    
    class Square() : Rectangle(), Polygon {
        // The compiler requires draw() to be overridden:
        override fun draw() {
            //overriding완료
        }
    }

    오버라이딩을 하여 컴파일 문제는 없어졌지만 오버라이딩된 메서드에서 조상의 메서드 call이 필요한 경우가 있다.

    super.draw()하게 되면 compiler가 어떤 조상의 draw메서드를 할 것인지 모르게 된다.

     

    그렇기 때문에 super<호출할조상클래스>.draw()를 사용하여 어떤 조상의 메서드를 호출할 것인지 선택해야 한다.

    이 메서드가 Rectangle의 메서드를 오버라이딩 한 것이라면 위의 것을 사용하면 되고

    Polygon의 메서드를 오버라이딩한 것이라면 아래의 것을 사용하면 된다.


    • property 오버라이딩

    property 오버라이딩을 딱 접한 순간 머리가 아팠다. 자바에서는 조상, 자손 클래스에 동일한 이름의 변수를 지정할 수 있었기 때문에 property오버라이딩이 왜 필요하지라는 생각이 들었다....

    그런데 decompile로 동작을 확인하니 코틀린의 property오버라이딩의 의도를 알게된 것 같다.

     

    자바에서는 조상과 자손 클래스에 동일한 이름의 멤버 변수를 선언할 수 있었다.

    자바에서는 참조변수의 다형성에 따라 각각 호출되는 멤버 변수가 메서드 방식과 조금 달랐다.

    ( 포스팅 맨 밑 부분 참고 ) 

     

    자바에서는 메서드에 오버라이딩이 되있으면 @Overriding을 붙이지만

    멤버변수가 조상,자손에 중복되어있는 경우에 중복되어있는지를 모를 수도있다.

    멤버변수가 조상,자손에 중복되어 있는 경우, 개발하는 과정에서 의도치 않은 결과가 일어날 수 있다.

    (자바에서는 항상 오버라이딩된 메서드가 호출되지만 멤버변수의 경우에는 참조변수 타입에 따라 조상의 멤버변수가 호출될 수도 있기 때문에)

    코틀린에서는 그것을 막기위해 중복된 이름의 프로퍼티 정의를 제한하고 property오버라이딩을 통해 조상과 똑같은 이름의 property가 있다는 것을 알아차릴 수 있게한다.

     

    기본적으로 코틀린에서는 자바와 다르게 자손 클래스에 중복된 이름의 property선언을 허용하지 않는다 

    property를 오버라이딩을 하면 그냥 자바의 조상, 자손에 동일한 이름의 멤버변수가 중복되 있는것으로 간주된다.

    또한 메서드 오버라이딩 처럼 조상의 메서드가 가려지듯이 property를 오버라이딩하면 조상의 property가 가려진다.

     

    코틀린에서 자손에 조상과 중복된 멤버(property)를 선언하려면 property를 오버라이딩 해야한다.

    function 오버라이딩의 문법처럼 하면 다음과 같이 된다.

    위의 코틀린 코드를 디컴파일 해보면 여러 java파일이 만들어지는데 그중 설명할 것만 뻈다.

    backing field로 a 필드가 공통으로 생성되고 getter,setter도 똑같은 이름으로 만들어진다.

     

     

    Drink.java

    public class Drink {
       @NotNull
       private String a = "drink";
    
       @NotNull
       public String getA() {
          return this.a;
       }
    
       public void setA(@NotNull String var1) {
          Intrinsics.checkNotNullParameter(var1, "<set-?>");
          this.a = var1;
       }
    }

    Cola.java

    public final class Cola extends Drink {
       @NotNull
       private String a = "cola";
    
       @NotNull
       public String getA() {
          return this.a;
       }
    
       public void setA(@NotNull String var1) {
          Intrinsics.checkNotNullParameter(var1, "<set-?>");
          this.a = var1;
       }
    }

    they must have a compatible type. Each declared property can be overridden by a property with an initializer or by a property with a get method:

     

    오버라이딩한 property는 조상의 것과 호환되는 타입이여야한다. 

    open class Drink {
        open var a: String = "drink"
    }
    
    class Cola : Drink() {
        override var a: Int = 3 //에러. 호환 안됨
    }

    val property를 오버라이딩하여 var property로 선언할 수 있다.

    (var property는 setter가 존재하기 때문에 반대로는 불가능하다)

     

    주생성자의 매개변수에서 프로퍼티를 정의할 수 있었다. 자손 클래스 주생성자에서 override로 property오버라이딩이 가능하다.

    interface Shape {
        val vertexCount: Int //참고- 인터페이스의 멤버는 default로 모두 open이다.
    }
    
    class Rectangle(override val vertexCount: Int = 4) : Shape // 변경 불가능
    
    class Polygon : Shape {
        override var vertexCount: Int = 0  // 변경 가능
    }

    property 다형성 테스트( 자바의 멤버변수 다형성의 동작과 다르다)

     

    자바에서는 자손, 조상에 멤버변수가 중복 정의된 경우 참조변수 타입의 것 멤버변수가 호출된다.

    (drink2의 타입 Drink의 멤버변수인 음료수1이 출력된다)

    그러나 코틀린에서는 property로의 접근은 getter메서드가 하므로(메서드다!)

    자바에서의 메서드에 해당하는 다형성이 property에도 똑같이 적용된다.

     

    자바의 다형성

    멤버변수는 1이라는 동작을 따르고 , 메서드는 2라는 동작을 따랐다면

     

    코틀린의 다형성

    코틀린의 프로퍼티의 접근은 getter,setter메서드이기 때문에 자바의2라는 동작을 따른다.

    메서드는 자바의 2동작을 따른다. 

    프로퍼티, 메서드 모두 자바의 2동작을 따르게 된다.

     

    어렵게 말했지만 단순히 말하면 그냥 코틀린의 프로퍼티도 메서드 오버라이딩식대로 호출된다고 보면된다.


    참고: 자바의 멤버호출 테스트 

     

    자바를 초반에 배울때 헷갈렸던 기억이 있다.

    편의성을 위해 encapsulation을 지키지 않았다.

     

    멤버변수는 참조변수 타입의 것이 호출되고

    멤버메서드는 참조변수에 담긴 실제 인스턴스의 것이 호출된다. 라고 외워뒀었다.

    class Parent {
    	public int a = 1; // 조상, 자손 둘다 있는 멤버변수
    	public int p = 1000;
    
    	public void method() {
    		System.out.println("parent메서드");
    	}
    
    	public void parentMethod() {
    		System.out.println("부모에만 있는 메서드");
    	}
    }
    
    class Child extends Parent {
    	public int a = 2; // 조상 ,자손 둘다 있는 멤버변수
    	public int c = 2000;
    
    	public void method() {
    		System.out.println("child메서드");
    	}
    
    	public void childMethod() {
    		System.out.println("자식에만 있는 메서드");
    	}
    }
    
    public class Test {
    	public static void main(String[] args) {
    		// 멤버 변수는 참조변수타입의 것을 따른다.
    		// 멤버 메서드는 참조변수에 들어있는 실제 인스턴스의 것을 따른다.
    		// 좁은 범위의 참조변수 타입일 경우 해당 멤버, 메서드만 사용가능하다.
    		// 좁은 범위라도 오버라이딩된 경우에는 오버라이딩된 것이 호출된다.
    		
    		Parent parent = new Parent();
    		//멤버변수 테스트
    		System.out.println(parent.a); //1 멤버변수는 참조변수parent의 것 Parent를 따름
    		System.out.println(parent.p); //1000
    		System.out.println(parent.c); //에러. parent참조변수로는 Parent멤버만 사용가능
    		//멤버메서드 테스트
    		parent.method(); // parent메서드
    		parent.parentMethod(); // 부모에만 있는 메서드
    		parent.childMethod(); //에러. Parent참조변수로는 Parent멤버만 사용가능
    
    		Parent parent2 = new Child();
    		//멤버변수 테스트
    		System.out.println(parent2.a); //1 멤버변수는 참조변수parent2의 것 Parent를 따름
    		System.out.println(parent2.p); // 1000
    		System.out.println(parent2.c); //에러. parent참조변수로는 Parent멤버만 사용가능
    		//멤버메서드 테스트
    		parent2.method(); // child메서드 
    		// 자손클래스에 메서드가 오버라이딩 되어있기 때문에 child의 메서드가 호출된다.
    		parent2.parentMethod(); // 부모에만 있는 메서드
    		parent2.childMethod(); //에러. parent참조변수로는 Parent멤버만 사용가능
    
    		Child child = new Child();
    		System.out.println(child.a); // 2
    		// 멤버변수는 참조변수child타입 Child의 값을 따른다.
    		System.out.println(child.p); // 1000
    		// 자손도 조상의 멤버를 포함한다.
    		System.out.println(child.c); //2000
    		child.method(); // child메서드
    		child.parentMethod(); // 부모에만 있는 메서드
    		child.childMethod(); // 자식에만 있는 메서드
    	}
    }

     

     

    댓글

Designed by Tistory.