상속(Inheritance) : 한 클래스가 다른 클래스의 속성을 가져와 확정하거나 수정하는 메커니즘 입니다.
- 자바는 다중 상속을 지원하지 않습니다. 그래서 extends 대상은 하나만 선택할 수 있습니다. 물론 부모가 상위 부모를 가지는 것은 괜찮습니다.
- 만약 비행기와 자동차를 상속 받아서 하늘을 나는 자동차를 만든가고 가정해보면. 아래 그림과 같이 다중 상속을 사용하게 됩니다. AirplaneCar 입장에서 move()를 호출할 때 어떤 부모의 move() 사용해야 할지 애매한 문제가 발생합니다. 이것을 다이아몬드 문제라고 합니다. 그리고 다중 상속을 사용하면 클래스 계층 구조가 매우 복잡해질 수 있습니다. 이런 문제점 때문에 자바는 클래스의 다중 상속을 허용하지 않습니다. 대신에 인터페이스의 다중 구현을 허용하여 이러한 문제를 피합니다.
다형성(Polymorphism) : 다형성은 "여러 형태" 라는 뜻의 그리스어에서 유래 되었습니다. 자바에서의 다형성은 객체가 여라 형태를 가진 수 있음을 의미합니다.
- 다형성 참조 : 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능
- 메서드 오버라이딩 : 기존 부모의 기능을 자식 타입에서 새로운 기능을 재정의 이때 오버라이딩한 메소드가 절대적으로 우선권을 가집니다.
컴파일 시간 다형성 : 주로 오버로딩을 통해 구현 됩니다. 같은 이름의 메서드를 여러 개 정의하지만 매개변수 타입이나 개수가 다를 때 이를 구분하여 사용하는 것입니다.
실행 시간 다형성 : 오버라이딩을 통해 구현됩니다. 상위 클래스의 메서드를 하위 클래스에서 재정의 하는 것입니다. 이 때, 상위 클래스의 참조 변수를 사용하여 하위 클래스를 참조할 수 있습니다.
public class ExtendsExampleMain {
public static void main(String[] args) {
Parent parent = new Parent();
Parent extendedParent = new Child(); // 다형성
Child child = new Child();
parent.someMethod();
extendedParent.someMethod();
// Child 인스턴스 이지만 Parent 타입으로 선언되었기 때문에 사용 불가
// extendedParent.anotherMethod();
child.someMethod(); // Parent 에게 상속 받은 메서드
}
}
public class Parent {
public int parentPublicInt;
protected int parentProtectedInt;
private int parentPrivateInt;
public void someMethod() {
System.out.println("Parent someMethod");
}
}
public class Child extends Parent {
public void anotherMethod() {
System.out.println("Child someMethod");
this.parentProtectedInt = 0;
this.parentPublicInt = 0;
// this.parentPrivateInt = 0; // 상속 되지 않음 (private)
}
}
오버라이딩(재정의) - Overriding
부모 클래스에서 상속 받은 메서드를 자식 클래스에서 다시 정의 하는 것
- 멤버 변수는 재정의 불가
- 메서드는 재정의 가능 , 이때 오버라이딩 된 메서드가 우선권을 가집니다.
public class ExtendsExampleMain {
public static void main(String[] args) {
Parent parent = new Parent();
Parent extendedParent = new Child(); // 다형성
Child child = new Child();
parent.someMethod();
extendedParent.someMethod();
// Child 인스턴스 이지만 Parent 타입으로 선언되었기 때문에 사용 불가
// extendedParent.anotherMethod();
}
}
@Override
public void someMethod() {
System.out.println("Child overriding someMethod");
}
오버로딩(중복정의) - Overloading
동일한 메서드 이름으로 파라미터, 리턴 타입이 다른 메서드를 여러 개 정의 하는 것
public class OverloadingExampleMain {
public static void main(String[] args) {
AddCalculator addCalculator = new AddCalculator();
int intResult = addCalculator.add(10, 20);
long longResult = addCalculator.add(10L, 20L);
double doubleResult = addCalculator.add(10.0, 20.0);
System.out.println(intResult);
System.out.println(longResult);
System.out.println(doubleResult);
}
}
public class AddCalculator {
public int add(int num1, int num2) {
return num1 + num2;
}
// return 타입만 다르게 오버로딩은 불가능
// public long add(int num1, int num2) {
// return num1 + num2;
// }
public long add(long num1, long num2) {
return num1 + num2;
}
public double add(double num1, double num2) {
return num1 + num2;
}
}
올바른 상속을 위해서는 메서드 재사용 은 지양하며 필드에 대한 재사용으로 사용해야 합니다.
올바른 상속 -> 메서드 재사용 X, 필드에 대한 재사용 할때 사용한다. -> 전략 패턴 구성 (Composite) 활용
-> 부모 타입이 할 수 있는 일은 자식 타입도 할 수 있여야 함 -> 리스코프 치환 원칙
상속과 메모리의 구조
- 인스턴스 생성 : 자식 클래스의 인스턴스가 생성될 때, 부모 클래스의 필드도 해당 인스턴스 내에 포함되어 힙 메모리에 할당 됩니다.
- 이때 생성된 인스턴스의 참조변수는 부모와 자식이 같은 참조값을 가집니다.
- 메서드 오버라이딩 : 자식 클래스에서 부모 클래스의 메서드를 오버라이딩하면, 해당 메서드의 호출은 동적 바인딩을 통해 실행 시간에 결정됩니다. 이는 JVM이 메모리에서 해당 객체의 실제 타입을 확인하고 적절한 메서드를 실행하는 과정을 포함합니다.
- 메서드 호출 : 객체는 메서드가 호출되면, 해당 메서드의 실행 정보 (매개변수, 로컬변수 등)는 스택 메모리에 저장합니다.
class Parent {
public Parent() {
System.out.println("Parent Constructor");
}
public void show() {
System.out.println("Parent show");
}
}
class Child extends Parent {
public Child() {
super(); // 부모 클래스의 생성자 호출, 생략할 경우 부모의 기본 생성자 자동으로 호출
System.out.println("Child Constructor");
}
public void show() {
super.show(); // 부모 클래스의 show 메서드 호출
System.out.println("Child show");
}
}
public class Test {
public static void main(String[] args) {
Child c = new Child(); // 부모와 자식 클래스의 생성자가 순서대로 호출됨
c.show(); // "Parent show"와 "Child show"가 순서대로 출력됨
}
}
요약하면 아래와 같습니다
- 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식이 모두 생성 됩니다.
- 상속 관계의 객체를 호출할 때, 대상 타입을 정해야 합니다. 이때 호출자는 타입을 통해 대상 타입을 찾습니다
- Parent extendedParent = new Child(); // 다형성
- 이때 자식에서 부모 메서드를 오버라이딩 했다면 자식 메서드에 코드를 실행하게 된다.
- 위에서 설명한 상속관계에서 인스턴스 생성은 부모 자식 모두 힙 메모리에 같은 참조 값으로 생성이 되고, 변수 extendedParent변수는 대상 (Parent) 에서 먼저 메소드를 찾고 해당 메서드를 오버라이딩한 자식 메서드의 코드를 실행하게 됩니다.
public class Parent {
public void method1() {
System.out.println("Parent.method1()");
}
}
public class Child extends Parent {
@Override
public void method1() {
System.out.println("Child.method1()");
}
public void chileMethod() {
System.out.println("Child.childMethod()");
}
public static void main(String[] args) {
Parent parent = new Child();
// 부모 타입으로 메서드 호출, 이때 코드는 자식 타입의 코드가 실행된다.
// 대상 타입의(Parent) 메서드를 호출해서 찾지만 인스턴스는 자식이기 때문이다.
parent.method1();
}
}
- 현재 타입에서 기능을 찾지 못하면 부모 타입으로 기능을 찾아서 실행 합니다. 이때는 자식 타입으로 인스턴스를 생성합니다.
- Child child = new Child();
'자바' 카테고리의 다른 글
예외 (0) | 2023.12.11 |
---|---|
캡슐화 (0) | 2023.12.11 |
추상클래스와 인터페이스 (0) | 2023.12.11 |
객체지향적으로 개발해야 하는 이유 (0) | 2023.12.11 |
동시성 이슈 (2) | 2023.12.11 |