Java의 정석 - 객체의 상속

원태연·2022년 5월 30일
0

Java의 정석

목록 보기
7/19
post-thumbnail

상속

기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것.

class Parent{
  
}
class Child extends Parent{
          //Parent클래스를 상속 받겠다는 뜻  
 }

상속을 하면, 부모 클래스의 멤버변수나 메소드를 사용할 수 있지만, 생성자나 초기화 블럭은 상속되지 않는다.

class Parent{
    int a = 10;
    void printMessage(){
        System.out.println("Hello");
    }
}

class Child extends Parent{
  
}
public class playGround {
    public static void play(){
        Child child1 = new Child();
        child1.printMessage();     //Hello
    }
}

상속은 여러 세대를 통해 이루어질 수 있으며, 상속받은 자손 클래스는 모든 조상의 멤버를 사용할 수 있다.

단일 상속이 원칙인 자바에서 여러 클래스를 상속받은 것처럼 사용할 수 있다.

class Parent{
    int a = 10;
    void printMessage(){
        System.out.println("Hello");
    }
}
class Parent2{
  int b = 20;
}
class Child extends Parent{
  Parent2 one = new Parent2()
}
public class playGround {
    public static void play(){
        Child child1 = new Child(); //Hello
        child1.printMessage();      //20
    }
}

명확한 상속과 포함의 관계를 통해 코드의 재사용성을 높일 수 있다.

~은 ~이다 와 어울리면 상속을,

~은 ~을 가지고 있다와 어울리면 포함을 선택하는 것이 권장된다.

과일, 바나나, 껍질 이라는 클래스들이 있다면,

과일 -> 바나나(껍질) 이 적합할 것이다.

자바가 다중상속을 포기한 이유는 두 개 이상의 조상 클래스가 같은 이름의 멤버변수나 메소드를 가진다면, 상속의 이유중 하나인 재사용성을 기대하기 어렵기 때문이다.

Object()

사실 상속을 하지 않은 클래스들도 이미 상속을 하고 있다.

class Person{
  int age;
  Person(int a){
    this.age = a;
  }
}

위 코드가 컴파일 되면, 선언부가 class Person extends Obejct 가 된다. 자동으로 Object()라는 클래스를 상속받는다.

그래서, 클래스는 기본적으로 Object()가 가지는 메소드들을 따로 정의하지 않아도 사용할 수 있다.

ex) toString(), equals(), ...

오버라이딩

class Parent{
    int a = 10;
    void printMessage(){
        System.out.println("Here is Parent");
    }
}
class Child extends Parent{
  	void printMessage(){
        System.out.println("Here is Child");
    }
}
public class playGround {
    public static void play(){
        Child child1 = new Child(); //Hello
        child1.printMessage();      // ?? --1
    }
}

--1의 결과는 Here is Child 일 것이다.

자손이 조상와 같은 이름의 메소드를 가지면, 조상의 메소드를 덮어쓰고(override) 자손의 메소드를 실행한다.

앞서 배운 오버로딩과 비슷하게 생각할 수 있다. 같은 이름의 메소드에 대해 어떤 처리가 이루어지기 때문이다.하지만 명확히 다르며 그 차이는,

  • 오버로딩은 기존의 없는 메서드를 정의하고,

  • 오버로딩은 이름만 같을 뿐, 매개변수에 대한 정보가 다르다.

  • 오버라이딩은 상속받은 메소드를 변경하고,

  • 오버라이딩은 상속받은 메소드와 이름 뿐만아니라, 매개변수, 반환타입모두 동일하여야 한다.

완전히 다르게 생각하자. 오버라이딩의 장점은 자손의 필요에 따라 알맞게 메소드를 변경할 수 있다는 것이다. 오버라이딩은 메소드이름을 통해 파악되므로, 오타가 있어도 원하는 오버라이딩 없이 정상작동 할 수 있다.

그래서 @Override라는 어노테이션을 사용하는 것이 권장된다.

super

참조변수 super와 메서드 super() 에 대해 살펴보자.

참조변수 superthis와 유사하게 객채를 가리키지만 그 대상의 차이가있다.

package playground;

class Parent{
    int a = 10;
    void printParentMessage(){
        System.out.println("Here is Parent"); 
    }
}
class Child extends Parent{
    int a = 20;
    void printNumber(){
        System.out.println(super.a);  // --1
        System.out.println(this.a);   // --2
    }

    @Override
    void printParentMessage(){
        System.out.println("Here is Child");
        super.printParentMessage();  
    }

}

public class playGround {
    public static void play() {
        Child c = new Child();
        c.printNumber();
        c.printParentMessage();
      	//Here is Child   --3
				//Here is Parent  --4
    }
}

--1--2를 보면, 같이 a를 지칭하지만 결과가 다르다. 그 이유가 superthis 라는 참조변수가 서로 가리키는 부분이 다르기 때문이다. super는 조상 클래스인 Parenta 를, this는 자기 자신의a 를 지칭 한 것이다.

--3--4의 결과를 보면, 조상의 메소드가 아닌 Child클래스의 printParentMessage()가 실행된 것을 보면 오버라이딩이 의도에 맞게 수행되었다는 것을 알 수 있다. super.printParentMessage(); 참조변수super 가 있기에, 조상의 메소드가 실행된 것을 알 수 있다.

super()

package playground;

class Parent{
    int a;
    Parent(int aInit){
        this.a = aInit;  // --1
    }
}
class Child extends Parent{
    int a;
    int b;
    Child(int aInit, int bInit){
        super(aInit + bInit);  // --2
        this.a = aInit;
        this.b = bInit;
    }
    void printMessage(){
        System.out.println(super.a);  //3
        System.out.println(this.a);   //2
        System.out.println(this.b);   //1
    }
}
public class playGround {
    public static void play() {
        Child c = new Child(1,2);
        c.printMessage();
    }
}

super()this() 처럼 생성자의 역할을 하지만, 가리키는 대상이 다르다.

--1에서 조상클래스인 Parent의 생성자가 존재하기 때문에 컴파일러는 자동생성자를 생성하지 않는다.

그래서 Parent의 멤버 변수에 대한 초기화가 필요한데, 이를 상속받은 Child가 그 동작을 super()을 통해 수행한다.

--2가 존재하지 않으면 컴파일 에러가 발생할 것이다. 과정을 살펴보면,

컴파일러는 생성자 첫줄에 생성자(thi(),super())가 존재하지 않으면 자동으로 super()를 삽입한다.

이때, Person의 생성자는 int aInit이라는 매개변수를 받는데, super() 과 일치 하지 않는다.

자동 생성될super()가 생성되지 않게 -> super(a) 로 수정하여 매개변수를 보내거나,

Parent(int aInit) - > null 로 하여 자동생성자를 통해 일치시켜 해결할 수 있다.

profile
앞으로 넘어지기

0개의 댓글