❗해당 포스팅은 인프런에서 제공해 주는 강의 내용을 개인적으로 정리하였음을 알려드립니다.
김영한의 실전 자바 - 기본편 강의 | 김영한 - 인프런
김영한 | 실무에 필요한 자바 객체 지향의 핵심 개념을 예제 코드를 통해 쉽게 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문
www.inflearn.com
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("기름을 주유합니다.");
}
}
전기차와 가솔린차는 자동차의 구제적인 개념이고
자동차는 전기차와 가솔린차의 추상적인 개념이다.
이때, 전기차와 가솔린차의 move는 공통 기능으로, 이런 경우 상속을 사용하는 것이 좋다.
2. 상속 실습
상속은 extends 키워드를 사용하고, 상속 대상은 하나만 선택할 수 있다.
- 부모 클래스 (슈퍼 클래스): 상속을 통해 자신의 필드와 메서드를 다른 클래스에 제공
- 자식 클래스 (서브 클래스): 부모 클래스로부터 필드와 메서드를 상속받는 클래스
이제 전기차와 가솔린차를 상속을 사용하여 코드를 수정해 보자
부모 클래스
package extends1.ex2;
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
}
전기차
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("기름을 주유합니다.");
}
}
Main
package extends1.ex2;
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();
}
}
전기차와 가솔린차에서 Car를 상속받고 있기 때문에 move() 메서드를 호출할 수 있다.
다중 상속이 안 되는 이유
=> 부모 A와 부모 B 중 어느 부모의 메서드를 가져올지 애매해지는 문제가 발생함, 클래스 계층 구조가 복잡해짐
=> 다이아몬드 문제
3. 상속과 메모리 구조
ElectricCar electricCar = new ElectricCar();
new ElectricCar()를 호출하면 ElectricCar와 상속관계의 Car까지 인스턴스를 생성한다.
즉, 하나의 인스턴스가 아닌 부모와 자식 두 개의 인스턴스가 생성된다.
메서드 호출 시
1. 호출하는 변수의 타입(클래스)을 기준으로 선택한다.
2. 호출하는 변수의 타입에 해당 기능이 없다면 부모타입으로 올라가서 찾는다.
3. 부모타입에도 없다면 컴파일 오류가 발생한다.
4. 기능 추가와 클래스 확장
모든 차량에 문 열기 기능을 추가해야 한다면?
Car 클래스에 openDoor() 메서드만 추가하면 자식 클래스에서 사용 가능하다.
새로운 차 종류인 수소차가 생긴다고 가정하면?
Car를 상속받기 때문에 1. 기존 기능을 그대로 사용하면서(상속) 2. 기능추가가 가능하다.(확장)
부모 클래스
package extends1.ex3;
public class Car {
public void move() {
System.out.println("차를 이동합니다.");
}
public void openDoor() {
System.out.println("문을 엽니다.");
}
}
수소차
package extends1.ex3;
public class HydrogenCar extends Car{
public void fillHydrogen(){
System.out.println("수소를 충전합니다.");
}
}
Main
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. 메서드 오버라이딩
메서드 오버라이딩이란? 부모에게서 상속받은 기능을 자식이 재정의 하는 것을 말한다.
전기차에서 move() 메서드의 내용을 수정하고 싶다면 부모의 move()를 재정의하면 된다.
package extends1.overriding;
public class ElectricCar extends Car {
@Override
public void move() {
System.out.println("전기차를 빠르게 움직입니다.");
}
public void charge(){
System.out.println("충전합니다.");
}
}
@Override : 부모의 메서드를 사용하겠다는 표식. 없어도 동작은 하지만 코드의 명확성을 위해, 실수 방지를 위해 권장함.
오버라이딩 메모리 구조
1. 호출하는 변수의 타입(ElectricCar)을 기준으로 move를 찾는다.
2. 실행할 메서드를 찾았으므로 부모 타입을 찾지 않는다.
오버로딩 vs 오버라이딩
오버로딩: 메서드 이름이 같고 파라미터가 다른 메서드를 여러 개 정의하는 것, 파라미터가 다르고 반환 타입이 다른 경우도 성립.
1. 매개변수의 개수가 다르거나
2. 매개변수의 타입이 다르거나
3. 매개변수의 순서가 달라야 됨!!
4. 반환타입은 같아도 되고 달라도 됨
오버라이딩: 하위 클래스에서 상위 클래스의 메서드를 재정의하는 과정.
1. 메서드 이름 같아야 됨
2. 메서드 개수, 타입, 순서 같아야 됨
3. 반환 타입 같아야 됨
4. 상위 클래스 메서드보다 더 제한적이어서는 안 됨
5. static, final, private 키워드가 붙은 메서드는 오버라이딩 불가
6. 생성자 오버라이딩 불가
6. 상속과 접근제어
접근 제어자 종류
- private: 모든 외부 호출을 막는다.
- default(package-private): 같은 패키지 안에서 호출은 허용한다.
- protected: 같은 패키지 안에서 호출은 허용한다. 패키지가 달라도 상속 관계의 호출은 허용한다.
- public: 모든 외부 호출을 허용한다.
실습코드
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();
}
}
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(); //상속 관계 or 같은 패키지
// defaultMehod(); //다른 패키지 접근 불가, 컴파일 오류
// privateMethod(); //접근 불가, 컴파일 오류
printParent();
}
}
접근제어와 메모리 구조
1. 호출하는 변수의 타입(Child)을 기준으로 찾는다.
2. 호출하는 변수의 타입(Child)에 없으면 부모 타입에서 기능을 찾는데, 이때 접근 제어자가 영향을 준다. (객체 내부에서 부모와 자식이 구분되어있고 부모 입장에서 자식은 외부 호출이기 때문에)
7. super 키워드
부모와 자식의 필드명이 같거나 자식에서 부모의 메서드를 오버라이딩 한 경우, 자식에서 부모의 필드나 메서드를 호출할 수 없는데, 이때 super 키워드를 사용하여 부모 참조가 가능하다. 아래 실습을 확인해 보자
부모 클래스
package extends1.super1;
public class Parent {
public String value = "parent";
public void hello() {
System.out.println("Parent.hello");
}
}
자식 클래스
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);
System.out.println("super value = " + super.value);
this.hello();
super.hello();
}
}
메인 클래스
package extends1.super1;
public class Super1Main {
public static void main(String[] args) {
Child child = new Child();
child.call();
}
}
실행결과
super - 생성자
상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다.
아래의 예제를 확인하자
최상위 클래스 ClassA
package extends1.super2;
public class ClassA {
public ClassA() {
System.out.println("ClassA 생성자");
}
}
ClassA를 상속받는 ClassB
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);
}
}
자식 클래스에서 부모 클래스의 생성자를 호출해야 하지만
기본 생성자(파라미터가 없는 생성자)인 경우는 super() 호출을 생략해도 된다. => 자바가 자동으로 만들어 준다.
classB를 상속받는 classC
package extends1.super2;
public class ClassC extends ClassB {
public ClassC() {
super(10,20);//부모가 기본 생성자가 없으면 자식이 직접 생성
System.out.println("ClassC 생성자");
}
}
classB에는 두 생성자가 있는데 하나만 선택할 수 있다.
classB에는 기본 생성자가 없으므로, 기본 생성자 super()를 호출하거나 생성자 호출을 생략할 수 없다.
package extends1.super2;
public class Super2Main {
public static void main(String[] args) {
ClassC classC = new ClassC();
}
}
실행결과
상속 관계의 생성자 호출은 부모 -> 자식 순서로 실행된다. 부모의 데이터를 먼저 초기화하고 자식의 데이터를 초기화한다.
8. 클래스와 메서드에 사용되는 final
클래스에 final이 붙으면 -> 상속받을 수 없다. 다른 클래스가 final로 선언된 클래스를 상속받을 수 없다.
메서드에 final이 붙으면 -> 오버라이드 될 수 없다. 상속받은 자식 클래스에서 final이 붙은 메서드를 수정할 수 없다.
'Back-end > Java' 카테고리의 다른 글
[Java] final 변수와 상수 (3) | 2024.12.20 |
---|---|
[Java] static 메서드 (1) | 2024.12.20 |
[Java] static 변수 (0) | 2024.12.16 |
[Java] 자바 메모리 구조 (0) | 2024.12.14 |
[Java] 기본형과 참조형 (3) | 2024.12.03 |