상속(Inheritance)이란?
OOP(객체지향)에서 중요한 개념이다.
상속을 이용하면 기존의 클래스에 기능을 추가하여 클래스를 정의할 수 있다.
기존 클래스의 확장(extend) 개념으로 extends키워드를 이용한다.
상속을 표현하는 클래스 다이어그램에는 상속받은 클래스(부모)를 가리키도록 화살표를 사용한다.
상속을 이용하는 궁극적인 목표는 코드의 재 사용성과 신뢰성을 높이는 것이다.
재사용성과 신뢰성을 높이는 것은 생산성과 유지보수에 유리하게 작용한다.
이를 이해하려면 클래스를 이용하여 객체지향을 이해할 수 있도록 프로그램을 개발해 봐야 한다.
먼저 예제를 통해 상속이 가지는 문법들을 이해하도록 한다.
이번 실습에서 접근 제한자에 대한 내용은 제외하고 진행하고
상속 개념 확인 후에 앞서 확인했던 접근 제한자를 적용해보도록 한다.
상속을 구성하는 개념
extends : 상속할 때 사용하는 키워드
implements : interface를 상속하여 구현하는 경우 사용하는 키워드
this말고 super : this는 자기자신, super는 부모 객체를 말한다.(생성자 호출 활용)
overriding : 부모클래스의 메서드를 자식클래스에서 재정의하는 것
up-casting/down-casting : 동일한 부모를 갖는 객체는 서로간 대입(참조)이 가능
다형성과 추상화 : overriding과 객체 형변환으로 구현되어지는 개념
예제코드
package exam;
class Point2D {
int x;
int y;
}
public class Ex01 {
public static void main(String[] args) {
Point2D pos1 = new Point2D();
pos1.x = 10;
pos1.y = 20;
System.out.println("[" + pos1.x + "," + pos1.y + "]");
}
}
위 예제에서 Point2D클래스는 x,y좌표를 저장하는 기능을 가짐
만일 3D포인트가 필요하다면?
다음과 같이 기존의 비슷한 기능을 가진 Point2D의 기능을 복사하여 새 클래스를 작성할 수 있다.
package exam;
class Point2D {
int x;
int y;
}
class Point3D {
int x;
int y;
int z;
}
public class Exam {
public static void main(String[] args) {
Point2D pos1 = new Point2D();
pos1.x = 10;
pos1.y = 20;
System.out.println("[" + pos1.x + "," + pos1.y + "]");
Point3D pos2 = new Point3D();
pos2.x = 10;
pos2.y = 20;
pos2.z = 30;
System.out.println("[" + pos2.x + "," + pos2.y + "," + pos2.z + "]");
}
}
위 코드에서 Point2D클래스의 멤버와 같은 내용을 Point3D클래스에 정의한 것을 보면
별로 좋은 방법이 아닌 것 같은 느낌이 드는가?
사실 위 예제는 상속을 설명하는 정말 간단한 예제이므로 완벽하게 상속을 이해하기 위해서는 부족할 수 있다. 어쨌든 상속의 개념만 이해해보자.
Point2D에서 x, y변수를 동일하게 사용하였다.
이 부분을 상속을 이용할 수 있다.
예제코드 확인
package exam;
class Point2D {
int x;
int y;
}
class Point3D extends Point2D {
int z;
}
public class Exam {
public static void main(String[] args) {
Point2D pos1 = new Point2D();
pos1.x = 10;
pos1.y = 20;
System.out.println("[" + pos1.x + "," + pos1.y + "]");
Point3D pos2 = new Point3D();
pos2.x = 10;
pos2.y = 20;
pos2.z = 30;
System.out.println("[" + pos2.x + "," + pos2.y + "," + pos2.z + "]");
}
}
위 코드를 보면 extends Point2D라고 상속을 선언하였고
Point3D에서는 x, y변수 정의를 제거하였다.
실행결과를 보면 상속을 이용하지 않았을 때와 동일한 결과를 보여준다.
위와 같이 상속은 기존 클래스의 내용에 기능이 추가된 클래스를 정의할 때 사용한다.
Point3D객체를 생성했을 때 부모인 Point2D객체의 멤버를 사용할 수 있는 이유는?
객체는 반드시 생성자를 호출해야 생성된다.
다음 예제를 본다.
package exam;
class Point2D {
int x;
int y;
Point2D() {
System.out.println("2D생성");
}
}
class Point3D extends Point2D {
int z;
Point3D() {
System.out.println("3D생성");
}
}
public class Exam {
public static void main(String[] args) {
Point2D pos1 = new Point2D();
pos1.x = 10;
pos1.y = 20;
System.out.println("[" + pos1.x + "," + pos1.y + "]");
Point3D pos2 = new Point3D();
pos2.x = 10;
pos2.y = 20;
pos2.z = 30;
System.out.println("[" + pos2.x + "," + pos2.y + "," + pos2.z + "]");
}
}
디폴트 생성자를 하나씩 정의하였다.
실행결과
실행결과를 보면 Point3D객체가 생성될 때 Point2D객체도 생성되는 것을 확인할 수 있다.
즉 상속된 클래스를 객체로 생성하면 부모객체도 생성되는 것을 이해할 수 있다.
이것이 가능한 이유는 생성자에 생략된 형태가 하나 있기 때문인데 다음 코드를 보자.
package exam;
class Point2D {
int x;
int y;
Point2D() {
super();
System.out.println("2D생성");
}
Point2D(int x, int y) {
super();
this.x = x;
this.y = y;
System.out.println("2D생성(x,y)");
}
}
class Point3D extends Point2D {
int z;
Point3D() {
super(100, 200);
System.out.println("3D생성");
}
Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
System.out.println("3D생성(x,y,z)");
}
}
public class Exam {
public static void main(String[] args) {
Point2D pos1 = new Point2D(11, 22);
System.out.println("[" + pos1.x + "," + pos1.y + "]");
Point3D pos2 = new Point3D();
System.out.println("[" + pos2.x + "," + pos2.y + "," + pos2.z + "]");
Point3D pos3 = new Point3D(11, 22, 33);
System.out.println("[" + pos2.x + "," + pos2.y + "," + pos2.z + "]");
}
}
실행결과
위 코드에는 생성자에 super()를 선언한 것을 볼 수 있다.
이것은 생성자를 호출 했을 때 상속한 부모의 생성자를 지정하여 호출하도록 하는 키워드이다.
기본적으로는 아무 인자도 받지 않는 부모의 생성자를 호출하도록 하지만 위와 같이 선언하여 사용이 가능하다.
pos2객체가 생성될 때는 Point3D생성자는 디폴트를 호출하지만 super(100,200)호출을 통해
Point2D(int x, int y)생성자가 호출된다.
pos3객체가 생성될 때는 Point3D(int x, int y, int z)생성자가 호출되고 부모의 super(x,y)생성자를 호출한다.
그러면 Point2D(int x, int y)생성자를 선택하여 호출이 되는 것이다.
위와 같은 개념으로 자식객체가 생성되기 전에 부모객체가 먼저 생성이 되기 때문에
자식 객체를 생성하면 부모의 멤버도 존재하는 것이다.
위 내용은 멤버 필드(변수)에 대한 내용을 주로 확인해보았는데 멤버 메서드에도 동일한 개념을 적용하면 된다.
메서드를 추가하여 테스트
package exam;
class Point2D {
int x;
int y;
Point2D(int x, int y) {
this.x = x;
this.y = y;
}
void printPointXY() {
System.out.println("[" + x + "," + y + "]");
}
}
class Point3D extends Point2D {
int z;
Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
void printPointXYZ() {
System.out.println("[" + x + "," + y + "," + z + "]");
}
}
public class Exam {
public static void main(String[] args) {
Point2D pos1 = new Point2D(10, 20);
pos1.printPointXY();
Point3D pos2 = new Point3D(10, 20, 30);
pos2.printPointXY();
pos2.printPointXYZ();
}
}
실행결과
상속을 받은 클래스로 객체를 생성하면 부모의 메서드를 사용할 수 있다.
다음과 같이 정리해본다.
상속 개념
- 상속한 클래스(자식 클래스)로 객체를 생성하면 부모의 내용을 모두 포함한다.
- 상속관계를 맺어주면 하위클래스들에서 사용하는 공통적인 기능은 상위 클래스에서 관리를 하도록 만들어 준다.
- 코드의 관리가 용이하다.
- 다중상속이 불가능하고 단일 상속(일자 상속)만 가능하다.(다이아몬드 상속 문제)
- interface를 이용하면 다중상속의 개념을 표현할 수 있음
- 클래스를 만들 때 클래스들 간에 관계에 따라 관계 형성을 고려해야 한다.
is ~ a 관계가 성립된다면 상속관계가 유리 ( ~는 ~다. )
has ~ a 관계가 성립된다면 포함관계가 유리 ( ~는 ~를 가지고 있다. )
예)
학생은 사람이다.(is ~ a)
자동차는 엔진을 가지고 있다. (has ~ a)
개발 시 사용할 클래스를 분석하여 최대한 많은 관계를 만들어 주는 것이
코드의 재사용성, 신뢰성 등을 높인다.
단 상속을 남용하는 것은 사용하지 않은 것보다 못 할 수 있다.
요구사항에 따른 클래스 설계가 무엇보다도 중요하다는 것을 알고 있어야 한다.
'교육자료 > Java' 카테고리의 다른 글
Java 클래스 구성 요소(Overriding) (0) | 2017.06.25 |
---|---|
Java 클래스 구성 요소 (0) | 2017.06.25 |
Java 클래스 구성 요소 (중첩클래스) (0) | 2017.06.22 |
Java 클래스 구성 요소(예약어) (0) | 2017.06.22 |
Java 클래스 구성 요소(Setter, Getter메서드) (0) | 2017.06.18 |