[CH7]상속과 다형성
✔상속 : 문법, 메모리 구조, 코드 표현, 생성자 상속
✔다형성
└ 객체 타입 변환 : 업캐스팅, 다운캐스팅, instanceof()
└메소드 오버라이딩
✔super와 super()
✔Object
객체와 클래스의 관계
객체 지향에 아주 중요한 개념.
상속이라는 개념이 잘 동작하도록 상속의 기능을 수용하기 위한 규칙
1. 상속
어떤 객체가 있을 때, 그 객체의 필드(변수)와 메소드를 다른 객체가 물려받을 수 있는 기능
부모 클래스의 멤버(필드, 메소드, 이너클래스)를 자식 클래스가 내려받아(상속) 클래스 내부에 포함.
상속 다이어그램을 표기할 때 부모 클래스 쪽으로 화살표 ex) 부모 클래스 ← 자식 클래스
(부모 클래스엔 공통적인 내용 - 공통 멤버가 들어간다)
(1) 상속의 필요 및 장점
클래스에 새로운 메소드를 추가하고 싶은데 그게 여유치 않을 때 - 자신이 코딩하지 않아 수정하기 어려울 때 기존의 클래스를 상속받는 클래스를 만들어 거기서 수정한다.
-장점
코드 중복 제거 : 부모에서 만든 기능을 자식이 상속받으므로 또 만들 필요가 없음.
재활용성
유지보수 편리 : 부모 클래스를 수정했을 때 상속받는 자식 클래스도 수정의 영향을 받는다. 일일이 수정할 필요 없다.
다형적 표현 기능 : 오버라이딩, 오버로딩.
(2) 상속의 개념과 구성
기존의 객체는 기능을 물려준다는 의미에서 '부모 객체',
새로운 객체는 기존 객체의 기능을 물려받았다는 의미에서 '자식 객체'라고 하며
기존의 부모 객체가 가지고 있던 변수나 메소드를 수정하거나 새로운 것을 추가할 수 있다.
-부모 클래스 : 상위 클래스, super class, 기초 클래스
-자식 클래스 : 하위 캘래스, derive class
Calculator (부모) | ← SubstractCalculator (자식) | |
← MutiplexCalculator (자식/부모) | ← divisionableCalculator (자식) |
└자식 클래스가 부모 클래스까지 확장했다. 그러므로 부모 클래스에 정의된 메소드 등을 사용할 수 있다.
(3) 상속 문법
키워드 extends 사용. 다중 상속 불가(부모 클래스 여러 개 불가), 자식 클래스는 여러 개 가능
class 자식클래스 extends 부모클래스 { ... }
// 다중 상속 불가 : 부모 클래스 여러 개 불가
class 자식클래스 extends 부모클래스1, 부모클래스2 { ... }
//자식 클래스는 여러 개 가능(이건가?)
class 자식클래스1 extends 부모클래스 { ... }
class 자식클래스2 extends 부모클래스 { ... }
_예제) 상속
//사람 클래스_부모 클래스
package inheritance;
public class 사람 {
String 이름;
int 나이;
void 먹기() {}
void 잠자기() {}
}
//대학생 클래스_자식 클래스
package inheritance;
public class 대학생 extends 사람 {
int 학번;
void 등교하기() {}
}
//직장인 클래스_자식 클래스
package inheritance;
public class 직장인 extends 사람 {
int 사번;
void 출근하기() {}
}
//상속 테스트
package inheritance;
public class 상속테스트 {
public static void main(String[] args) {
대학생 c = new 대학생(); // 객체 생성
c.이름 = "홍길동";
c.나이 = 20;
c.먹기();
직장인 w = new 직장인();
w.이름 = "홍홍홍";
w.사번 = 1000;
w.출근하기();
}
}
> 상속됐으면 아래 처럼 입력할 때 선택지로 뜬다.
사람으로부터 상속받은 나이, 이름, 먹기, 잠자기를 불러올 수 있음. public. 상속.
(4) 상속 시 메모리 구조
상속 받으면 부모 클래스 멤버를 가질 수 있는 이유는 객체 속 부모 클래스 객체를 먼저 생성해 포함하기 때문에.
(5) 상속 관계 코드 표현
A는 A이다. | A a1 = new A(); | A가 부모 클래스, B가 자식 클래스 |
A ↑ B |
B는 A이다. | A a2 = new B(); |
(6) 생성자 상속
상속 불가(*생성자 조건 : 클래스 이름 == 생성자 이름, 리턴 타입 없음)
상속하게 되면 클래스 이름과 생성자 이름이 달라지게 되기 때문.
class A {
A(){ }
}
class B extends A {
A(){ //클래스 이름(B) != 생성자 이름(A)이라서 오류 }
}
2. 상속과 다형성
하나의 객체가 여러 가지 타입을 가질 수 있는 것.
하나의 메소드나 클래스가 있을 때, 이것들이 다양한 방법으로 동작하는 것.
ex) 오버라이딩, 오버로딩(같은 이름, 다른 동작. 매개변수에 따라 다르게)
_예시) 상속 관계에 따른 객체 정의
A (부모) | ← B (자식/부모) | ← D (B의 자식) |
← C (자식) |
//객체 생성 1 - 클래스이름 == 생성자이름
A a = new A();
B b = new B();
C c = new C();
D d = new D();
//객체 생성2 : 부모 클래스 - 자식 클래스 //업캐스팅 자동변환
A a1 = new B();
A a2 = new C();
A a3 = new D();
B b1 = new C();
B b1 = new D();
//객체 생성3 : 오류. 자식 클래스 - 부모 클래스
B b1 = new A();
C c1 = new A();
C c2 = new B();
D d1 = new A();
D d2 = new B();
D d3 = new C();
└부모 클래스형으로 자식 클래스 생성자 객체 선언 가능
└자식 클래스형으로 부모 클래스 생성자 객체 선언 불가능.
-상속 관계
└자식 데이터 타입은 부모에 할당할 수 있음.
└부모 데이터 타입은 자식 데이터 타입으로 할당되는 것 불가. 명시적 형 변환(다운캐스팅)
package lcoding;
class Student {
String name;
Student(String name){//생성자
this.name = name;
}
public boolean equals(Object obj) { //equals 재정의
// Student s = obj; //부모가 자식의 데이터 타입으로 할당되는 것 불가.
Student ss = (Student)obj; //강제적으로 형 변환. 다운캐스팅
return this.name == ss.name;
//s1 == s2
}
}
class ObjectDemo{
public static void main(String[] args) {
Student s1 = new Student("ee");
Student s2 = new Student("ee");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
}
}
(1) 객체 타입 변환 : 업캐스팅, 다운캐스팅
상속 관계일 때 가능. 상속관계인지 따지는 것은 instanceof() 메소드.
-업캐스팅 : C, D는 B를 상속받고 있어서 (3)번 같은 객체 생성 가능. 클래스명 체크
-다운캐스팅 : 경우에 따라 가능하기도 하고 불가능하기도 하고.
A (부모) | ← B (자식/부모) | ← D (자식) |
← C (자식) |
└업캐스팅 : B는 A다. C는 A다. D는 B다.
└다운캐스팅 : A는 B다.
업캐스팅되었을때, 부모 클래스의 메소드와 이름이 같은 메소드를 계속 찾아감. 자식 클래스로. 맨마지막의 메소드가 본인의 것.
_예제) p259
package polymorphism;
class Animal { //부모
public void move() {
System.out.println("동물이 움직입니다.");
}
}
class Human extends Animal {
public void move() {
System.out.println("사람이 두 발로 걷습니다.");
}
}
class Tiger extends Animal {
public void move() {
System.out.println("호랑이가 네 발로 뜁니다.");
}
}
class Eagle extends Animal {
public void move() {
System.out.println("독수리가 하늘을 납니다.");
}
}
//------
public class AnimalTest1 {
public static void main(String[] args) {
AnimalTest1 aTest = new AnimalTest1();
aTest.moveAnimal(new Human()); //사람이 두 발로 걷습니다.
aTest.moveAnimal(new Tiger()); //호랑이가 네 발로 뜁니다.
aTest.moveAnimal(new Eagle()); //독수리가 하늘을 납니다.
}
public void moveAnimal(Animal animal) { //Method
animal.move();
}
}
public void move(){ } 함수들이 클래스 명에 따라서 값이 틀려짐.
보통 e1.setEname("홍길동"); 느낌인데... 왜? 저렇게 넣는 거지? 메서드?
(2) 다운캐스팅 가부 확인 : instanceof()
객체가 어떤 클래스 타입인지 알아보는 방법.
B(자식)은 A(부모)를 상속받고 있다.
객체 instanceof 클래스 //true | false 반환
A a = new B();
if( a instanceof B ){ //true
B b = (B)a;
}
A a = new A();
if( a instanceof B ){ //false
B b = (B)a;
}
(3) 메소드 오버라이딩
부모 클래스에서 상속받은 메소드를 사용 또는 재정의(덮어쓰기)
부모 클래스의 변수나 메소드를 자식 클래스가 사용할 수 있게 하면서 동시에 자식 클래스에 어떤 변수나 메소드를 추가할 수 있음.
-조건
부모 클래스와 자식 클래스 간의 메소드 형식(서명, 시그니처)
└메소드 이름, 메소드 매개변수의 숫자와 데이터 타입과 순서, 메소드의 리턴 타입이 일치해야 함.
부모 클래스의 메소드보다 접근지정자는 같거나 넓어야 함.
class A{
void print(String str){
System.out.println("A class");
}
}
class B extends A {
void print(String str){
System.out.println("B class");
}
}
└메소드 이름(print), 매개변수 숫자와 데이터타입(String str), 메소드 리턴타입(void) 동일함.
-메모리 구조
class A{
void.print(){
System.out.println("A Class");
}
}
class B extends A {
void.print(){
System.out.println("B Class");
}
}
// = = = = =
public static void main(String[] args){
A aa = new A();
aa.print(); // A Class
B.bb = new B();
bb.print(); // B Class
//A타입 선언, B 객체 생성(다형적 표현)
A.ab = new B();
ab.print(); // B Class
}
-부모 클래스와 자식 클래스의 메소드 A
부모 클래스의 A 메소드를 자식 클래스가 상속받아 재정의. 자식 클래스의 A 메소드를 호출하면 부모 클래스와 자식 클래스 중 어떤 메소드를 호출할까? 자식 클래스의 것 사용(부모의 것을 가린다)
_예제)
class Animal{
void cry() { }
}
class Bird extends Animal{
void cry(){ System.out.println("짹짹"); }
}
class Cat extends Animal{
void cry(){ System.out.println("야옹"); }
}
class Dog extends Animal{
void cry(){ System.out.println("멍멍"); }
}
// = = = = =
//각각 타입으로 선언 + 각각 타입으로 객체 생성
Animal aa = new Animal();
Bride bb = new Bird();
Cat cc = new Cat();
Dog dd = new Dog();
aa.cry(); //
bb.cry(); //짹짹
cc.cry(); //야옹
dd.cry(); //멍멍
//Animal 타입으로 선언 + 자식클래스 타입으로 객체 생성(다형적 표현)
Animal ab = new Bird();
Animal ac = new Cat();
Animal ad = new Dog();
ab.cry(); //짹짹
ac.cry(); //야옹
ad.cry(); //멍멍
//배열로 한번에 관리 가능
Animal[] animals = new Animal[]{new Bird(), new Cat(), new Dog()};
for(Animal animal : animals){
animal.cry();
} // 짹짹, 야옹, 멍멍
메서드 오버라이딩 | 메서드 오버로딩 |
부모의 리턴 데이터 타입 = 자식의 리턴 데이터 타입 메소드 이름, 매개변수의 개수와 데이터 타입 같아야 함 |
메서드 이름 같은데 매개변수 달라 반환값 다른 것 |
오버라이딩 : 부모 클래스에 올라탄다는 느낌.
오버로딩 : 다른 매개변수, 같은 이름의 메소드를 여러 클래스에서 로딩한다는 느낌.
class A{
void print1(){System.out.println("A print1");} //1 : 3번과 메서드 오버라이딩
void print2(){System.out.println("A print2");} //2 : 4번과 메서드 오버로딩
}
class B extends A {
void print1(){System.out.println("B print1");} //3
void print2(int a){System.out.println("B print2");}//4
}
public static void main(String[] args){
//#1. A 타입 선언 A 객체 생성
A aa = new A();
aa.print1(); //A print1
aa.print2(); //A print2
//#2. B 타입 선언 B 객체 생성
B bb = new B();
bb.print1(); //B print1
bb.print2(); //A print2
bb.print2(3); //B print2
//#3. A 타입 선언 B 객체 생성 (다형적 표현)
A ab = new B();
ab.print1(); //B print1
ab.print2(); //A print2
//ab.print2(3); // 오류
}
└bb.print2();는 메서드 이름, 매개변수 종류와 개수 맞는 거 찾아 부모 클래스로 올라감. 상속.
└ab.print2(3); 오류는 메서드오버라이딩 되지 않았기 때문(이름. 매개변수 종류와 개수, 타입. 리턴타입)
-오버라이딩 여부 정리
A | a | = | new | A( ); |
instance필드, static 필드, static 메서드 |
instance메서드 | |||
오버라이딩 X | 오버라이딩 O |
(+)
class A{
public String x(){
return "A.x";
}
}
class B extends A{
public String y(){
return "Y";
}
}
//
public class PolymorphismDemo1 {
public static void main(String[] args){
A obj = new B(); //부모 클래스형으로 객체 생성
obj.x(); //A.x
obj.y(); //오류 : A 안에 y()가 없기 때문에.
}
}
B를 인스턴스화(객체 생성)할 때, 데이터타입은 자기자신일수도 있고 부모 클래스의 데이터타입일수도 있음. 만약 부모 클래스의 데이터타입으로 정의내렸다면 부모 클래스에 없는 메소드를 호출할 수 없다. 부모에 없고 자식에게는 있다고 할지라도.
class A{
public String x(){
return "A.x";
}
}
class B extends A{
public String x(){ //method Overriding
return "B.x";
}
public String y(){
return "Y";
}
}
class B2 extends A{
public String x(){
return "B2.x";
}
}
//
public class PolymorphismDemo1 {
public static void main(String[] args){
A obj = new B(); //부모 클래스형으로 객체 생성
A obj2 = new B();
System.out.println(obj.x()); //B.x
System.out.println(obj2.x()); //B2.x
}
}
부모 클래스의 메소드를 자식 클래스에서 재정의(오버라이딩)했음. 이후에 객체를 생성해서 접근할때, 자식 클래스에 있는 메소드 호출한다. 부모인 A class, 자식인 B class 둘다 메소드 x() 있다면, 메소드 x()를 호출했을때, 자식 클래스의 메소드 x() 호출한다. (자식이 부모를 가린다.)
3. super와 super()
(1) super
현재 클래스의 부모 클래스 객체. 자식 클래스가 부모 클래스 호출할 때 super 사용.
필드명 중복 또는 메소드 오버라이딩으로 가려진 부모 필드/메소드를 호출하기 위해 주로 사용.
(만약 메소드를 재정의하고 싶다면, super 아래쪽으로 추가하면 됨.)
(2) super()
부모 클래스 생성자 호출. 괄호 안의 값 가져온다.
(* this()는 클래스 내 다른 생성자 호출)
-주의사항
생성자 내부에서만 사용 가능. 처 줄에 위치(this()랑 조건 같음)
자식 클래스 생성자의 첫줄에는 반드시 this() 또는 super()가 포함되어야 함. 안 쓰면 super() 자동삽입.
super()를 안 쓰면 컴파일러가 자동 삽입하는데 이때 기본 생성자가 없다면 오류. 그래서 기본생성자 만들어 두라는 것.
class A{
A(){
this(3);
System.out.println("A 생성자1");
}
A(int a){
System.out.println("A 생성자2");
}
}
class B extends A{
B(){
this(3);
System.out.println("B 생성자1");
}
B(int a){
System.out.println("B 생성자2");
}
}
A aa1 = new A(); // A 생성자2 // A 생성자1
A aa2 = new A(2); // A 생성자2
B bb1 = new B(); // A 생성자2 // A 생성자1 // B 생성자2 // B 생성자1
B bb2 = new B(2); // A 생성자2 // A 생성자1 // B 생성자2
└this( );는 클래스 내의 다른 생성자 호출.
└3번 : B( )가서 this(3) 때문에 B(int a) 이동. 자동 삽입된 super( ) 때문에 부모 클래스 기본 생성자 이동(A( ))
A( )의 this(3) 때문에 A(int a)로 이동. 출력 A 생성자2 → A 생성자1 → B 생성자2 → B 생성자1