0. 상속은 중복을 줄이고 기능을 확장한다.
1. 부모 클래스 / 자식 클래스들의 각 각의 객체가 존재하나 같은 참조값을 바라본다.
2. 오버라이딩은 상속에서만 사용되며, 부모의 메서드를 재정의하는 것이다.
3. 상속의 접근 제어자 protected이다.
4. super을 통해 부모를 참조하고, 생성자를 만든다. 생성자 호출은 부모→자식으로 진행된다.
1. 상속 이해
[p.3 / Car_부모 클래스 ]
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
}
[p.3 / ElectircCar_자식 클래스] Car에서 상속받음
public class ElectricCar extends Car {
public void charge() {
System.out.println("충전합니다.");
}
}
[p.3 / CarMain] 자식 객체를 생성하면 자식 뿐만 아니라 상속받은 부모의 객체까지 함께 생성한다.
public class CarMain {
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
}
}
- `ElectricCar` 객체를 생성할 때 `ElectricCar`의 인스턴스 변수뿐만 아니라 `Car` 클래스의 인스턴스 변수도 함께 생성
- 자식 클래스의 객체를 생성하면, 그 자식 클래스의 인스턴스 뿐만 아니라 상속된 부모 클래스의 인스턴스도 함께 생성
>> 이것은 자식 클래스가 부모 클래스의 모든 속성과 행동을 상속하기 때문
∴ 자식 클래스는 부모 클래스의 모든 것을 상속받아 확장하고, 그 자식 클래스의 객체를 생성하면
부모 클래스의 객체도 함께 생성되어 사용될 수 있습니다.
*해당 부분(p.7~9) 을 제대로 이해하는 것이 중요하다.
[p.7 / CarMain 이해를 위한 발췌] 통장(참조값)과 부모/only내 통장(객체)을 예시로 들어 이해 도모
- 참조값 : 통장
- 객체 : (방안에)부모에게 상속받은 돈의 통장과 only 내 돈의 통장을 확인할 수 있음
ElectricCar electricCar = new ElectricCar();
- `ElectricCar electricCar = new ElectricCar();` 구문에서 `electricCar`는 참조값(내 방)을 가지고 있으며,
방에는 `ElectricCar` 클래스에서 상속받은 속성/메서드뿐만 아니라 `Car` 클래스에서 상속받은 속성/메서드도 포함
>> 상속의 경우 부모 통장(Car)을 통해 찾을지, 내 통장(ElectricCar)을 통해서 찾을지 정해야 한다.
: 호출하는 변수의 타입(클래스)을 기준으로 선택한다.
단, 찾는 특정 메서드가 없을 경우 부모 클래스로 올라가서 찾는다.
(없을 경우 컴파일 에러가 발생되며 계속해서 상위 부모 클래스로 올라간다.)
실상 참조값은 1개가 생성되지만, 내부를 살펴보면 2개의 객체 인스턴스가 있다.
다만 참조값은 동일한 공간을 바라본다고 이해하면 된다.
[p.9 / Car_부모 클래스] openDoor기능 생성, 자식 클래스가 상속받을 경우 사용 가능 (불필요한 중복 감소)
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
public void openDoor() {
System.out.println("문을 엽니다.");
}
}
[p.9 / HydrogenCar_자식 클래스] 새로 생성, 즉 클래스 확장
public class HydrogenCar extends Car {
public void fillHydrogen() {
System.out.println("수소를 충전합니다.");
}
}
- 해당 클래스도 extends Car을 통해 부모에게 접근할 수 있고, 자유롭게 기능을 물려받을 수 있다.
상속의 장점은 새로운 클래스 생성에도 불구하고 중복을 줄이고, 기능을 확장할 수 있다.
2. 상속 오버라이딩
[p.12 / ElectricCar] 오버라이딩 : 부모에게 상속받은 기능을 자식이 재정의하는 것을 의미한다.
@Override
public void move() {
System.out.println("전기차를 빠르게 이동합니다.");
}
ex) 일반적으로 자동차는 그냥 이동한다고 출력하지만, 전기차가 더 빠르기 때문에 이를 구별해 출력하고 싶음
[p.12 / Car] 기존과 동일
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
public void openDoor() {
System.out.println("문을 엽니다.");
}
}
[p.12 / ElectricCar] 부모의 기능을 그대로 사용하는 것이 아닌, 이름은 같은 새로운 기능을 사용하고 싶을 때
public class ElectricCar extends Car {
public void charge() {
System.out.println("충전합니다.");
}
@Override
public void move() {
System.out.println("전기차를 빠르게 이동합니다.");
}
}
- 명확성 향상을 위해 꼭 @(애노테이션)을 호출해주는 것을 권고한다.
[p.12 / CarMain] 자식의 메서드에 먼저 접근할 경우, 부모의 메서드까지 접근하지 않는다.
public static void main(String[] args) {
ElectricCar electricCar = new ElectricCar();
electricCar.move();
electricCar.charge();
electricCar.openDoor();
}
- electricCar로 호출했기 때문에 electricCar에 있는 move에 먼저 접근하게 되면, 부모의 move에는 접근하지 않는다.
메서드 오버로딩(Overloading) | 메서드 오버라이딩(Overriding) |
- 이름이 같고 매개변수(파라미터가)다른 메서드를 여러개 정의 |
- 하위 클래스에서 상위 클래스 메서드를 재정의하는 것 - 주로 상속 관계에서 사용 |
메서드 오버라이딩을 통해 자식이 부모의 메서드를 재정의할 수 있고,
자식 매서드에 접근하게 함으로써 부모 매서드까지 닿지 않게 만든다.
순번 | 내용 | 적요 |
1 | 매서드 이름 | 메서드 이름이 같아야 한다. |
2 | 메서드 매개변수(파라미터) | 매개변수(파라미터) 타입, 순서, 개수가 같아야 한다. |
3 | 반환타입 | 반환 타입이 같아야 한다. |
4 | 접근 제어자 | 상위 클래스 제어자보다 더 제한적이면 안된다. |
5 | 예외 | 부모보다 더 많은 예외는 안된다. |
6 | static, final, private | - static : 클래스 레벨에서 작동하는 것이기 때문에, 인스턴스와 무관하다. - final 메서드는 재정의 자체를 금지한다. - prviate는 해당 클래스에서만 접근 가능한 접근 제어자이기에 사용 불가하다. |
7 | 생성자 오버라이딩 | 생성자는 오버라이딩 할 수 없다. |
3. 상속과 접근 제어
[p.17 / 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();
}
}
[p.18 / child] Parent 부모 클래스 사용 : 상속관계 접근제어자로 protected는 허용
import extends2.access.parent.Parent;
public class Child extends Parent {
public void call() {
publicValue = 1;
protectedValue = 1;
//defaultValue = 1;
//privateValue = 1;
publicMethod();
protectedMethod();
//defaultMethod();
//privateMethod();
printParent();
}
}
- 다른 클래스 사용으로 인해 default, 내부 외적인 클래스이므로 private 사용 불가
[p.19 / ExtendAccessMain]
public class ExtendsAccessMain {
public static void main(String[] args) {
Child child = new Child();
//child 객체가 생성되지만 부모의 parent 객체도 같이 생성
//실상 child와 parent의 객체의 참조값(클래스 인스턴스)이 같다고 보면된다.
child.call(); //publicValue , protectedValue = 1 수행
//publicMethod, protectedMethod 출력
//publicMethod, protectMethod는 부모에서 상속된 것, 부모것 실행
//상기에서 같은 참조값에 따라 객체 결과가 공유되므로 publicValue=1이 공유됨
//printParent : 상동, 자식 클래스 메서드 확인 불가로 부모 메서드 출력
}
}
통장 | 부모 통장 (2) |
내 통장 (1) |
- 자식이 부를때 부모입장에서 보면 외부 호출을 한 것처럼 보인다.
>> ExtendAceessMain 더 구체적인 설명 : 통장을 그려 구조화를 이해해라 (p.7 내용 참고)
public class ExtendsAccessMain {
public static void main(String[] args) {
Child child = new Child(); // Child 객체가 생성되지만 부모 클래스인 Parent의 인스턴스도 같이 생성된다.
// 실제로 child와 parent는 같은 객체를 가리키므로,
// child와 parent는 모두 Parent 클래스의 인스턴스를 가리키는 참조값을 갖게 된다.
child.call(); // Child 클래스의 call() 메서드를 호출하여,
// publicValue와 protectedValue에 각각 1이 할당되고 해당 값을 출력한다.
// publicMethod와 protectedMethod는 부모 클래스인 Parent에서 상속되었으며,
// Child 클래스에서 해당 메서드를 호출하면 부모 클래스의 메서드가 실행된다.
// 이는 자바에서 상속 관계에서 메서드 오버라이딩의 개념과 관련이 있다.
// 따라서 부모 클래스의 메서드가 출력된다.
// 상기에서 같은 참조값에 따라 객체 결과가 공유되므로 publicValue=1이 공유된다.
// 이는 child와 parent가 같은 객체를 참조하고 있기 때문이다.
// printParent 메서드는 Parent 클래스에 정의된 메서드이며,
// child 객체에서 호출하더라도 부모 클래스의 메서드를 호출하게 된다.
// 이는 부모 클래스와 자식 클래스 간의 상속 관계에서 메서드 호출이 이뤄지기 때문이다.
// 따라서 부모 클래스의 메서드가 출력된다.
}
}
상속관계는 접근 제어자 protected를 이용하여 접근하여 사용할 수 있다.
4. super - 부모 참조
[p.21 / Parent]
public class Parent {
public String value = "parent";
public void hello() {
System.out.println("parent.hello");
}
}
[p.21 / Child] 부모 클래스를 참조하기위해 super 사용
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);
//부모 클래스에 대한 참조
//이름이 같지만 super을 사용해 기능을 사용할 수 있다.
this.hello();
super.hello();
}
}
[p.22 / Super1Main]
public class Super1Main {
public static void main(String[] args) {
Child child = new Child();
child.call();
//this value ::::: 바로 가져오기 child
//super value :::: 부모거에서 호출
//this hello ::::: 바로 내 hello 가져오기
//super hello :::: 부모거에서 호출
}
}
5. super - 생성자
[p.23 / ClassA] 부모 생성자를 호출할 때는 super을 사용하면 된다.
public class ClassA {
public ClassA() {
System.out.println("ClassA 생성자");
}
}
- 상속 관계를 사용하면 자식 클래스 생성자에서 부모 클래스 생성자를 반드시 호출해야 한다.
[p.23 / ClassB] 자식 생성자는 부모 생성자를 무조건 호출 해야하며, 매개변수가 없을 경우 생략 가능하다.
public class ClassB extends ClassA {
public ClassB(int a) { //B생성시 초기값 설정
//부모 클래스이 생성자 호출해야한다. 따라서 super 언급
super(); //기본 생성자는 생략이 가능하다. /classA();로 만들었기 때문
System.out.println("ClassB 생성자 a = " + a);
}
public ClassB(int a, int b) {//생성자도 오버로딩이 가능하다(복습)
super(); //기본 생성자 생략 가능
System.out.println("ClassB 생성자 a = " + a + ", b = " + b);
}
}
[p.24 / ClassC] 자식 생성자는 부모 생성자(super)를 무조건 호출해야 하며, 여러개일 경우 하나만 호출할 수 있다.
public class ClassC extends ClassB{
public ClassC() {
super(10, 20); //생성자는 하나만 호출할 수 있다.
//따라서 두 생성자 중에 하나를 고르면 된다.
System.out.println("Class C 생성자");
}
}
[p. 24 / Super2Main] 자식 생성자 첫줄에서 부모 생성자를 호출하기에 최상위 부모부터 하나씩 실행되어 내려온다.
public class Super2Main {
public static void main(String[] args) {
ClassC classC = new ClassC();
//(C) super(10,20) + classC생성자 -->1번쨰 스탭으로 들어간다.
//super이 부모 클래스이기 때문에 뒤에 10,20 이 아닌 super 기준으로 부모로 들어간다.
//(B) supper() + classB생성자 --> 2번째 스탭으로 들어간다.
//상기와 동일하게 super 기준으로 상위 부모 A로 들어간다
//(A) classA 생성자 먼저 호출
//(B) supper 밑 classB 생성자 호출
//(C) supper 밑 classC 생성자 호출
}
}
- step을 밟으면서 천천히 읽으면 이해할 수 있다.
- this를 사용하더라도 반드시 한 번은 super을 호출한다.
1. `ClassC` 객체가 생성될 때, 먼저 `ClassC`의 생성자가 호출된다.
2. `ClassC` 생성자 내에서 `super(10, 20);` 구문이 실행되며, 이는 부모 클래스인 `ClassB`의 생성자를 호출한다.
3. `ClassB` 생성자에서는 `super();` 구문이 실행되어 부모 클래스인 `ClassA`의 생성자를 호출한다.
4. 마지막으로 `ClassA` 생성자가 호출되어 객체의 초기화가 완료된다.
'Java > [inflearn] 자바 기본' 카테고리의 다른 글
[김영한_자바기본][11. 다형성2] 다형성 사용 이유 및, 순수 추상 클래스(p.1-20) (0) | 2024.02.13 |
---|---|
[김영한_자바기본][10. 다형성1] 상속과 메모리 구조(p.1-23) (2) | 2024.02.10 |
[김영한_자바기본][7. 자바 메모리 구조와 static] static 변수 및 메서드 이해 (p.15-31) (2) | 2024.02.07 |
[김영한_자바기본][7. 자바 메모리 구조와 static] 자바 메모리 구조 이해 / 스택과 큐 자료 구조(p.1-12) (0) | 2024.02.07 |
[김영한_자바기본][6. 접근 제어자] 배열 / 접근 제어자 함께 이해하기_문제풀이 쇼핑 카트(p.17) (0) | 2024.02.06 |