07-1 상속
⭐클래스간의 관계가 생명인 객체지향 프로그래밍에서 굉장히 중요한 부분!!⭐
▶ 상속이란❓
부모 클래스의 필드, 메소드를 자식 클래스에서 사용할 수 있게 하는 것
→ 기존 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것
▶ 상속의 장점
- 코드 재사용
- 부모 클래스를 수정하면 자식 클래스의 해당 부분도 일괄 수정되므로 유지 보수에 좋음
▶ 상속의 방법
자식이 부모를 선택함 -> 클래스 선언문 뒤에 extends 작성
▶ 상속의 특징
여러 부모를 상속할 수 없음
부모 클래스의 private 멤버는 상속 불가, 다른 패키지라면 default 멤버도 상속 불가
protected와 public 멤버만 상속 가능
cf. 정확히 말하자면 private과 default는 상속은 받되 접근할 수는 없는 것이지만, 그냥 상속 불가라고 편하게 생각하자!
▶ 상속의 비밀
상속을 한다는 것은 부모 멤버가 자식 객체의 고유 멤버로 생성된다는 것이 아니다.
내부적으로 부모 생성자를 호출하여 부모 객체를 생성하고, 그 객체의 멤버를 참조하는 것이다.
Ex. BMW가 Car을 상속한다는 것은 내부적으로 Car 객체를 만들고 BMW가 그 객체를 참조한다는 뜻
이처럼 부모 객체를 '참조'하기 때문에 부모의 수정이 자식에게 반영될 수 있다.
(물이 위에서 아래로 흐르는 뭐 그런 용어 있었는데;;)
▶ 부모 생성자 호출
이는 자동으로든, 수동으로든, 자식 생성자의 첫 줄에서 부모 생성자 호출이 일어나기 때문에 가능하다.
자식 생성자 맨 첫줄에 부모 생성자 호출이 명시적으로 선언되어있지 않다면,
컴파일러가 자동으로 부모의 '기본 생성자'를 호출한다. Ex. super( ){ };
부모 생성자는 자식 생성자 맨 첫줄에서 super( ... )로 호출이 가능하다. (맨 첫줄이 아니면 컴파일 에러 발생)
=> 부모 생성자를 호출하지 않는 자식은 없다.
❗ 실수 주의 ❗
당연한 이야기지만, super( )은 부모 생성자의 매개변수 목록을 따라야 한다.
예를들어 Person(String name)을 Yonso가 상속받고 있을 때,
Yonso생성자에서 super(String)생성자 호출을 생략할 경우 기본 생성자인 super( )이 호출되는데,
Person는 기본 생성자가 없으므로 컴파일 오류가 발생한다.
▶ 메소드 오버라이딩 (Over riding)
메소드 오버라이딩이란? 부모에게 상속받은 메소드를 자식이 재정의 하는 것
메소드 오버라이딩을 하면, 부모 메소드가 아닌 재정의된 자식 메소드가 호출된다.
부모의 메소드를 호출하려면 super.메소드( ); 를 하면 된다.
(복습) - 부모 생성자 : super( ) / 본인 생성자 : this( )
규칙
- 부모 메소드와 메소드 선언부가 동일해야 함 (리턴타입, 메소드 이름, 매개변수 목록)
이때 @Override 어노테이션을 사용하면 선언부 오타를 쉽게 잡아낼 수 있다.
- 접근 제한을 강화할 수 없음 : 부모 public -> 자식 private 불가능
- 새로운 예외를 throw할 수 없음 (왜용?🤔)
▶ 예제
아래 예제에서 Calculator 클래스를 Computer가 상속받고 있으며, 더 정확한 원의 넓이를 구하기 위해
calArea( ) 함수를 Computer에서 재정의하고 있다.
class Calculator {
Calculator(){
System.out.println("부모 생성자 실행");
}
double calArea(int r) {
System.out.println("Calculator 클래스의 calArea() 실행");
return 3.14*r*r;
}
}
class Computer extends Calculator{
Computer(){
System.out.println("자식 생성자 실행");
}
@Override // 생략해도 되지만, 해두면 컴파일러가 오버라이딩 오류 잡아줌
double calArea(int r) {
System.out.println("Computer 클래스의 calArea() 실행");
return Math.PI*r*r;
}
}
public class ComputerExample {
public static void main(String[] args) {
Computer com = new Computer();
System.out.println(com.calArea(3));
}
}
부모 생성자 실행
자식 생성자 실행
Computer 클래스의 calArea() 실행
28.274333882308138
결과를 통해 '자식 생성자의 첫 줄에서 부모 생성자가 호출된다는 것'과
'오버라이드 하면, 부모의 메소드는 가려지고 자식의 메소드가 호출된다는 것'을 확인할 수 있다.
▶ final 키워드
(복습) - final이 붙은 필드는 초기화 후 값을 변경할 수 없음
final 필드 초기화 방법 : ① 선언과 동시에 초기화 ② 선언만 하고 생성자에서 초기화
클래스와 메소드에 final이 붙으면 상속과 관련되어 의미가 달라짐
final이 붙은 클래스 - 최종적인 클래스이므로 자식 클래스를 만들 수 없음
final이 붙은 메소드 - 최종적인 메소드이므로 자식 클래스에서 overriding 할 수 없음
🐈 실수 노트 🐈
cf. 상속을 예제로 연습하다가 이론상 맞는 코드를 작성했음에도 오류가 발생함.
→ 저장을 안해서 그런거였음 저장을 해야 수정된 내용이 상속된 클래스에 반영됨
Q. static 메소드 상속의 오류
결론부터 말하자면, static 메소드는 override 될 수 없다.
static 멤버는 메모리를 고정적으로 할당받아 프로그램이 끝날때까지 유지하여 사용한다.
따라서 static 멤버는 클래스에 대한 정보가 JVM 메모리에 올라갈 때
즉, 메소드 영역에 클래스에 대한 정보가 저장되는 시점인 Compiletime에 메모리를 할당받는다.
반면 인스턴스 멤버는 '객체를 만든 이후에'야 접근하여 사용할 수 있으므로,
힙영역에 객체가 생성되는 시점인 Runtime에 메모리를 할당받는다.
따라서 [static - Method Area - Compiletime], [instatnce - Heap Area - Runtime]
즉 static 멤버와 instance 멤버는 메모리에 올라가는 영역이 다르고, 저장되는 시간 또한 다르다.
① 부모의 static 메소드를 자식에서 instance 메소드로 Overriding 하는 경우를 생각해보자.
애초에 Overriding이란게 부모의 메소드를 '재정의'한다는 것인데,
이름이 같더라도 저장되는 장소도, 시간도 다른 두 메소드는 서로 다른 별개의 메소드이므로 이는 Overriding이라 보기 어렵다.
② 부모의 static 메소드를 자식에서 static 메소드로 Overriding 하는 경우를 생각해보자.
이클립스에서 아래 코드를 실행하면, 부모의 static 메소드와 같은 이름의 static 메소드를
자식 클래스에서 정의해도 오류가 발생하지 않는 것을 볼 수 있다.
이것만 보면 '부모의 static도 Overriding 가능한데요?'라 할 수 있겠지만,
이는 엄밀히 Overriding이 아니라 Hiding이다.
Overriding은 부모 메소드의 존재를 아예 지워버리고 새로운 함수를 덮어씌운 것이라고 하면,
가려진 상태는 존재가 지워지거나 삭제된 것이 아니기 때문에
해당 메소드의 호출 환경에 따라서 2개의 메소드를 모두 사용할 수 있다.
public class Main{
public static void main(String []args){
// 부모를 참조하는 부모 객체
SuperClass superClassWithSuperCons = new SuperClass();
// 자식을 참조하는 부모 객체
SuperClass superClassWithSubCons = new SubClass();
// 자식을 참조하는 자식 객체
SubClass subClassWithSubCons = new SubClass();
superClassWithSuperCons.staticMethod();
superClassWithSubCons.staticMethod();
subClassWithSubCons.staticMethod();
}
}
class SuperClass{
public static void staticMethod(){
System.out.println("부모의 static 메소드 실행");
}
}
class SubClass extends SuperClass{
//overriding the static method
public static void staticMethod(){
System.out.println("자식의 static 메소드 실행");
}
}
부모의 static 실행
부모의 static 실행
자식의 static 실행
일반적으로 자식을 참조하는 부모 객체에서는 오버라이딩한 자식 메소드가 호출되어야 한다.
하지만 두번째 출력 결과를 보면 부모의 메소드가 호출되었음을 알 수 있다.
이를 통해 ① 부모의 static 메소드 재정의는 Overriding이 아니다
② static 메소드는 컴파일 타입의 정보(클래스에 대한 것)만 사용한다 라는 사실을 알 수 있다.
결론 : static 메소드는 여느 Overriding 메소드와는 메모리에 저장되는 영역, 시간이 다르기 때문에
static을 Overriding 하는 것은 `이름만 같고 전혀 다른 함수를 Overriding한다`는 경우처럼 말이 되지 않는다.
같은 이름의 static 메소드를 static으로 정의할 수 있긴 하지만, 이는 Overriding이 아니라 Hiding이다.
이처럼 static 메소드는 상속에 있어서 'Overriding 되지 않는다'라는 특징을 가지고 있으므로
static을 오버라이드하는 오류를 피하기 위해 @Override 키워드를 꼭 써야 하고,
인스턴스 필드를 사용하지 않는 메소드임에도 자식에서 Override할 메소드는 static을 쓰면 안된다.
참고 : https://jinyoungchoi95.tistory.com/16
https://micropilot.tistory.com/3050
https://velog.io/@cchloe2311/Java-static-method-%EC%83%81%EC%86%8D
https://wedul.site/457
https://blog.naver.com/gngh0101/221206214829
https://docs.oracle.com/javase/tutorial/java/IandI/override.html
'JAVA > 혼공자' 카테고리의 다른 글
[혼공자] Chapter 07-3 추상 클래스 (1) | 2022.08.03 |
---|---|
[혼공자] Chapter 07-2 타입 변환과 다형성 (1) | 2022.08.02 |
[혼공자] Chapter 06-4~6 메소드, 인스턴스 멤버, 패키지와 접근 제한자 (2) | 2022.07.30 |
[혼공자] Chapter 06-1~3 객체 지향 프로그래밍, 생성자 (1) | 2022.07.27 |
[혼공자] Chapter 05-3 열거 타입 (0) | 2022.07.26 |