오버라이딩 & 오버로딩
p. 329
class A {
void print() {
System.out.println("A 클래스");
}
}
class B extends A {
void print() { //재정의
System.out.println("B 클래스");
}
}
클래스 A 내부에는 print() 메서드를 포함하고 있고,
클래스 A를 상속받은 클래스 B는 print() 메서드를 다시 재정의함으로써 오버라이딩 했다
A aa = new A();로 객체를 생성하고 print()메서드를 호출 했을 때 메모리에서 일어나는 일

힙 메모리에 A() 생성자로 객체가 생성되고
이를 A 타입으로 선언한 참조 변수 aa는 이 객체를 가리키고 있다.
A 객체 내에는 print() 메서드가 있지만, 객체 내의 메서드는 실제 메서드의 위치 정보만 저장 돼 있고
실제 print()는 메서드 영역에 정의돼 있다.
aa.print()를 실행하면 "A 클래스"가 출력 될 것이다.
B bb = new B();로 생성하고 print() 메서드를 호출 했을 때의 메모리

B() 생성자가 호출되면
A객체 내의 print()가 메서드 영역에 생성된다.
(부모클래스인 객체가 힙 메모리에 먼저 생성되므로)
이후 B객체가 생성되고 B객체의 print()가 메서드 영역에 저장되는데,
이미 A객체를 생성하는 과정에서 print() 메서드가 존재하고 있어서
이미 있는 A객체의 print()에 B객체의 print()를 덮어쓰기하는 것.
bb.print()를 실행하면 bb객체 내부의 print() 메서드가 가리키는 곳에 가서 print() 메서드가 실행되므로 "B 클래스" 가 출력된다.
덮어쓰기 : 이전 파일이 완전히 삭제되고, 새로운 파일로 바뀌는 것
오버라이딩 : 이전의 print() 메서드 위에 새로운 메서드가 올라(over) 타고(riding) 있는 것, 그래서 이전의 메서드 호출도 가능하다.
A ab = new B();로 객체 생성하고, print()메서드를 호출 했을 때의 메모리 구조
객체를 B() 생성자로 생성했지만 A타입으로 참조변수를 선언한 경우이다.

부모 클래스인 A객체가 만들어지고
B의 객체가 만들어진다.
이때 메서드 영역에는 print() 메서드의 오버라이딩이 발생한다
중요한 점은 A타입의 참조변수를 사용하고 있으므로 참조변수는 A객체를 가리키고 있다는 것.
class A {
void print() {
System.out.println("A 클래스");
}
}
class B extends A {
@Override
void print() { //재정의
System.out.println("B 클래스");
}
}
public class MethodOverriding_1 {
public static void main(String[] args) {
//A 타입, A 생성자
A aa = new A();
aa.print(); //A 클래스
//B 타입, B 생성자
B bb = new B();
bb.print(); //B 클래스
//A 타입, B 생성자
A ab = new B();
ab.print(); //B 클래스
}
}
부모 클래스 Animal
자식 클래스 Bird, Cat Dog 을 예시로 보면
class Animal {
void cry() {
}
}
class Bird extends Animal {
void cry() {
System.out.print("짹짹");
}
}
class Cat extends Animal {
void cry() {
System.out.print("야옹");
}
}
class Dog extends Animal {
void cry() {
System.out.print("멍멍");
}
}
이때 각각의 타입으로 선언하고 각각의 타입으로 객체를 생성하면 각각의 울음소리가 출력된다
Animal aa = new Animal();
Bird bb = new Bird();
Cat cc = new Cat();
Dog dd = new Dog();
aa.cry(); //출력 없음
bb.cry(); //짹짹
cc.cry(); //야옹
dd.cry(); //멍멍
다형적 표현을 사용해 부모 클래스 타입으로 선언하고 각각의 타입으로 객체를 생성하기
Animal ab = new Bird();
Animal ac = new Cat();
Animal ad = new Dog();
ab.cry(); //짹짹
ac.cry(); //야옹
ad.cry(); //멍멍
이때 참조변수 ab, ac, ad는 모두 Animal 타입이지만 각각 서로 다른 메서드로 오버라이딩 됐으므로 각각의 cry() 메서드는 서로 다른 출력 결과를 보이는 것
이것이 Animal 클래스 내부에 아무 기능을 하지 않는 cry() 메서드가 있는 이유
(단순히 자식 클래스에서 호출하기 위해 작성하는 메서드는 굳이 메서드의 원형을 지켜서 작성할 필요가 없기 때문에 생성된 문법이 '추상 메서드(abstract method)'이다.)
이렇게 모든 객체를 부모 타입 하나로 선언하면 다음처럼 배열로 한번에 관리 할 수 있다는 장점이 있다.
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");
}
void print2() {
System.out.println("A 클래스 print2");
}
}
class B extends A {
void print1() { //오버라이딩
System.out.println("B 클래스 print1");
}
void print2(int a) { //오버로딩
System.out.println("B 클래스 print2");
}
}
이때 클래스 B에서는 3개의 메서드를 사용할 수 있다.
print1()은 상속받은 메서드, 리턴 타입, 시그너처가 완벽하게 동일하므로 오버라이딩 된다.
print2()은 A에게 상속받은 입력매개변수가 없는 메서드와 클래스 B에서 추가로 정의한 정숫값을 1개받는 메서드 이므로 메서드 시그너처가 다르다.
즉 print2() 메서드는 오버로딩 된다.