일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 자바의 정석 기초편 ch11
- 자바 기본편 - 다형성
- @Aspect
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch13
- jpa - 객체지향 쿼리 언어
- 자바 중급1편 - 예외 처리
- 스프링 mvc1 - 서블릿
- 스프링 mvc2 - 검증
- 게시글 목록 api
- 자바의 정석 기초편 ch12
- 스프링 mvc2 - 타임리프
- jpa 활용2 - api 개발 고급
- 스프링 db2 - 데이터 접근 기술
- 스프링 mvc1 - 스프링 mvc
- 스프링 mvc2 - 로그인 처리
- 스프링 고급 - 스프링 aop
- 코드로 시작하는 자바 첫걸음
- 자바 중급1편 - 날짜와 시간
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch2
- 스프링 db1 - 스프링과 문제 해결
- 자바의 정석 기초편 ch1
- 스프링 입문(무료)
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch4
- Today
- Total
나구리의 개발공부기록
상속, 상속 관계, 상속과 메모리 구조, 상속과 기능 추가, 상속과 메서드 오버라이딩, 상속과 접근 제어, super(부모 참조, 생성자) 본문
상속, 상속 관계, 상속과 메모리 구조, 상속과 기능 추가, 상속과 메서드 오버라이딩, 상속과 접근 제어, super(부모 참조, 생성자)
소소한나구리 2025. 1. 12. 15:29출처 : 인프런 - 김영한의 실전 자바 - 기본편 (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 상속 - 시작
1) 예제 코드
(1) 패키지 위치에 주의하여 작성
package extends1.ex1;
public class ElectricCar {
public void move() {
System.out.println("차를 이동합니다.");
}
public void charge() {
System.out.println("충전합니다.");
}
}
package extends1.ex1;
public class GasCar {
public void move() {
System.out.println("차를 이동합니다.");
}
public void fillUp() {
System.out.println("기름을 주유합니다.");
}
}
package extends1.ex1;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
GasCar gasCar = new GasCar();
gasCar.move();
gasCar.fillUp();
}
}
/* 실행 결과
차를 이동합니다.
충전합니다.
차를 이동합니다.
기름을 주유합니다.
*/
(2) 예제 설명
- 전기차(ElectricCar)와 가솔린차(GasCar)를 생성
- 전기차는 이동과 충전기능이 있고 가솔린차는 이동과 주유 기능이 있음
- 전기차와 가솔린차는 자동차(Car)의 좀 더 구체적인 개념이고 반대로 자동차(Car)는 전기차와 가솔린차를 포함하는 추상적인 개념이기 때문에 전기차와 가솔린차의 기능을 보면 이동이라는 공통 기능이 있음
- 전기차든 가솔린차는 주유하는 방식이 다른 것이지 이동하는 것은 똑같은데 이런 경우 상속관계를 사용하는 것이 효과적임
2. 상속 관계
1) 상속 관계
(1) 용어 정리
- 상속은 객체 지향 프로그래밍의 핵심 요소 중 하나로 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용하게 해줌
- 이름 그대로 기존 클래스의 속성과 기능을 그대로 물려받는 것이며 상속을 사용하려면 extends 키워드를 사용하면되며 extends의 대상은 하나만 선택할 수 있음
- 부모 클래스(슈퍼 클래스): 상속을 통해 자신의 필드와 메서드를 다른 클래스에 제공하는 클래스
- 자식 클래스(서브 클래스): 부모 클래스로부터 필드와 메서드를 상속받는 클래스
2) 상속 관계 코드
(1) Car - 수정
- 자동차의 공통 기능인 move()가 포함되어있음
package extends1.ex2;
public class Car {
public void move() {
System.out.println("차를 이동 합니다");
}
}
(2) ElectricCar, GasCar - 수정
- 전기차, 가스차는 extends Car를 사용하여 부모 클래스인 Car를 상속받음
- 상속을한 덕분에 전기차, 가스차에서도 부모인 Car의 메서드 move()를 사용할 수 있게 됨
package extends1.ex2;
public class ElectricCar extends Car {
public void charge() {
System.out.println("충전합니다.");
}
}
package extends1.ex2;
public class GasCar extends Car {
public void fillUp() {
System.out.println("기름을 주유합니다.");
}
}
(3) CarMain 실행
- CarMain의 코드 수정없이 동일한 코드로 패키지만 ex2로 바꿔서 실행해보면 서브 클래스에서 슈퍼 클래스의 메서드를 문제없이 사용할 수 있는 것을 확인할 수 있음
(4) 상속 구조도
- 전기차와 가솔린차가 Car를 상속받은 덕분에 electricCar.move(), gasCar.move()를 사용할 수 있게됨
- 상속은 부모의 기능을 자식이 물려 받는 것이므로 자식이 부모의 기능을 물려 받아서 사용할 수 있음
- 반대로 부모 클래스는 자식 클래스에 접근할 수 없음
- 즉 자식 클래스는 extends Car를 통해서 부모 클래스의 기능을 물려 받기 때문에 접근할 수 있지만 부모 클래스는 자식에 대한 정보가 하나도 없기 때문에 누가 자신을 상속했는지 알 수 없으므로 접근할 수 없음
(5) 단일 상속
- 자바는 다중 상속을 지원하지 않기때문에 extends 대상은 하나만 선택할 수 있음
- 즉, 부모를 하나만 선택할 수 있다는 뜻이며 부모가 또 다른 부모를 하나 가지는 것은 괜찮음
- 만약 비행기와 자동차를 상속 받아서 하늘을 나는 자동차를 만든다고 가정했을 때 만약 그림과 같이 다중 상속을 사용하게 되면 AirplaneCar 입장에서 move()를 호출할 때 어떤 부모의 move()를 사용해야할지 애매한 문제가 발생하게 되는데 이런 문제를 다이아몬드 문제라고 함
- 그리고 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해질 수 있기 때문에 자바는 클래스의 다중 상속을 허용하지 않는 대신 이후에 설명할 인터페이스의 다중 구현을 통해서 이런 문제를 해결함
** 이부분을 제대로 이해하는 것이 앞으로 정말 중요함
3. 상속과 메모리 구조
1) 상속과 메모리 구조
(1) Car를 상속받은 new ElectricCar() 호출
ElectricCar electricCar = new ElectricCar();
- new ElectricCar()를 호출하면 ElectricCar 뿐만 아니라 상속 관계에 있는 Car까지 함께 포함해서 인스턴스를 생성함
- 참조값은 x001(예시)로 하나이지만 실제로 그 안에서는 Car, ElectricCar라는 두가지 클래스 정보가 공존하는 것임
- 즉, 부모의 필드와 메서드만 물려 받는게 아니라 부모 클래스도 함께 포함해서 생성되며 외부에서 볼때는 하나의 인스턴스를 생성하는 것 같지만 내부에서는 부모와 자식이 모두 생성되고 공간도 구분됨
(2) electricCar.charge() 호출
- electricCar.charge()를 호출하면 참조값을 확인하여 x001.charge()를 호출하는데 상속관계의 경우에는 내부에 부모와 자식이 모두 존재하기 때문에 Car를 통해서 charge()를 찾을지 ElectricCar를 통해서 charge()를 찾을지 선택해야함
- 이때 호출하는 변수의 타입(클래스)을 기준으로 선택함
- electricCar 변수의 타입이 ElectricCar이므로 인스턴스 내부에 같은 타입인 ElectricCar를 통해서 charge()를 호출함
(3) electricCar.move() 호출
- electricCar.move()를 호출하면 먼저 x001 참조로 이동하고 마찬가지로 내부에 Car, ElectricCar 두가지 타입이 있어 호출하는 변수의 타입이 ElectricCar이므로 이 타입을 선택함
- 그러나 ElectricCar에는 move() 메서드가 없는데, 상속 관계에서는 자식 타입에 해당 기능이 없으면 부모 타입으로 올라가서 찾음
- ElectricCar의 부모인 Car로 올라가서 move()를 찾고 부모에 move() 메서드가 있으면 호출하고 만약 부모에서도 해당 기능을 찾지 못하면 더 상위 부모에서 필요한 기능을 찾아봄
- 부모에 부모로 계속 올라가면서 필드나 메서드를 찾고 계속 찾아도 없으면 컴파일 오류가 발생함
(4) 지금까지 설명한 상속과 메모리 구조는 반드시 이해해야함
- 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성됨
- 상속 관계의 객체를 호출할 때 대상 타입을 정해야 하며 이때 호출자의 타입을 통해 대상 타입을 찾음
- 현재 타입에서 기능을 찾지 못하면 상위 부모 타입으로 기능을 찾아서 실행하며 기능을 찾지 못하면 컴파일 오류가 발생함
4. 상속과 기능 추가
1) 예제 기능 추가
(1) 추가 할 기능들
- 모든 차량에 문열기(openDoor()) 기능을 추가
- 새로운 수소차(HydrogenCar)를 추가하고 fillHydrogen() 기능을 통해 수소를 충전할 수 있음
(2) Car - 수정
- openDoor() 메서드를 추가
package extends1.ex3;
public class Car {
public void move() {
System.out.println("차를 이동 합니다");
}
public void openDoor() {
System.out.println("문을 엽니다");
}
}
(3) HydrogenCar - 추가
- Car를 상속 받은 덕분에 move(), openDoor()의 기능을 바로 사용할 수 있음
- 수소차 전용 기능인 fillHydrogen() 메서드를 가지고 있음
package extends1.ex3;
public class HydrogenCar extends Car {
public void fillHydrogen() {
System.out.println("수소를 충전합니다.");
}
}
(4) CarMain - 수정
- 새로 작성한 Car의 기능과 HydrogenCar의 기능이 모두 정상적으로 동작하는 것을 확인할 수 있음
package extends1.ex3;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
electricCar.openDoor();
GasCar gasCar = new GasCar();
gasCar.move();
gasCar.fillUp();
gasCar.openDoor();
HydrogenCar hydrogenCar = new HydrogenCar();
hydrogenCar.move();
hydrogenCar.fillHydrogen();
hydrogenCar.openDoor();
}
}
/* 실행 결과
차를 이동 합니다
충전합니다.
문을 엽니다
차를 이동 합니다
기름을 주유합니다.
문을 엽니다
차를 이동 합니다
수소를 충전합니다.
문을 엽니다
*/
(5) 기능 추가와 클래스 확장
- 여기에서 중요한 점은 상속 관계 덕분에 코드 중복은 줄어들고 새로운 수소차를 편리하게 확장(extend)한 것을 알 수 있음
5. 상속과 메서드 오버라이딩
1) 기능 재정의
(1) 예시
- 부모 타입의 기능을 자식에서는 다르게 재정의 하고 싶을 수 있음
- 예를 들어 자동차의 경우 Car.move()라는 기능을 사용하면 단순히 "차를 이동합니다"라고 출력하는데, 전기차에서는 "전기차를 빠르게 이동합니다"라고 출력을 변경하고 싶을 수 있음
- 이렇게 부모에게서 상속 받은 기능을 자식이 재정의 하는 것을 메서드 오버라이딩(Overriding)이라 함
(2) ElectricCar 수정
- ElectricCar는 부모인 Car의 move()기능을 그대로 사용하는 것이 아니라 메서드 이름은 같지만 기능을 새로 정의하였음
- 이렇게 부모의 기능을 자식이 새로 재정의 하는 것이 오버라이딩이며 ElectricCar()의 move()를 호출하게 되면 Car의 move()가 아니라 ElectricCar의 move()가 호출됨
package extends1.overriding;
public class ElectricCar extends Car {
@Override
public void move() {
System.out.println("전기차를 빠르게 이동합니다.");
}
public void charge() {
System.out.println("충전합니다.");
}
}
(3) @Override
- @이 붙은 부분을 애노테이션이라 하며 애노테이션은 프로그램이 읽을 수 있는 특별한 주석으로 이와 관련된 내용은 따로 설명함
- 해당 애노테이션은 상위 클래스의 메서드를 오버라이드한 것임을 나타내며 이름 그대로 오버라이딩한 메서드 위에 이 애노테이션을 붙여야 함
- 컴파일러는 이 애노테이션을 보고 메서드가 정확히 오버라이드 되었는지 확인하고 오버라이딩 조건을 만족시키지 않으면 컴파일 에러를 발생시키기 때문에 실수로 오버라이딩을 못하는 경우를 방지해줌 즉, 부모에 오버라이딩할 메서드가 없다면 컴파일 오류가 발생됨
- 이 기능은 필수는 아니지만 코드의 명확성을 위해서 붙여주는 것이 좋음(사실 관례같은건 무조건 한다 라고 생각하면됨)
(4) CarMain - 수정
- 실행 결과를 보면 electricCar.move()를 호출했을 때 오버라이딩한 ElectricCar에 정의한 move() 메서드가 실행된 것을 확인할 수 있음
package extends1.overriding;
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
GasCar gasCar = new GasCar();
gasCar.move();
}
}
/* 실행 결과
전기차를 빠르게 이동합니다.
차를 이동 합니다
*/
(5) 오버라이딩과 클래스 및 메모리 구조
- 클래스 구조: Car의 move() 메서드를 ElectricCar에서 오버라이딩
- 메모리 구조
1. electricCar.move()를 호출
2. 호출한 electricCar의 타입은 ElectricCar이므로 인스턴스 내부의 ElectricCar 타입에서 시작함
3. ElectricCar 타입에 move() 메서드가 있으므로 해당 메서드를 실행하며 실행할 메서드를 이미 찾았으므로 부모 타입을 찾지 않음
2) 오버로딩(Overloading)과 오버라이딩(Overriding)
(1) 메서드 오버로딩
- 메서드 이름이 같고 매개변수(파라미터)가 다른 메서드를 여러개 정의하는 것
- 오버로딩을 번역하면 과적이라는 뜻인데 과하게 물건을 담았다는 뜻으로 같은 이름의 메서드를 여러개 정의했다고 이해하면 됨
(2) 메서드 오버라이딩
- 하위 클래스에서 상위 클래스의 메서드를 재정의 하는 과정을 말하므로 상속 관계에서 사용함
- 부모의 기능을 자식이 다시 정의하는 것으로 오버라이딩을 단순히 해석하면 무언가를 넘어서 타는 것을 말하는데 자식의 새로운 기능이 부모의 기존 기능을 넘어서 기존 기능을 새로운 기능으로 덮어버린다고 이해하면 됨
- 오버라이딩을 번역하면 무언가를 다시 정의한다고하여 재정의라 하는데 상속 관계에서는 기존 기능을 다시 정의한다고 이해하면됨
3) 메서드 오버라이딩 조건
- 메서드 오버라이딩은 까다로운 조건을 가지고 있는데 지금은 단순히 부모 메서드와 같은 메서드를 오버라이딩 할 수 있다 정도로 이해하면 충분함
(1) 메서드 이름
- 메서드 이름이 같아야함
(2) 메서드 매개변수(파라미터)
- 매개변수(파라미터) 타입, 순서 개수가 같아야함
(3) 반환 타입
- 반환 타입이 같아야 하며 반환 타입이 하위 클래스 타입일 수 있음
(4) 접근 제어자
- 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안됨
- 예를 들어 상위 클래스의 메서드가 protected로 선언되어있으면 하위 클래스에서 이를 public 또는 protected로 오버라이드 할 순있지만 private 또는 default로 오버라이드 할 수 없음
(5) 예외
- 오버라이딩 메서드는 상위 클래스의 메서드보다 더 많은 체크 예외를 throws로 선언할 수 없고 더 적거나 같은 수의 예외 또는 하위 타입의 예외는 선언할 수 있음
- 이는 예외를 학습해야 이해할 수 있음
(6) static, final, private
- 해당 키워드가 붙은 메서드는 오버라이딩이 될 수 없음
- static은 클래스 레벨에서 작동하므로 클래스 이름을 통해 필요한 곳에 직접 접근하면 되기 때문에 인스턴스 레벨에서 사용하는 오버라이딩이 의미가 없음
- final 메서드는 재정의를 금지함
- private 메서드는 해당 클래스에서만 접근 가능하기 때문에 하위 클래스에서 보이지않으므로 오버라이딩 할 수 없음
(7) 생성자 오버라이딩
- 생성자는 오버라이딩 할 수 없음
6. 상속과 접근 제어
1) 예제
(1) 패키지 분리
- 상속 관계와 접근 제어를 알아보기 위해서 부모와 자식의 패키지를 따로 분리
- 부모 패키지: parent
- 자식 패키지: child
(2) 접근 제어자를 표현하기 위한 UML 표기법을 일부 사용
- +: public
- #: protected
- ~: default
- -: private
(3) 접근제어자 복습
- private: 모든 외부 호출을 막음
- default: 같은 패키지안에서 호출은 허용
- protected: 같은 패키지 안에서 호출은 허용하고 패키지가 달라도 상속 관계의 호출은 허용
- public: 모든 외부 호출을 허용
- private -> default -> protected -> public 순으로 private이 가장 많이 차단하고 public이 가장 많이 허용함
(4) Parent
- 다양한 접근제어자의 필드 및 메서드가 존재하고 자기 자신의 필드를 호출하는 printParent() 메서드를 가지고 있음
package extends1.access.parent;
public class Parent {
public int publicValue;
protected int protectedValue;
int defaultValue;
private int privateValue;
public void publicMethod() {
System.out.println("Parent.publicMethod");
}
protected void protectedMethod() {
System.out.println("Parent.protectedMethod");
}
void defaultMethod() {
System.out.println("Parent.defaultMethod");
}
private void privateMethod() {
System.out.println("Parent.privateMethod");
}
public void printParent() {
System.out.println("==parent 메서드 안 ==");
System.out.println("publicValue: " + publicValue);
System.out.println("protectedValue: " + protectedValue);
System.out.println("defaultValue: " + defaultValue);
System.out.println("privateValue: " + privateValue);
// 부모 메서드 안에서 모두 접근 가능
defaultMethod();
privateMethod();
}
}
(2) Child
- 부모와 다른 패키지 위치에 자식 클래스를 생성
- 부모클래스의 필드와 메서드의 접근제어자가 public, protected인 경우에는 접근이 가능하지만 default, private인 경우에는 접근할 수 없는 것을 확인할 수 있음
- 서로 다른 패키지에 상속관계가 되어있어도 protected는 접근이 가능함
package extends1.access.child;
import extends1.access.parent.Parent;
public class Child extends Parent {
public void call() {
publicValue = 1;
protectedValue = 1; // 상속 관계 or 같은 패키지 접근 가능
// defaultValue = 1; // 다른 패키지 접근 불가, 컴파일 오류
// privateValue = 1; // 접근 불가, 컴파일 오류
publicMethod();
protectedMethod();
// defaultMethod();
// privateMethod();
printParent();
}
}
(3) ExtendsAccessMain
- Child를 생성하여 호출하는 코드
- 코드를 실행해보면 Child클래스의 call()메서드 Parent클래스의 printParent()메서드 순서로 호출되는 것을 확인할 수 있음
- Child는 부모의 public, protected필드나 메서드만 접근할 수 있는 반면 Parent안에 있는 printParent()의 메서드는 자신의 모든 필드와 메서드에 접근할 수 있으므로 모두 출력되는 것을 확인할 수 있음
package extends1.access;
import extends1.access.child.Child;
public class ExtendsAccessMain {
public static void main(String[] args) {
Child child = new Child();
child.call();
}
}
/* 실행 결과
Parent.publicMethod
Parent.protectedMethod
==parent 메서드 안 ==
publicValue: 1
protectedValue: 1
defaultValue: 0
privateValue: 0
Parent.defaultMethod
Parent.privateMethod
*/
(4) 접근 제어와 메모리 구조
- 본인 타입에 없으면 부모 타입에서 기능을 찾는데 이때 접근 제어자가 영향을 줌
- 외부에서는 하나로 보이겠지만 객체 내부에서는 자식과 부모가 구분되어있기 때문에 자식 타입에서 부모 타입의 기능을 호출할 때 부모 입장에서 보면 외부에서 호출한것과 동일하기 때문임
- 즉, 본인 타입에서 찾고 없으면 부모타입에서 찾으려고하는데 접근제어자 때문에 접근을 못하는 메커니즘임
7. super
1) 부모 참조
(1) 설명
- 부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어 있으면 자식에서 부모의 필드나 메서드를 호출할 수 없음
- 이때 super 키워드를 사용하면 부모를 참조할 수 있는데 super는 이름 그대로 부모 클래스에 대한 참조를 나타냄
(2) Parent
package extends1.super1;
public class Parent {
public String value = "parent";
public void hello() {
System.out.println("Parent.hello");
}
}
(3) Child
- this는 자기 자신의 참조를 뜻하므로 생략할 수 있음
- super는 부모 클래스에 대한 참조를 뜻함
- 필드 이름과 메서드 이름이 같지만 super를 사용하여 부모 클래스에 있는 기능을 사용할 수 있음
package extends1.super1;
public class Child extends Parent {
public String value = "child";
@Override
public void hello() {
System.out.println("Child.hello");
}
public void call() {
System.out.println("this value = " + this.value); // this 생략 가능
System.out.println("super value = " + super.value);
this.hello(); // this 생략 가능
super.hello();
}
}
(4) Super1Main
- 실행 결과를 보면 super를 사용한 경우 부모 클래스의 필드와 메서드에 접근한 것을 확인할 수 있음
package extends1.super1;
public class Super1Main {
public static void main(String[] args) {
Child child = new Child();
child.call();
}
}
/* 실행 결과
this value = child
super value = parent
Child.hello
Parent.hello
*/
(5) super 메모리 그림
2) 생성자
(1) 설명
- 상속 관계의 인스턴스를 생성하면 결국 메모리 내부에는 자식과 부모 클래스가 각각 다 만들어지므로 Child를 만들면 부모인 Parent까지 함께 만들어 지고 각각의 생성자도 모두 호출되어야 함
- 상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야하는 규칙이 있음
- 상속 관계에서 부모의 생성자를 호출할 때는 super(...)를 사용하면 됨
(2) ClassA
- 최상위 부모 클래스
package extends1.super2;
public class ClassA {
public ClassA() {
System.out.println("ClassA 생성자");
}
}
(3) ClassB
- ClassB는 ClassA를 상속 받았으므로 생성자의 첫줄에 super(...)를 사용하여 부모클래스의 생성자를 호출해야함
- 그러나 부모 클래스의 생성자가 기본 생성자(파라미터가 없는 생성자)인 경우에는 super()를 생략할 수 있음
- 상속 관계에서 첫줄에 super(...)를 생략하면 자바가 부모의 기본 생성자를 호출하는 super를 자동으로 만들어 주는데 기본 생성자는 많이 사용하기 때문에 편의상 이런 기능을 제공함
- 예외로 생성자 첫줄에 this(...)를 사용할 수는 있지만 자식의 생성자 안에서 언젠가는 반드시 super(...)를 호출해야함
package extends1.super2;
public class ClassB extends ClassA {
public ClassB(int a) {
super(); // 기본 생성자 생략 가능
System.out.println("ClassB 생성자 a= " + a);
}
public ClassB(int a, int b) {
super(); // 기본 생성자 생략 가능
System.out.println("ClassB 생성자 a= " + a + ", b= " + b);
}
}
(4) ClassC
- ClassC는 ClassB(int a), ClassB(int a, int b) 생성자를 가지고 있는 ClassB를 상속받음
- super()는 생성자의 첫줄에 입력할 수 있으므로 ClassB의 두 생성자 중에 하나를 선택하여 호출하면 됨(인수에 값을 입력하여 선택)
- ClassB에는 직접 생성자를 정의했으므로 자바가 기본생성자를 만들어주지 않기때문에 ClassC에서 부모의 기본 생성자를 호출하는 super()를 사용하거나 생략할 수 없음
package extends1.super2;
public class ClassC extends ClassB {
public ClassC() {
super(10, 20);
System.out.println("ClassC 생성자");
}
}
(5) Super2Main
- ClassC를 생성후 실행해보면 ClassA -> ClassB -> ClassC 순서로 실행됨
- 생성자의 실행 순서가 결과적으로 최상위 부모부터 실행되어서 하나씩 아래로 내려오는 구조로 실행되는데 자식 생성자의 첫 줄에서 부모의 생성자를 호출해야 하기 때문에 초기화가 최상위 부모부터 이루어짐
package extends1.super2;
public class Super2Main {
public static void main(String[] args) {
ClassC classC = new ClassC();
}
}
/*
ClassA 생성자
ClassB 생성자 a= 10, b= 20
ClassC 생성자
*/
(6) 초기화 과정
- 1 ~ 3 까지의 과정을 보면 new ClassC()를 통해 인스턴스를 생성하며 이때 ClassC()의 생성자가 먼저 호출되는 것은 맞음
- 그러나 ClassC()의 생성자는 가장 먼저 super(...)를 통해 ClassB의 생성자를 호출하게 되고 ClassB()의 생성자도 부모인 ClassA()의 생성자를 가장 먼저 호출함
- 4 ~ 6 까지의 과정을 보면 ClassA()의 생성자는 최상위 부모로 생성자 코드를 실행하면서 "ClassA 생성자"를 출력하고 ClassA() 생성자 호출이 끝나면 ClassA()를 호출한 ClassB(...) 생성자로 제어권이 돌아감
- ClassB(...) 생성자가 코드를 실행하면서 "ClassB 생성자 a=10 b=20"를 출력하고 생성자 호출이 끝나면 ClassB()를 호출한 ClassC()의 생성자로 제어권이 돌아감
- ClassC()가 마지막으로 생성자 코드를 실행하면서 "ClassC 생성자"를 출력함
(7) 정리
- 상속 관계의 생성자 호출은 결과적으로 부모에서 자식 순서로 실행므로 부모의 데이터를 먼저 초기화하고 그 다음에 자식의 데이터를 초기화함
- 상속 관계에서 자식 클래스의 생성자 첫줄에 반드시 super(...)를 호출해야 함 단, 기본 생성자(super())인 경우 생략할 수 있음
(8) this(...)와 함께 사용
- 코드의 첫줄에 this(...)를 사용하더라도 반드시 한번은 super(...)를 호출해야함
- ClassB의 매개변수가 하나있는 생성자에 this(a, 0)를 사용하여 자기 자신의 매개변수가 있는 다른 생성자를 호출할 수 있는데 결국에는 마지막에 호출되는 생성자에서 super(...)를 호출해야 함
- 만약 마지막 생성자에서도 super(...)를 하지 않으면 컴파일 오류가 발생함(자바가 기본으로 생성해주는 super()는 제외)
package extends1.super2;
public class ClassB extends ClassA {
public ClassB(int a) {
this(a, 0);
System.out.println("ClassB 생성자 a= " + a);
}
public ClassB(int a, int b) {
super(); // 기본 생성자 생략 가능
System.out.println("ClassB 생성자 a= " + a + ", b= " + b);
}
}
package extends1.super2;
public class ClassC extends ClassB {
public ClassC() {
super(10, 20);
System.out.println("ClassC 생성자");
}
}
/*실행 결과
ClassA 생성자
ClassB 생성자 a= 100, b= 0
ClassB 생성자 a= 100
*/
8. 문제와 풀이
1) 상속 관계 상품
(1) 요구사항
- 쇼핑몰의 판매 상품을 작성하는 문제
- Book, Album, Movie 3가지 상품을 클래스로 생성
- 코드 중복이 없게 상속 관계를 사용하고 부모 클래스는 Item이라는 이름을 사용해야 함
- 공통 속성: name, price
- Book: 저자(author), isbn(isbn)
- Album: 아티스트(artist)
- Movie: 감독(director), 배우(actor)
(2) 문제
- 다음 코드와 실행결과를 참고하여 Item, Book, Album, Movie 클래스를 생성
package extends1.ex;
public class ShopMain {
public static void main(String[] args) {
Book book = new Book("JAVA", 10000, "han", "12345");
Album album = new Album("앨범1", 15000, "seo");
Movie movie = new Movie("영화1", 18000, "감독1", "배우1");
book.print();
album.print();
movie.print();
int sum = book.getPrice() + album.getPrice() + movie.getPrice();
System.out.println("상품 가격의 합: " + sum);
}
}
실행 결과
이름:JAVA, 가격:10000
- 저자:han, isbn:12345
이름:앨범1, 가격:15000
- 아티스트:seo
이름:영화1, 가격:18000
- 감독:감독1, 배우:배우1
상품 가격의 합: 43000
(3) 정답
Item
package extends1.ex;
public class Item {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public void print() {
System.out.println("이름:"+ name + ", 가격:" + price);
}
}
Book
package extends1.ex;
public class Book extends Item {
private String author;
private String isbn;
public Book(String name, int price, String author, String isbn) {
super(name, price);
this.author = author;
this.isbn = isbn;
}
@Override
public void print() {
super.print();
System.out.println("- 저자:" + author + ", isbn:" + isbn);
}
}
Album
package extends1.ex;
public class Album extends Item {
private String artist;
public Album(String name, int price, String artist) {
super(name, price);
this.artist = artist;
}
@Override
public void print() {
super.print();
System.out.println("- 아티스트:" + artist);
}
}
Movie
package extends1.ex;
public class Movie extends Item {
private String director;
private String actor;
public Movie(String name, int price, String director, String actor) {
super(name , price);
this.director = director;
this.actor = actor;
}
@Override
public void print() {
super.print();
System.out.println("- 감독:" + director + ", 배우:" + actor);
}
}
2) 정리
(1) 클래스와 메서드에 사용되는 final
- 상속 불가
- final로 선언된 클래스는 확장 될 수 없으므로 다른 클래스가 final로 선언된 클래스를 상속받을 수 없음
(2) 메서드에 final
- 오버라이딩 불가
- final로 선언된 메서드는 오버라이드 될 수 없으므로 상속받은 서브 클래스에서 이 메서드를 변경할 수 없음
'인프런 - 실전 자바 로드맵 > 실전 자바 - 기본편' 카테고리의 다른 글
다형성, 다형성 활용, 추상 클래스, 인터페이스, 인터페이스 - 다중 구현, 클래스와 인터페이스 활용 (0) | 2025.01.14 |
---|---|
다형성, 다형성 시작, 다형성과 캐스팅, 캐스팅의 종류, 다운캐스팅과 주의점, instanceof, 다형성과 메서드 오버라이딩 (0) | 2025.01.12 |
final, final 변수와 상수, final 변수와 참조 (0) | 2025.01.11 |
자바 메모리 구조와 static, 자바 메모리 구조, 스택과 큐 자료 구조, 스택 영역, 스택 영역과 힙 영역, static 변수, static 메서드 (0) | 2025.01.08 |
접근제어자, 접근 제어자 이해, 접근 제어자 종류, 접근 제어자 사용(필드,메서드,클래스레벨) 캡슐화 (0) | 2025.01.04 |