상속(Inheritance)


클래스라는 것을 배우면서 Object 클래스에 대해서 잠깐 소개한 적이 있었습니다. 그 이야기를 하면서 상속에 대해 언급한 적이 있었습니다. 자바에서는 이 상속의 사전적인 뜻을 그대로 반영합니다.


inheritance

1. 상속받은 재산, 유산; 상속   2. (과거·선대로부터 물려받는) 유산, 유전(되는 것)


내가 500억을 상속받았으면 이 포스팅을 하지 않았다.


사전을 보니 물려받는 것으로 간단하게 정의할 수 있겠군요.

우리는 이제부토 상속이란 클래스 관점으로 보면 됩니다. 클래스에는 변수와 메소드가 있죠. 클래스 입장에서는 이것이 재산이라고 생각하면 됩니다. 그래서 자식이 되는 클래스는 이런 재산을 코드의 추가없이 그대로 사용하거나, 변경해서 사용할 수 있습니다.




여기서 왜 우리는 상속을 해야하는 지 알 수 있습니다. 바로 코드의 재사용성을 더욱 확대하는 것이지요. 그렇다면 그냥 복사, 붙여넣기를 하면 되는 것이 아닌가? 라고 생각할 수도 있지만, 코드의 변경 역시 신경써야합니다. 그리고 관리도 복잡하게 되구요.


가장 중요한 것은 상속을 통해서 다형성을 만족시킬 수 있다는 점입니다. 이 부분에 대해서는 이 다음에 알아보도록 하지요.


extends 

상속을 하는 방법 아래와 같습니다.


class 자식클래스 extends 부모클래스


어떤 클래스를 정의함과 동시에 extends 예약어로 다른 클래스를 포함하게 된다면 extends 이후에 나오는 클래스를 상속하게 되는 것입니다.


어떤 클래스는 자동적으로 Object클래스를 상속했다고 했는데, 그럼 이런 형태일까요?


class 자식클래스 extends 부모클래스 extends Object


아닙니다. 자바에서는 클래스의 다중상속(여러 부모클래스를 두는 것)을 허용하지 않습니다. 이런 경우에는 이미 부모클래스가 Object클래스를 이미 상속받고 있기 때문에 자식클래스는 부모클래스를 상속받음으로써 Object클래스의 메소드를 사용할 수 있는 것입니다.


이렇게 되는 것이죠.


class 부모클래스 extends Object { ... }

class 자식클래스 extends 부모클래스 { ... }





코드를 통해 알아보자.

이쯤되면 코드를 볼 타이밍이겠군요.



class Parent{
	int age=45;
	String name="Parent";
	public Parent(){
		System.out.println("Parent Default Constructor");
	}
	public Parent(int _age,String _name){
		age=_age;
		name=_name;
		System.out.println("Parent Constructor");
	}
	public void showInfo(){
		System.out.println("Name:"+name+", age:"+age);
	}
}

class Child extends Parent{
	public Child(int _age, String _name){
		System.out.println("Child Constructor");
	}
}
public class Main {

	public static void main(String[] args){
		Child a=new Child(25,"REAKWON");
		a.showInfo();
	}
}


우선 Child 클래스는 Parent 클래스를 상속받고 있군요. 


코드에 대해서 간단하게 요약하면 Parent 클래스는 아래와 같은 특징이 있군요.


Default 생성자를 갖고 있다.

매개변수를 갖는 생성자도 있다.

showInfo라는 메소드가 있다.


Child클래스는 그저 매개변수를 받는 생성자가 다에요.


main메소드에서는 Child 객체를 초기화값을 넣어 생성자를 호출합니다. 어떤 결과가 나올까요? 결과를 봅시다.


Parent Default Constructor

Child Constructor

Name:Parent, age:45


어? 부모클래스의 생성자를 우선 호출하는 군요. Default 생성자를 호출하네요.

아아, 우리가 아무 클래스를 상속하게 된다면 부모클래스의 디폴트 생성자를 우선 호출하는 군요.


그리고 우리는 Child클래스에서 showInfo라는 메소드를 만들지 않았음에도 불구하고 main함수에서는 사용하고 있습니다. Parent 클래스를 상속 했기 때문이죠.


근데 좀 부족해요. 뭔가 2%만족이 되지 않는군요.


Child a=new Child(25,"REAKWON");


우리는 생성자를 통해서 age와 name을 바꾸고 싶다는 거죠. 어떻게 해야될까요?




super, this

우리는 위의 코드를 조금 변경해서 age와 name 변수의 데이터를 바꿀 수 있습니다. 바로 부모클래스의 데이터를 직접 변경하는 것이죠.


class Child extends Parent{
	public Child(int _age, String _name){
                age=_age;
                name=_name;
		System.out.println("Child Constructor");
	}
}

우리는 이미 Parent클래스를 상속받은 상태입니다. 그렇기 때문에 Parent클래스의 변수를 접근할 수도 있습니다(물론 부모클래스가 허용한다면).


그런데 우리는 이미 정의된 부모 클래스의 생성자를 다시 활용해보고 싶습니다. 


자식클래스에서 부모클래스를 super클래스라고 부릅니다. 그렇기 때문에 부모클래스의 변수나 메소드, 생성자에 접근할땐 super라는 키워드를 사용합니다.


class Child extends Parent{
	public Child(int _age, String _name){
                super(_age,_name);
		System.out.println("Child Constructor");
	}
}

이런식으로 부모클래스의 생성자(super( args))를 호출함으로써 구현해낼 수도 있습니다.

super. 을 눌러 어떤 변수와 메소드들이 있는지 확인해보세요. 부모클래스가 허락한다면 super클래스의 변수와 메소드를 직접 사용할 수도 있습니다.


이제 코드를 조금 더 변형시켜보도록 해서 실험해 봅시다.


class Child extends Parent{
	public Child(int age, String name){
                age=age;
                name=name;
		System.out.println("Child Constructor");
	}
}


이제 매개변수와 클래스 멤버 변수의 이름이 같습니다. 이런 경우에는 어떤 일이 벌어질까요?


결론은 클래스의 멤버 변수는 변하지 않습니다. 메소드가 호출할때는 매개변수나 그 안의 지역변수가 항상 우선순위를 갖기 때문입니다.


클래스에서 자기 자신을 가리킬 때는 this라는 키워드를 사용합니다. 그렇기 때문에 이런 코딩을 해야합니다.

class Child extends Parent{
	public Child(int age, String name){
                this.age=age;
                this.name=name;
		System.out.println("Child Constructor");
	}
}


this.age는 자기 자신의 변수 age를 명시적으로 나타냅니다. 어? 우리는 Child에는 age와 name이라는 변수가 없는데요?


아까 말했다시피 상속을 받았기 때문에 슈퍼클래스의 변수도 이제 Child 클래스의 것입니다. 그래서 super.age이건 this.age이건 age접근하는 것은 결론적으로 같습니다.




생성자는 어떨까요?

생성자 역시 다르지 않습니다.

this(args)를 통해서 자기 자신의 생성자를 호출 할 수 있습니다.


class Child extends Parent{
        public Child(){
                System.out.println("Child Default Constructor");
        }
	public Child(int age, String name){
                this();
                this.age=age;
                this.name=name;
		System.out.println("Child Constructor");
	}
}


this()를 보세요. 자신의 Default 생성자를 호출하고 있습니다.


다만, 한가지 주의해야할 점은 자기 자신의 생성자나 슈퍼클래스의 생성자를 다른 생성자 메소드에서 호출해야 될 때에는 호출할 생성자 메소드의 최상위에 코딩을 해야합니다. 


overriding

우리는 슈퍼클래스의 메소드를 우리 나름대로 다시 정의해보고 싶습니다. 그것도 역시 가능합니다.

우리는 부모가 정의한 메소드를 우리 입맛에 맞게 바꾸는 것을 Overriding이라고 합니다.




코드와 함께 보도록 합시다.


class Parent{
	int age=45;
	String name="Parent";
	public Parent(){
		System.out.println("Parent Default Constructor");
	}
	public Parent(int _age,String _name){
		age=_age;
		name=_name;
		System.out.println("Parent Constructor");
	}
	public void showInfo(){
		System.out.println("Name:"+name+", age:"+age);
	}
}

class Child extends Parent{
	public Child(){}
	public Child(int _age, String _name){
		super(_age,_name);
		System.out.println("Child Constructor");
	}
	@Override
	public void showInfo(){
		System.out.println("Child showInfo");
		super.showInfo();
	}
}
public class Main {

	public static void main(String[] args){
		Child a=new Child(25,"REAKWON");
		a.showInfo();
	}
}

Child클래스의 showInfo()를 보세요. 부모 클래스의 메소드를 오버라이딩한다는 것을 알리기 위해 @Override annotation으로 명시하고 있습니다. @Overriding이라는 annotation을 써주지 않아도 오류는 없지만, 우리는 컴파일러에게 부모클래스의 메소드를 오버라이딩한다는 것을 알려주어 원치않는 코딩(오타)을 막을 수 있습니다.


또한 Child클래스의 showInfo()는 super클래스의 showInfo() 역시 호출하며 코드를 줄이고 있습니다.


그 결과입니다.


Parent Constructor

Child Constructor

Child showInfo

Name:REAKWON, age:25



여기까지 상속에 대해 주저리 주저리 떠들어봤습니다.

아 힘들었다...

반응형
블로그 이미지

REAKWON

와나진짜

,