수업 시작에 앞서...
자바에서 클래스 고급 이후의 대부분 문법이 상속에서 출발한다고 할 수 있을 정도로 상속은 자바의 뼈대가 되는 매우 중요한 개념이다.
상속을 공부할 때 『코드의 재사용, 재활용』의 관점으로 보는 것은 자바를 학습하는 관점에서 매우 도움이 된다. 하지만 실무에서는 코드의 재활용을 위해 상속을 이용하는 일은 거의 없다.
예를 들어 지금 자동차를 만든다고 가정하자. 친구가 전에 만들어 둔 엔진이 있다며 가져다 쓰라고 했다 하자.(만들어져 있는 것을 재사용)
내가 만들어 놓은 자동차에 친구의 엔진을 쓰려고하니 규격이 맞지 않는다. 이렇게 연결해보고 저렇게 연결해보고.. 가져온 엔진만 연결하려 했을 뿐인데, 내가 만든 자동차의 일부를 수정하게 된다. 심하면 갈아엎을 수도 있다. 이럴거면 엔진을 가져다 쓰기 보다 직접 만드는 게 나은 상황이 올 수도 있다.
그러면 자바의 『상속』은 어떻게 이용할까?
실무에서는 상속을 가이드라인 제시용으로 굉장히 많이 사용한다.
타인이 만들어 둔 코드를 그대로 쓰는 것이 아니라, 코드의 특징에 맞게 활용하는 방향으로 사용할 것이다. (물론 이것을 위해서는 활용되는 코드가 추상적일 확률이 높다.)
새로운 포유류의 동물을 만든다고 가정하면, 포유류이기 위한 특징(가이드라인)을 뼈대삼아 구체적인 생김새는 내 입맛대로 구현하면 된다. 앞선 예제와 같이 자동차의 엔진을 받아온다고 하면 상속을 통해 엔진의 공통 설계도(가이드라인, 뼈대)를 가져와서 내 자동차에 맞는 엔진으로 구현하여 사용한다.
정리하자면 상속은 코드의 재사용, 재활용(Ctrl+C, Ctrl+V)의 관점으로만 이해하면 안 된다. (자바에서 제공하는 System.out.println() 정도의 신뢰를 갖고 있다면 단순 재사용해도 된다..)
상속은 클래스들의 공통된 규칙을 정의해주고, 그 규칙들을 잘 적용하도록 도와주는 것이 실질적인 의미라고 할 수 있다.
상속(Inheritance)
교재 310p, 317p 참고
새로 설계(생성)하고자 하는 클래스가 이미 설계되어 있는 다른 클래스의 기능과 중복되는 경우 이미 설계된 클래스의 일부분이나 전체 구조를 공유할 수 있도록 하는 기능을 의미한다.
즉, 상속은 객체를 좀 더 쉽게 만들 수 있는 고수준의 재사용성(reusability)을 확보하고 객체 간의 관계를 구성함으로써 객체 지향의 또 다른 큰 특징인 『다형성』의 문법적 토대가 된다.
상속의 장점
상속은 기존 프로그램의 클래스 내용을 공유함으로써 중복된 코드들을 재작성할 필요 없이 반복적이고 세부적인 작업을 처리하지 않기 때문에 ①프로그램을 작성하는 시간을 절약할 수 있고 ②유지보수를 편리하게 할 수 있으며, ③프로그램의 길이도 짧아지게 된다. 또한, 이미 작성된 프로그램들은 앞서 테스트되었기 때문에 ④오류를 줄일 수 있어 현재 작성 중인 프로그램에만 전념할 수 있다.
상속 관계인 클래스의 명칭, 용어
물려주는 클래스 = 상속하는 클래스 = 상위 클래스 = 부모(조상) 클래스 = 슈퍼 클래스(Super Class)
물려받는 클래스 = 상속받는 클래스 = 하위 클래스 = 자식(자손) 클래스 = 서브 클래스(Sub Class)
상속에 관한 주의사항 (★중요)
- 자바는 다중상속을 지원하지 않기 때문에 두 개 이상의 클래스로부터 상속받을 수 없다. 즉, 자바는 ★단일상속만 허용된다. (참고로 C++은 다중상속 가능)
- 자바의 상속은 자식 클래스가 extends 키워드로 상속받을 부모 클래스를 결정한다. 그래서 단일상속이며 부모 클래스(상위 클래스)는 자신을 상속받는 하위의 존재를 알지 못한다. 자식만 부모의 존재를 안다.
- 따라서 상속 받은 자식 클래스가 로딩되어 메모리에 올라갈 때 부모 클래스도 같이 메모리에 올라간다. 부모가 가진 메모리 영역과 자식이 가진 메모리 영역은 따로따로 할당된다. == 자식 클래스의 인스턴스가 생성되어 메모리를 할당받을 때는 부모 클래스도 메모리를 할당받는다. → 업캐스팅(upcasting) - 자식 클래스의 의무①
하지만 부모 클래스는 어떤 클래스가 자신을 상속받고 있는지 조차 모르기 때문에 위 내용의 역은 성립하지 않는다.(부모 클래스로 인스턴스를 생성할 때는 부모 클래스만 메모리를 할당받는다.)
자바 16(2020.08.25) 에서 업캐스팅에 대해 다룰것이며 클래스 고급 파트에서 중요한 부분이다. 자식 클래스가 메모리에 올라갈때 부모 클래스도 함께 메모리에 저장된다는 것을 반드시 기억하자. (역은 성립Ⅹ)
- 부모 클래스로부터 상속받는 대상(변수, 메소드) 중에 부모의 생성자는 제외한다(부모의 생성자는 상속되지 않는다). 다만 자식 클래스의 생성자 내부에 부모 클래스의 생성자를 호출하는 super() 구문이 (눈에 보이지 않게) 자동 삽입된다. 자식 클래스의 생성자는 부모 클래스의 생성자를 호출해야 할 의무가 있다. - 자식 클래스의 의무②
- 상위 클래스에서 선언된 멤버 변수의 이름과 하위 클래스에서 선언된 멤버 변수의 이름이 같으면 상위 클래스의 멤버 변수는 일반적인 사용 과정에서 무시된다. 이 때, 상위 클래스의 멤버 변수를 사용하기 위해서는 『super』키워드를 이용해야 한다. 『super』키워드를 활용하면 상위 클래스의 멤버 변수를 사용할 수 있다. 하단에서 super에 대해 자세히 다룬다.
- 상속 관계에서 자식 클래스는 부모의 private 변수, 메소드는 상속받을 수 없다.
부모 클래스와 같은 패키지 내에 위치한다면 접근제어범위가 default, protected, public인 멤버를,
다른 패키지에 위치한다면 protected, public인 멤버를 상속 받는다.
※ Tip!
상위(부모) 클래스를 설계할 때 protected 접근제어자를 적극적으로 활용하므로써 다른 클래스가 상속할 것을 염두하고 설계할 수 있다.
상속 예제 코드
/*=================================
■■■ 클래스 고급 ■■■
- 상속(Inheritance)
=================================*/
// 부모 클래스
class SuperTest116
{
// 『protected』 : 상속받는 클래스, 동일한 패키지, 클래스 내부에서 접근 가능
protected double area;
// 『default』 : 동일한 패키지, 클래스 내부에서 접근 가능
// 부모클래스의 생성자
SuperTest116() // ★생성자는 상속 대상에서 제외된다.
{
System.out.println("Super Class...");
}
// 『default』 : 동일한 패키지, 클래스 내부에서 접근 가능
void write(String title)
{
System.out.println(title + " - " + area);
}
}
// 자식 클래스(동일한 패지키에 존재하는 자식 클래스)
public class Test116 extends SuperTest116 //, SuperTest117, SuperTest118 이런거 안된다. 다중상속 안된다.
{
// 눈에 보이지는 않지만 부모 클래스로부터 상속받은 내용이다. 물론 부모클래스 생성자는 상속 대상 제외
/*
protected double area;
// 디폴트 접근제어자이고 같은 패키지에 있기 때문에 상속이 된 것. 다른 패키지였다면 상속 되지 않는다.
void write(String title)
{
System.out.println(title + " - " + area);
}
*/
//double area = 10.1234;
// 자식 클래스의 생성자
Test116()
{
// 자식 클래스의 생성자 내부에는 부모 클래스의 생성자 호출 자동 삽입
//SuperTest116(); //→ super();
//super 가 포함된 클래스가 상속받고 있는 상위 클래스 이름으로 변환해서 대입
//Test116(); //→ this();
//this 가 포함된 클래스 이름으로 동일하게 변환해서 대입
System.out.println("Sub Class...");
//super();
//--==>> 에러 발생(컴파일 에러)
}
public void circle()
{
int r = 10;
area = r * r * 3.141592;
write("원");
// write가 출력하는 area는 부모가 가지고 있는 area 이다.
// 현재 부모의 area 값은 0.0 이다.
}
public void rect()
{
int w = 20, h = 5;
super.area = w * h;
// 변수 또는 메소드의 이름이 부모 클래스와 같을 때, 자신의 변수는 그냥 부르면 되지만
// 부모의 변수를 부를 때는 super라는 키워드를 붙여줘야 한다.
write("사각형");
}
public static void main(String[] args)
{
// 자식 클래스 기반 인스턴스 생성
Test116 ob = new Test116();
//--==>> Super Class...
// Sub Class...
// 출력되는 문장을 통해
//자식 인스턴스를 생성하면 부모 생성자가 호출되고 자식 생성자가 호출됨을 알 수 있다.
ob.circle();
//--==>> 원 - 314.1592
ob.rect();
//--==>> 사각형 - 100.0
}
}
super
키워드 super 는 현재 클래스가 상속받은 상위 클래스의 객체를 가리킨다.
super 는 상위 클래스의 생성자를 호출하거나 상위 클래스의 멤버 변수 또는 메소드를 호출할 때 사용할 수 있다.
생성자와 상속 간의 관계
- 하위 클래스는 상위 클래스의 멤버를 상속받지만, 생성자는 상속 대상에서 제외된다.
- 하위 클래스의 생성자가 호출될 때 자동으로 상위 클래스의 생성자가 호출된다. 이 때, 상위 클래스의 생성자는 인수가 없는 생성자(default 생성자 형태)가 호출된다.
상위 클래스 및 하위 클래스를 설계하는 과정에서 상위 클래스의 생성자를 정의하지(작성하지) 않거나 인수가 없는 생성자만을 정의한(작성한) 경우, 명시적으로 하위 클래스에서 상위 클래스의 생성자를 호출하지 않아도 아무런 문제가 발생하지 않지만, 상위 클래스에 인자가 있는 생성자만 존재하는 경우에는 주의해야 한다.
예를 들어 다음 코드와 같이 생성자를 호출하면 에러가 발생한다.
// 상위 클래스
class A_class
{
A_clasS(int n) // 인자, 매개변수가 있는 생성자 정의 (이때 디폴트 생성자는 자동생성 안된다.)
{ }
}
// 하위 클래스
class B_class extends A_class
{
B_class()
{
super();
// 에러 발생. 정의되지 않은 생성자를 호출하고 있다.
}
}
하위 클래스인 B_class 의 생성자에서 명시적으로 A_class 의 생성자를 호출하지 않으면 자동으로 인자 없는 생성자를 호출한다. 하지만, A_class 에는 인자가 있는 생성자만 존재하고 인자가 없는 생성자는 존재하지 않기 때문에 에러 발생한다. 따라서 B_class 생성자의 선두에 다음처럼 명시적으로 상위 클래스의 생성자 호출 구문을 작성해야 한다.
// 상위 클래스
class A_class
{
A_class(int n)
{ }
}
// 하위 클래스
class B_class extends A_class
{
B_class()
{
super(10); // 명시적으로 상위 클래스의 생성자 호출 (인자가 있는 생성자 호출)
...
...
}
}
상위 생성자 super()의 호출 위치
하위 클래스의 생성자 내에서 상위 클래스의 생성자를 명시적으로 호출할 때에는 하위 클래스의 생성자 정의 구문의 첫 줄에서만 super() 를 호출할 수 있다.
생성자와 클래스 상속간의 관계 정리
상위 클래스 | 하위 클래스 | 결과 |
생성자 정의 Ⅹ | 생성자 정의 Ⅹ | ○ |
인수가 없는 생성자 | ○ | |
인수가 있는 생성자 | ○ | |
인수가 없는 생성자만 정의 | 생성자 정의 Ⅹ | ○ |
인수가 없는 생성자 | ○ | |
인수가 있는 생성자 | ○ | |
인수가 있는 생성자만 정의 | 생성자 정의 Ⅹ | 에러 발생 |
인수가 없는 생성자 | 상위 클래스의 해당 생성자를 명시적으로 호출하지 않으면 에러 발생 |
|
인수가 있는 생성자 | 상위 클래스의 해당 생성자를 명시적으로 호출하지 않으면 에러 발생 |
super 키워드 사용 시 주의사항 - main() 에서의 super ??
아래 코드의 가장 아랫 줄 『println(ob.super.a);』 와 같이, main() 메소드에서 SubTest119 인스턴스를 생성하고 super 키워드를 사용하면 그 인스턴스에게 상속하는 부모 클래스 SuperTest119에 접근 할 수 있을까?
/*=================================
■■■ 클래스 고급 ■■■
- 상속(Inheritance)
=================================*/
class SuperTest119 // 부모 클래스
{
protected int a = 10;
}
class SubTest119 extends SuperTest119 // 자식 클래스
{
protected int a = 100;
}
// main() 메소드를 포함하고 있는 외부의 다른 클래스(동일 패키지)
public class Test119
{
public static void main(String[] args)
{
// 하위 클래스(SubTest119) 인스턴스 생성
SubTest119 ob = new SubTest119();
// 자식 클래스의 인스턴스 ob 를 통해 부모 클래스의 변수 a 에 접근해보기
System.out.println(ob.super.a);
}
}
위와 같은 방법으로 부모클래스의 변수 a에는 접근할 수 없다. 왜냐하면 현재 super 키워드가 포함(노출)되고 있는 메소드의 클래스는 Test119 클래스이므로 super가 의미하는 객체는 Test119의 부모 클래스이지, ob인스턴스의 부모 클래스가 아니다.
super 키워드를 사용하면 현재 super 키워드가 노출된(포함된) 클래스의 상위 클래스를 찾는다. 따라서 의도했던 『인스턴스의 상위 클래스』를 찾는 것이 아니라, (super가 노출되고 있는) main() 메소드를 포함하는 클래스의 상위클래스를 찾을 것이다.(extends 하지 않았다면 최상위 클래스인 Object 클래스를 가리킨다. Object 클래스에 대한 내용은 포스팅 자바 17(2020.08.26) 에서 다룬다.)
위 코드와 같은 상황에서 의도한대로 부모 클래스의 변수 a에 접근하려면 아래 코드와 같이 『println(((SuperTest119)ob).a); 』 로 접근해야한다.
// 자식 클래스의 인스턴스 ob 를 통해 부모 클래스의 변수 a 에 접근해보기
//System.out.println(ob.super.a); //-- (Ⅹ)
// super 키워드는 자신을 감싸는 클래스를 먼저 찾고 그 상위 클래스로 대치된다
// ★★★ 현재 super 가 노출된 곳 : ★Test119 (→ SubTest119 아님 주의★)
System.out.println(((SuperTest119)ob).a); //-- (○)
//--==>> 10
// ※ 슈퍼 부름
* super 키워드를 통해 부모 클래스의 멤버에 접근하려면 상속 관계의 하위 클래스를 설계하는 과정에서 super를 사용해야한다.
상속 예제 코드 (상속 특징 살펴보기)
/*=================================
■■■ 클래스 고급 ■■■
- 상속(Inheritance)
=================================*/
// Rect117 클래스와 Circle117 클래스의 부모 클래스 SuperTest117
class SuperTest117
{
protected double area;
private String title; // private : 상속 안해준다.
public SuperTest117()
{
System.out.println("SuperTest117... 인자 없는 생성자");
}
public SuperTest117(String title)
{
this.title = title;
System.out.println("SuperTest117... 문자열을 인자로 받는 생성자");
}
public void write()
{
System.out.println(title + " - " + area);
}
}
// SuperTest117 클래스를 상속받는 자식 클래스 Rect117
class Rect117 extends SuperTest117
{
// 눈에 보이지는 않지만 부모 클래스로부터 상속받은 내용..
/*
protected double area;
public void write()
{
System.out.println(title + " - " + area);
}
*/
private int w, h;
// 자식 클래스의 사용자 정의 생성자(deault 생성자 형태)
public Rect117()
{
// 자동으로 삽입
//super(); → SuperTest117();
}
public void calc(int w, int h)
{
this.w = w;
this.h = h;
area = (double) this.w * this.h;
write();
}
// 어노테이션을 붙이므로써 상속받은 메소드와 이름이 겹친 것이 실수가 아니라 의도적임을 알린다.
// 아래의 write() 함수는 상위 클래스의 write() 를 재정의(오버라이딩) 한 것. 자바 15-2 포스팅에서 다룬다.
@Override
public void write()
{
System.out.println("w : "+ w + ", h : " + h);
System.out.println("사각형 - " + area);
}
}
// SuperTest117 클래스를 상속받는 자식 클래스 Circle117
class Circle117 extends SuperTest117
{
// 눈에 보이지는 않지만 부모 클래스로부터 상속받은 내용..
/*
protected double area;
public void write()
{
System.out.println(title + " - " + area);
}
*/
// 자식 클래스의 사용자 정의 생성자
public Circle117(String title)
{
super(title); // 만약 이 부분을 주석처리 하면
// 인자가 없는 생성자를 호출할 것이다.
}
public void calc(int r)
{
area = r * r * 3.141592;
write();
}
}
// main() 메소드를 포함하고 있는 외부의 다른 클래스(동일 패키지)
public class Test117
{
public static void main(String[] args)
{
Rect117 ob1 = new Rect117();
//--==>> SuperTest117... 인자 없는 생성자
//Circle117 ob2 = new Circle117();
//--==>> 에러 발생(컴파일 에러)
//-- 현재 Circle117 클래스에는 매개변수를 필요로하는 사용자 정의 생성자가 만들어져 있으며
// 이로 인해 default 생성자가 자동으로 삽입되지 않은 상태.
Circle117 ob2 = new Circle117("원");
//--==>> SuperTest117... 문자열을 인자로 받는 생성자
ob1.calc(10, 20);
//--==>> w : 10, h : 20
// 사각형 - 200.0
ob2.calc(10);
//--==>> 원 - 314.1592
}
}
키워드 this 와 super 를 정리하면...
this : method(){ 영역 } 에서 매개변수의 이름과 전역변수의 이름(클래스에 선언된 변수)이 동일할 때 this 키워드를 붙여서 전역변수를 구분짓는다.
또한 클래스의 생성자 정의문 첫 줄에서 this(인자..)를 호출하여 다른 생성자를 호출한다.
클래스의 메소드 내부에서 매개변수 이름 vs 전역변수 이름
super : 하위 클래스에서 메소드 구현 과정에서 상속 관계인 상위 클래스와 하위 클래스가 동일한 멤버이름을 사용할 때 super 키워드를 붙여서 상위 클래스의 것을 구분짓는다.
또한 하위 클래스의 생성자 정의문 첫 줄에서 super([인자...])를 명시적으로 호출하여 상위 클래스의 생성자를 호출한다.(호출 하지 않을 때에는 상위 클래스의 default 생성자가 자동으로 호출)
상위 클래스의 멤버이름 vs 하위 클래스의 멤버이름
최근댓글