Class part.3 (Inheritance 상속)
객체지향 중에서 가장 복잡하고 새로운 기능이 많다.
상속은 소스 없이 기존의 프로그램을 그대로 가져다가 내가 원하는 기능만 추가할 수 있다.
<상황>
A회사에서 B회사에게 전체 프로그램 중에 일부 기능 프로그램을 외주제작 해달라고 맡겼다.
B회사에서는 프로그램을 만들어 주었지만 기존에 만든 기능 중에
다른 기능을 더 추가 할 일이 생겼다. 어떻게 해야할까?
package prjClass3;
public class Cms {
int no;
String name;
char level;
//기본생성자 추가
public Cms() {
}
//생성자 생성
public Cms(int no, String name, char level) {
super();
this.no = no;
this.name = name;
this.level = level;
}
public void display() {
System.out.println("고객번호 : " + no);
System.out.println("고객이름 : " + name);
System.out.println("고객이름 : " + level);
}
}
package prjClass3;
//'extends Cms'는 Cms라는 클래스로 부터 상속을 받겠다는 뜻
//여기서 Cms는 부모클래스, CmsExt는 자식클래스이다.
//눈에 보이지는 않지만 Cms 소스가 그대로 복사가 되어있다.
// Cms에서 기본생성자가 없으면 에러가 난다.
public class CmsExt extends Cms{
private String address;
//기존 기능에서 adress를 추가해준다.
public CmsExt(int no, String name, char level, String address) {
this.no = no;
this.name = name;
this.level = level;
this.address = address;
}
public void print() {
// display라는 메소드가 이 클래스에는 없어도 Cms에 있었기 때문에 상속받아 쓸 수 있다.
display();
System.out.println("고객주소 : " + address);
}
}
package prjClass3;
public class InheritanceTest {
public static void main(String[] args) {
// TODO 상속의 기본 개념
Cms kim = new Cms(1, "김유신", 'C');
kim.display();
System.out.println("\n\n");
CmsExt lee = new CmsExt(1, "김유신", 'C', "서울시 강남구");
lee.print();
}
}
※ 결과값
고객번호 : 1
고객이름 : 김유신
고객이름 : C
고객번호 : 1
고객이름 : 김유신
고객이름 : C
고객주소 : 서울시 강남구
** 용어정리
부모클래스 : 상속을 해주는 클래스 (Super Class)
자식클래스 : 상속받는 클래스 (SubClass)
[Ms (C, C++) 에서는 부모클래스를 베이스클래스, 자식클래스를 파생클래스 derived 라고 부른다.]
1. 상속 제외
1) 생성자
- 생성자는 자식이다. 자식에게 부모를 달라고 할 수는 없다.
2) pivate
- 중요하니까 보안을 해 놓은건데 상속이 되게 할 수는 없으므로
- 엄밀히 말하면 상속이되지만 고려해야한다.
- 부모클래스를 처음 만들 때는 변수를 private로 하고 고민한다.
2. 부모의 생성자를 호출 할 수 있다.
- 부모생성자를 상속받고 싶지만 변수와 메서드가 private로 막혀있어서 어려울 때는
super 메서드를 이용하여 부모에게 먼저 저장하고, 부모의 생성자를 호출한다.
- 반드시 생성자 안에서만 호출 가능하다.
- 부모의 생성자를 호출 할 때는 반드시 첫번째 줄에서 호출한다.
부모의 생성자를 호출
super()
super.변수, super.메서드()
자기 자신의 생성자를 호출
this()
this.변수, this.메서드()
- this()는 자주 쓰지 않지만, super는 자주 쓴다.
package prjClass3;
public class Cms {
private int no;
private String name;
private char level;
//기본생성자 추가
public Cms() {
}
//생성자 생성
public Cms(int no, String name, char level) {
super();
this.no = no;
this.name = name;
this.level = level;
}
public void display() {
System.out.println("고객번호 : " + no);
System.out.println("고객이름 : " + name);
System.out.println("고객이름 : " + level);
}
}
package prjClass3;
//'extends Cms'는 Cms라는 클래스로 부터 상속을 받겠다는 뜻이다.
//여기서 Cms는 부모클래스, CmsExt는 자식클래스이다.
//눈에 보이지는 않지만 Cms 소스가 그대로 복사가 되어있다.
// Cms에서 기본생성자가 없으면 에러가 난다.
public class CmsExt extends Cms{
private String address;
//기존 기능에서 adress를 추가해준다.
public CmsExt(int no, String name, char level, String address) {
/*
//부모생성자를 상속받고 싶지만 private로 막혀있어서 어려움
this.no = no;
this.name = name;
this.level = level;
*/
//super 메서드를 이용하여 부모에게 먼저 저장하고, 부모의 생성자를 호출한다.
//부모의 생성자를 호출 할 때는 반드시 첫번째 줄에서 작성한다.
super(no, name, level);
this.address = address;
}
public void print() {
// display라는 메소드가 이 클래스에는 없어도 Cms에 있었기 때문에 상속받아 쓸 수 있다.
display();
System.out.println("고객주소 : " + address);
}
}
package prjClass3;
public class InheritanceTest {
public static void main(String[] args) {
// TODO 상속의 기본 개념
Cms kim = new Cms(1, "김유신", 'C');
kim.display();
System.out.println("\n\n");
CmsExt lee = new CmsExt(1, "김유신", 'C', "서울시 강남구");
lee.print();
}
}
※ 결과값
고객번호 : 1
고객이름 : 김유신
고객이름 : C
고객번호 : 1
고객이름 : 김유신
고객이름 : C
고객주소 : 서울시 강남구
3. 생성자의 호출 순서
class A{
A(){System.out.println("A생성자호출");}
}
//A를 상속받자
class B extends A{
//보이지는 않지만 super(); 가 생략되어 있다.
B(){System.out.println("B생성자호출");}
}
//B를 상속받자
class C extends B{
C(){
//생략되어있는 super(); 써도 된다.
super();
System.out.println("C생성자호출");}
}
public class CallOrderTest {
public static void main(String[] args) {
//TODO 생성자의 호출순서
new C();
}
}
※ 결과값
A생성자호출
B생성자호출
C생성자호출
class A{
}
//A를 상속받자
class B extends A{
//부모클래스에 생성자를 만들경우 기본생성자가 없어지기 오류가 난다.
B(int i){
}
}
//B를 상속받자
class C extends B{
//생성자에는 아래와 같은 기본 생성자인 super 코드가 숨겨져있다.
C(){
super();
}
}
public class CallOrderTest {
public static void main(String[] args) {
//TODO 생성자의 호출순서
new C();
}
}
※ 결과값
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
The constructor B() is undefined
at prjClass3.C.<init>(CallOrderTest.java:18)
at prjClass3.CallOrderTest.main(CallOrderTest.java:25)
package prjClass3;
class A{
}
//A를 상속받자
class B extends A{
//부모클래스에 생성자를 만들경우 기본생성자가 없어지기 오류가 나기 때문에 기본생성자를 만들어준다.
B(){}
B(int i){
}
}
//B를 상속받자
class C extends B{
}
public class CallOrderTest {
public static void main(String[] args) {
//TODO 생성자의 호출순서
new C();
}
}
※ 결과값
(에러가 나지 않는다.)
4. 상속은 확장의 개념이다.
- 반드시 부모로 부터 물려 받은 것을 추가해야한다.
그렇게 하지 않고 물려받은 그대로 쓴다면 상속할 필요가 없다.
- 상속은 반드시 철저한 계획하에 해야한다.
5. 클래스 상속은 단일 상속만 지원한다.
<상속의 법칙>
- 상속은 중복기능이 있으면 안된다.
- 부모는 최소의 기능(공통으로 필요한 기능)만 갖는다.
<상속의 종류>
1.일반상속
- 부모가 하나이다.
- 일반적인 트리구조를 가지고 있다.
- 추가 보완 할 수 있다.
- 어떤 기능이 있는지 알려면 부모를 알면 된다. (안전하고 견고하다.)
=> java에서는 다중상속의 문제점 때문에 class에서는 일반상속만 한다.
2.다중상속
- 부모가 여럿이다.
- 다중상속은 중복된 기능이 있을 수 있을 가능성이 많다.
- 다중상속은 처음부터 완벽한 설계아래 만들어야 해서 수정이 어렵다.
- 다중상속은 어떤 기능을 가지고 있는지 알기 어렵다.
(다중상속이 처음 받아들인 C++은 나중에 MFC라는 프로그램을 만들 때 다중상속을 없애버렸다.)
=> java에서는 인터페이스 등에서만 다중상속을 한다.
<상속의 설계>
- 부모로 갈 수록 최소의 기능, 공통적으로 필요한 기능만 갖는다.
- 자식은 조금더 복잡하다.
예) 자동차를 만들어 보자.
★ 상속 주제를 정해서 실제로 설계를 해보면 좋다. ★
6. 부모클래스와 자식클래스의 참조관계
package prjClass3;
class First{
int a = 10;
void display() {
System.out.println("a : " + a);
}
}
class Second{
int b = 10;
void print() {
System.out.println("b : " + b);
}
}
public class ReferenceTest {
public static void main(String[] args) {
// TODO 부모와 자식클래스의 참조 관계
First f1 = new First();
f1.display();
Second s1 = new Second();
s1.print();
//First 는 First 끼리 주소를 참조한다.
First f2 = f1;
f2.display();
// f2 = (First)s1; 서로다른 클래스들 끼리는 주소를 넘겨받을 수 없다.
}
}
※ 결과값
a : 10
b : 10
a : 10
부모가 자식의 주소를 참조할 수 있다.
이는 부모가 잠시 가지고 있는 주소를 자식이 돌려줄 때만 가능하다.
이런 경우는 어떤 자식 클래스를 인스턴스로 생성할지 모를 때 유용하다.
또 하나의 예제를 들어보자.
A a;
B b;
C c;
B b1 = new B();
C c1 = new C();
B b2 = new B();
B b3 = new B();
C c2 = new C();
//부모클래스가 자식클래스의 주소값을 받을 수 있다.
A[] a = {b1, c1, b2, b3, c2, new B(), new C()};
사용 예제를 자꾸 보아야 이해가 가능하다.
package prjClass3;
class First{
int a = 10;
void display() {
System.out.println("a : " + a);
}
}
class Second extends First{
int b = 20;
void print() {
System.out.println("b : " + b);
}
}
public class ReferenceTest {
public static void main(String[] args) {
// TODO 부모와 자식클래스의 참조 관계
First f1 = new First();
f1.display();
Second s1 = new Second();
s1.print();
//First 는 First 끼리 주소를 참조한다.
First f2 = f1;
f2.display();
f2 = s1;
s1.a =11;
f2.display();
//부모가 자식의 주소를 보관하고 있을 때만 다시 자식에게 넘겨줄 수 있다.
Second s2 = (Second)f2;
s2.b = 21;
s2.print();
}
}
※ 결과값
a : 10
b : 20
a : 10
a : 11
b : 21
이렇게 부모가 자식의 주소값(주소)를 받아 올 수 있는 이유는
자식 클래스가 생길 때부터 이미 부모클래스를 포함한 관계이기 때문이다.
7. 메서드의 오버라이딩(Overrding) : 재정의
<오버로딩>
- 서로다른 메서드를 같은 이름으로 쓰는 것. (중복정의, 다형성을 지원)
- 어디에서나 사용가능
컴퓨터를 용도별로 컴퓨터로 산다면 얼마나 좋을까?
음악용, 작업용, 영화감상용...
이렇게 한다면 낭비가 너무 심할 것이다.
보통은 하나의 컴퓨터를 용도에 맞게 조금씩 고쳐서 쓴다.
오버라이딩은 하나의 메서드를 고쳐서 쓰는 것을 말한다.
여기서 오버라이딩은
부모로 부터 물려받은 메소드를 고쳐서 쓰는 것을 말한다.
조금 더 리소스를 절약하자는 측면에서 쓴다.
상속에서만 사용 가능하다.
오버라이딩
- 부모로 부터 물려받은 메소드를 고쳐쓰는 것
- 상속에서만 사용 가능
- UX (User eXperience) 부모클래스를 만든 사용자 경험을 그대로 쓸 수 있다는 장점이 있다.
1) 다형성지원
2) 상속에서만 사용가능
package prjClass3;
public class Cms {
private int no;
private String name;
private char level;
//기본생성자 추가
public Cms() {
}
//생성자 생성
public Cms(int no, String name, char level) {
super();
this.no = no;
this.name = name;
this.level = level;
}
public void display() {
System.out.println("고객번호 : " + no);
System.out.println("고객이름 : " + name);
System.out.println("고객이름 : " + level);
}
}
package prjClass3;
//'extends Cms'는 Cms라는 클래스로 부터 상속을 받겠다는 뜻이다.
//여기서 Cms는 부모클래스, CmsExt는 자식클래스이다.
//눈에 보이지는 않지만 Cms 소스가 그대로 복사가 되어있다.
// Cms에서 기본생성자가 없으면 에러가 난다.
public class CmsExt extends Cms{
private String address;
//기존 기능에서 adress를 추가해준다.
public CmsExt(int no, String name, char level, String address) {
/*
//부모생성자를 상속받고 싶지만 private로 막혀있어서 어려움
this.no = no;
this.name = name;
this.level = level;
*/
//super 메서드를 이용하여 부모에게 먼저 저장하고, 부모의 생성자를 호출한다.
//부모의 생성자를 호출 할 때는 반드시 첫번째 줄에서 작성한다.
super(no, name, level);
this.address = address;
}
/*
public void print() {
// display라는 메소드가 이 클래스에는 없어도 Cms에 있었기 때문에 상속받아 쓸 수 있다.
display();
System.out.println("고객주소 : " + address);
}
*/
//부모가 물려준 display()메소드를 고쳐서 쓰겠다.
//오버라이딩은 부모클래스 메서드와 똑같은 형식으로 해야 된다.
//이름만 같고 타입이 다르면 오버로딩이 된다.
//UX (User eXperience) 부모클래스를 만든 사용자 경험을 그대로 쓸 수 있다는 장점이 있다.
public void display() {
//부모가 가진 disply()호출한다는 의미로 super.을 써준다.
//그래야 무한반복이 되지 않는다.
super.display();
System.out.println("고객주소 : " + address);
}
}
package prjClass3;
public class InheritanceTest {
public static void main(String[] args) {
// TODO 상속의 기본 개념
Cms kim = new Cms(1, "김유신", 'C');
kim.display();
System.out.println("\n\n");
CmsExt lee = new CmsExt(1, "김유신", 'C', "서울시 강남구");
//lee.print();
//오버로딩을 사용하여 CmsExt 클래스에서 받아옴.
lee.display();
}
}
※결과값
고객번호 : 1
고객이름 : 김유신
고객이름 : C
고객주소 : 서울시 강남구
8. 추상클래스, 추상메서드
1) 추상메서드
- 내용은 없고 선언만 되어있는 메서드
- 자식이 가져다 쓸 수 있도록 부모가 빈 메서드를 선언 할 경우.
- 반드시 오버라이딩을 해야한다.
- 반드시 abstract 키워드를 사용
추상메서드를 이용한 부모클래스
// 안에 내용이 없고 거쳐만 간다.
void a(){
}
// 부모클래스에서 중괄호를 생략하여 적어 시행속도를 빠르게 한다.
// 자시클래스에서는 중괄호를 펼친채로 사용한다.
//자식은 반드시 오버라이딩 해야한다. 프로그래밍을 안전하게 할 수 있다.
abstract void a();
2) 추상 클래스
- 추상메서드를 1개 이상 가지고 있는 경우
- abstract 키워드 사용
abstract class 클래스명{
}
- 절대로 인스턴스를 생성할 수 없다.
인스턴스가 없다 -> 실행할 수 없다. -> 직접일을 하지 않겠다.
내가 가지고 있는 것을 자식에게 물려주는 용도로만 사용.
자식을 위해 상속을 하는 의미로만 사용
예)
도형의 공통된 속성 A : 점, 선, 면적
사각형 : A 상속
원 : A 상속
삼각형 : A 상속
package prjClass3;
//추상클래스를 만드려면 abstract를 해준다.
abstract class TwoDShape {
private double width;
private double height;
private String name;
//기본생성자를 미리 만들어 주는 것이 좋다.
public TwoDShape() {}
//private 변수를 쓸 수 있도록 생성자 호출
public TwoDShape(double width, double height, String name) {
super();
this.width = width;
this.height = height;
this.name = name;
}
//상속법에 의해 자식에게 상속해 줄 수 있는 것은 getter메서드 하나이다.
//private의 값을 입력할 수 있도록 getter 메서드를 만든다.
public double getWidth() { return width; }
public double getHeight() { return height; }
public String getName() { return name; }
/*
public double getArea() {
//미리 면적을 구하기 어렵기 때문에 기본값으로
return 0.0;
}
*/
//추상메서드를 만든다.
public abstract double getArea();
}//end class
//부모에게 getter메서드를 상속
class Triangle extends TwoDShape{
public Triangle(double w, double h, String n) {
//Triagle이 저장할 능력이 없어서 변수가 있는 부모에게 대신 저장한다.
super(w, h, n);
}
//부모로 부터 메소드를 물려받아 고쳐쓴다. 오버라이딩한다.
public double getArea() {
//부모에게 저장한 값을 꺼내온다.
return getWidth() * getHeight() /2;
}
}//end class
//부모에게 getter메서드를 상속
class Rectangle extends TwoDShape{
public Rectangle(double w, double h, String n) {
//Triagle이 저장할 능력이 없어서 변수가 있는 부모에게 대신 저장한다.
super(w, h, n);
}
//부모로 부터 메소드를 물려받아 고쳐쓴다. 오버라이딩한다.
public double getArea() {
//부모에게 저장한 값을 꺼내온다.
return getWidth() * getHeight();
}
}//end class
public class ShapeTest {
public static void main(String[] args) {
// TODO 2차원 도형을 기르기 위한 프로그램
Triangle tr1 = new Triangle(5.0, 10.0, "정삼각형");
Triangle tr2 = new Triangle(7.0, 15.0, "직각삼각형");
Rectangle re1 = new Rectangle(3.0, 3.0, "정사각형");
Rectangle re2 = new Rectangle(3.0, 6.0, "직사각형");
System.out.println(tr1.getName() + " : " + tr1.getArea());
System.out.println(tr2.getName() + " : " + tr2.getArea());
//부모가 어떤 기능을 가졌는지 알아야 기능을 파악 할 수 있다.
//부모가 자식의 주소를 한 번에 참조할 수 있다.
TwoDShape[] t = {tr1, tr2, re1, re2,
//배열 안에 바로 인스턴스 생성을 해서 추가할 수도 있다.
new Rectangle(3.0, 4.0, "그냥사각형")};
for(int i=0; i<t.length; i++) {
System.out.println(t[i].getName()+" : "
+ t[i].getArea());
}
}//end method
}//end class
※ 결과값
정삼각형 : 25.0
직각삼각형 : 52.5
정삼각형 : 25.0
직각삼각형 : 52.5
정사각형 : 9.0
직사각형 : 18.0
그냥사각형 : 12.0
void 앞에 final을 붙이면 오버라이딩을 금지한다는 뜻이다.
final void 메서드명(){
...
}
class 앞에 final을 붙히면 상속을 금지한다는 뜻이다.
final class 클래스명{
...
}
(10) Object 클래스
- 모든 자바 클래스는 Object라는 클래스에서 상속을 받는다.
- 모든 자식을 참조 할 수 있지만, 너무 기본적인 클래스라 제한을 받는다.
- Java API에서 Object 클래스를 찾아보자 (javabase -> java.lang-> Object )
https://docs.oracle.com/javase/9/docs/api/index.html?overview-summary.html
'개발 > Java' 카테고리의 다른 글
[java] 인터페이스(InterFace) (0) | 2017.10.26 |
---|---|
[java] 패키지 package (0) | 2017.10.25 |
[java] 클래스 (Class) part.2 (0) | 2017.10.24 |
[java] 배열 (Array) (0) | 2017.10.19 |
[java] 클래스 (Class) part.1 (0) | 2017.10.18 |