상속
상속이란?
- 기존 클래스의 속성을 물려 받아, 새로운 클래스를 생성하는 것
extends
: java에서 상속을 위해 사용하는 키워드- 하나의 대상만 선택 가능. (단일상속만 지원)
- java는 다중상속을 지원하지 않음!
- 해결 방안 :
implements
키워드를 이용한 인터페이스의다중구현
으로 해결
- 하나의 대상만 선택 가능. (단일상속만 지원)
단일 상속 vs. 다중 상속
1) 단일 상속
2) 다중 상속
자바가 다중상속을 지원하지 않는 이유
-
다이아몬드 문제
발생 가능성 때문
1)GrandFather
클래스에memberMethod()
라는 이름의 메소드가 있다고 가정하자.
2)FatherA
와FatherB
가 각각memberMethod()
오버라이딩하여 구현했다.
3)FatherA
와FatherB
를 모두 상속받은 Son 클래스는 어떤 부모의memberMethod()
를 사용해야할지 애매함 -> 충돌 발생!
상속과 메모리 구조
1. ElectricCar electricCar = new ElectricCar();
new ElectricCar()
을 호출하면 ->ElectricCar
뿐만 아니라 상속 관계에 있는Car
이 함께 포함되어 인스턴스가 생성됨- 참조값은
x001
하나이지만, 실제로 그 안에는Car
과ElectricCar
두개의 클래스 정보가 공존!
=> 상속은 단순히 부모의 필드와 메서드만 물려 받는 것이 아님. 부모 클래스 자체를 함께 포함하여 객체를 생성!
💡 상속 관계의 객체를 생성하면, 인스턴스 내부에 부모와 자식이 모두 생성된다.
2. electricCar.charge()
electricCar.charge()
를 호출하면 -> 참조값을 확인하여x001.charge()
를 호출함.- 이때, 상속 관계의 경우
x001
내부에 부모와 자식이 모두 존재함. - 부모인
Car
을 통해서charge()
를 찾을지,ElectricCar
을 통해서charge()
를 찾을지 선택해야 함.- 호출하는 변수의 타입(클래스)를 기준으로 선택한다.
electricCar
변수의 타입이ElectricCar
클래스이므로,ElectricCar
을 통해서charge()
를 호출한다.
💡 상속 관계의 객체를 호출할 때, 호출자의 타입과 동일한 대상 타입을 찾는다.
3. electricCar.move()
elctricCar.move()
를 호출하면 ->x001
참조로 이동Car
,ElectricCar
두가지 타입이 있는데, 호출하는 변수인electricCar
의 타입인ElectricCar
선택- 그런데,
ElectricCar
에는move()
메소드가 존재하지 않음! - 상속 관계에서, 자식 타입에 해당 기능이 없으면 부모 타입으로 올라가서 찾는다.
ElectricCar
의 부모인Car
로 올라가서move()
를 찾는다.- 부모인
Car
에move()
가 존재하므로 부모에 있는move()
메서드를 호출한다.
💡 현재 타입에서 기능을 찾지 못하면, 부모 타입으로 이동하여 찾아서 실행한다.
찾지 못하면 최상위 부모까지 이동하게되고, 그곳에도 없다면 컴파일 오류가 발생한다.
상속과 기능 추가
상속 관계에 메소드와 클래스를 추가해야 한다.
- 모든 차량에 문열기(
openDoor()
) 기능을 추가했다. - 수소차(
HydrogenCar
) 추가되었다.- 수소차는
fillHydrogen()
기능을 통해 수소를 충전할 수 있다.
- 수소차는
1. Car
클래스에 openDoor()
메서드를 추가한다.
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
//추가
public void openDoor() {
System.out.println("문을 엽니다.");
}
}
- 모든 차량에 문열기 기능을 추가하고자 할 때, 상위 부모인
Car
에openDoor()
기능을 추가하면 된다.- 모든 차량이 Car 클래스를 상속하고 있기 때문에 가능하다.
2. HydrogenCar
클래스를 추가하고, fillHydrogen()
메서드를 추가한다.
public class HydrogenCar extends Car {
public void fillHydrogen() {
System.out.println("수소를 충전합니다.");
}
}
Car
클래스를 상속받았기 때문에move()
,openDoor()
기능을 바로 사용할 수 있다.
3. ElectricCar
클래스는 손대지 않아도 된다.
public class ElectricCar extends Car {
public void charge() {
System.out.println("충전합니다.");
}
}
Main 클래스
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(); //문을 엽니다.
}
}
💡 상속관계 덕분에 중복이 줄어들고, 새로운 클래스를 편리하게 확장할 수 있다.
메서드 오버라이딩과 메모리 구조
메서드 오버라이딩
- 부모에게서 상속받은 기능을 자식이 재정의하는 것
메모리 구조
Car
의move()
메서드를ElectricCar
에서 오버라이딩했다.
elctricCar.move()
를 호출한다.- 호출한
electricCar
의 타입은ElectricCar
이다. 따라서 인스턴스 내부의ElectricCar
타입에서 시작한다. ElectricCar
타입에move()
메서드가 존재하므로 해당 메서드를 실행한다. 실행할 메서드를 찾았으므로, 부모 타입을 탐색하지 않는다.
오버라이딩 vs. 오버로딩
메서드 오버라이딩(Overriding)
- 부모에게서 상속 받은 기능을 자식이 재정의 하는 것
오버라이딩 성립 조건
- 메서드 이름 : 메서드 이름이 일치해야 함
- 메서드 매개변수(파라미터) : 매개변수의 개수, 순서, 데이터 타입이 일치해야 함
- 반환 타입 : 메서드의 return 타입이 일치해야 함 (반환 타입이 하위 클래스 타입일 수 있음)
- 접근 제어자 : 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이면 안 됨.
- ex) 상위 클래스 메서드 :
protected
이면, 하위클래스에서public
,protected
로만 오버라이드 가능! (private
,default
로는 불가능)static
,final
,private
키워드가 붙은 메서드는 오버라이딩 될 수 없음!
static
은 클래스 레벨에서 작동한다. 따라서, 인스턴스 레벨에서 사용하는 오버라이딩이 의미가 없음.
- 그냥 클래스 이름을 통해 필요한 곳에 직접 접근하면 됨
final
메서드는 재정의를 금지함private
메서드는 해당 클래스에서만 접근 가능하기 때문에 오버라이딩 불가능- 생성자는 오버라이딩 할 수 었다.
메서드 오버로딩(Overloading)
- 메서드 이름이 같고, 매개변수(파라미터)가 다른 메서드를 여러개 정의하는 것 (같은 클래스 내부에서 메소드를 확장하기 위한 개념)
오버로딩 성립 조건
- 메소드 이름이 일치해야 함
- 메소드 매개변수의 개수 또는 타입이 서로 달라야 함
- 개수가 같다면 타입을 다르게, 타입이 같다면 개수를 다르게 해야 함(둘 중에 하나는 반드시 다르게!)
- 메소드의
return
타입이 서로 다라야 함
super - 부모 참조
- 부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어 있으면, 자식에서 부모의 필드나 메서드를 호출할 수 없다. 이때,
super
키워드를 사용하면 부모를 참조할 수 있다.super
는 이름 그대로 부모 클래스에 대한 참조를 뜻한다. this
: 자기 자신 클래스에 대한 참조super
: 부모 클래스에 대한 참조
코드 예시
public class Parent {
public String value = "parent";
public void hello() {
System.out.println("Parent.hello");
}
}
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();
}
}
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
메모리 구조
super - 생성자
- 상속 관계의 인스턴스를 생성하면 결국 메모리 내부에는 자식과 부모 클래스가 모두 만들어짐.
Child
를 생성하면 부모인 Parent까지 함께 만들어짐
- 각각의 생성자도 모두 호출되어야 함
상속 관계를 사용하면, 자식 클래스의 생성자에서 super()를 이용하여 부모 클래스의 생성자를 반드시 호출해야 한다.
package extends1.super2;
public class ClassA {
public ClassA() {
System.out.println("ClassA 생성자");
}
}
// Class A 기본 생성자 존재
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);
}
}
// Class B 기본 생성자 없음
package extends1.super2;
public class ClassC extends ClassB {
public ClassC() {
super(10, 20); // 생략 불가능
System.out.println("ClassC 생성자");
}
}
package extends1.super2;
public class Super2Main {
public static void main(String[] args) {
ClassC classC = new ClassC();
}
}
// 출력
ClassA 생성자
ClassB 생성자 a=10 b=20
ClassC 생성자
메모리 구조
'Java > 문법' 카테고리의 다른 글
[Java] 우선순위 큐 (PriorityQueue) 기본 사용법부터 객체 다루는 방법까지 (0) | 2024.04.22 |
---|---|
[JAVA 중급 스터디 - 6회차] final (0) | 2024.03.20 |
[JAVA 중급 스터디 - 4~5회차] static (0) | 2024.03.20 |
[JAVA 중급 스터디 - 3회차] 자바의 메모리 구조 (0) | 2024.03.09 |
[JAVA 중급 스터디 - 2회차] 접근 제한자와 캡슐화 (0) | 2024.03.08 |