[JAVA] 상속

JUJU·2024년 1월 18일

✏️ 상속이란

상속이란, 부모 클래스의 멤버를 자식 클래스에게 물려주는 것이다.

멤버를 상속한 자식 클래스는 부모 클래스의 필드와 메소드를 사용할 수 있다.

상속의 조건: 상속은 하나의 부모 클래스만 가능하다, 부모 클래스의 private 멤버는 상속되지 않는다.

// 부모 클래스
public class Car{
	int speed;
    
    public void run(){ }
}
// 자식 클래스
// extends 키워드를 사용하여 상속받는다.
public class SportsCar extends Car{ 
	// speed 필드를 상속받음
    int maxSpeed;
    
    // run 메소드를 상속받음
    public void hyper(){ }
}


✏️ 부모의 생성자

부모 없는 자식은 없다!

자식 객체가 생성되기 위해선 반드시 부모 객체가 먼저 생성되어야 한다.

SportsCar s1 = new SportsCar();

SportsCar 클래스, 즉 자식 클래스의 생성자를 호출하면 자동으로 부모 클래스의 기본 생성자가 호출된다.
위의 예시에서는, SportsCar 클래스의 생성자를 명시하지 않았다. 따라서, 컴파일러가 자식 클래스의 기본 생성자를 만들어준다.

public SportsCar(){
	super();
}

컴파일러는 자식 클래스의 기본 생성자를 만들어주는 경우, super(); 코드를 추가한다.

super()는 부모 클래스의 기본 생성자다!!

내가 자식 클래스의 생성자를 미리 만들어놓고 싶다면??

직접 선언하는 경우, super()를 넣어주기만 하면 된다.

public SportsCar(int para1){
	super(para1);
    // 부모 클래스에는 부모클래스(int para1)의 생성자가 반드시 존재해야 한다.
}

super()는 반드시 자식 생성자의 첫 줄에 위치해야 한다.



✏️ 메소드 오버라이딩

메소드 오버라이딩이란, 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 뜻한다.

메소드 오버라이딩의 조건

  1. 부모의 메소드와 리턴타입, 메소드 이름, 매개변수가 반드시 일치해야 한다.
  2. 접근 제한을 더 강하게 할 수 없다.
  3. 새로운 예외를 throws 할 수 없다.
public class SportsCar extends Car{ 
    int maxSpeed;
    
    // run 메소드를 재정의 한다.
    // @Override를 사용하면, 메소드가 정확하게 재정의 되었는지 컴파일러가 확인해준다.
    @Override
    public void run() {
    	System.out.println("메소드가 오버라이드 되었습니다.");
    }
}
public class executeClass{
	public static void main(String[] args){
    	SportsCar s1 = new SportsCar();
        
        s1.run(); // 자식 클래스 SportsCar의 메소드 실행됨
    }
}

오버라이드된 메소드를 호출하면, 자식 클래스의 메소드가 실행된다.

그럼 부모 클래스의 메소드는 이제 못쓰나??

super 키워드를 사용해서, 부모 메소드를 호출할 수 있다.

public class SportsCar extends Car{ 
    int maxSpeed;
    
    @Override
    public void run() {
    	System.out.println("메소드가 오버라이드 되었습니다.");
    }
    
    // 오버라이딩 되지 않은 부모 클래스의 메소드를 사용하고 싶은 경우
    public void parentRun() {
    	super.run();
    }
}


✏️ final

[JAVA] 클래스 포스팅에서 final 키워드에 대해 간단히 설명했었다.
final 키워드는 어떤 멤버 앞에 붙는지에 따라서 그 효과가 달라진다.

1. final 필드

2. final 클래스

  • final 키워드가 붙은 클래스는 상속되지 않는다.
  • 대표적인 예시로 String 클래스가 있다. String 클래스는 상속할 수 없다.

3. final 메소드

  • final 키워드가 붙은 메소드는 재정의할 수 없다.


✏️ 형 변환과 다형성

다형성이란, 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질이다.

다형성을 구현하려면 메소드 재정의형 변환이 필요하다.

1. 자동 형 변환 (업스케일)

기본형에 대해서 학습했을 때, 작은 데이터 범위 --> 큰 데이터 범위로의 형 변환은 자동으로 발생할 수 있음을 알게되었다.

int num1 = 10;
long num2 = num1; // 자동으로 long으로 형 변환됨

클래스도 형 변환이 존재한다.
자식 클래스 --> 부모 클래스로의 형 변환은 자동적으로 발생한다.

⚠️ 기본형의 자동 형 변환은 데이터가 더 커지는 방향이지만, 클래스의 자동 형 변환은 사용 범위를 더 제한하는 방향이다.

부모 클래스로 형 변환된 자식 클래스는, 부모 클래스가 가진 필드와 메소드만 사용 가능하다.
하지만! 오버라이딩된 메소드는 자식 클래스의 메소드로 실행된다.

public class executeClass{
	public static void main(String[] args){
    	Car c1 = new SportsCar(); // 자식 클래스의 인스턴스가 부모 클래스로 자동 형 변환

        c1.speed; // 접근 가능
        // c1.maxSpeed는 자식 클래스의 필드이므로 접근 불가능

        c1.run(); // 오버라이딩된 자식 클래스의 메소드 실행됨
    }
}

c1의 자료형은 Car이고 참조하는 객체는 SportsCar의 인스턴스다.


2. 필드의 다형성

그냥 부모 따로 자식 따로 객체 생성하면 되는데, 왜 형 변환을 해서 사용하는가?

바로 다형성을 구현하기 위함이다.
필드의 자료형을 부모 클래스로 선언하면, 다양한 자식 객체들이 저장될 수 있다.

자동차 클래스 Car에서 2개의 필드를 선언하려 한다.
2개의 필드는 자동차의 바퀴 객체를 참조한다.
부모 클래스를 Tire, 자식 클래스를 각각 JuTire, KimTire라고 해보자.

public class Car{
	Tire first = new Tire();
    Tire second = new Tire();
}
// 부모 클래스
public class Tire{
	public int limit;
    
    public Tire(int limit){
    	this.limit = limit;
    }
    
    public void run(){ }
}
// 자식 클래스
public class JuTire extends Tire{
	public JuTire(int limit){
    	super(limit + 5); // 최대 5번 더 쓸 수 있는 성능 좋은 바퀴
    }
    
    @Override
    public void run(){ 
    	System.out.println("Ju Tire runs");
    }
}


public class KimTire extends Tire{
	public KimTire(int limit){
    	super(limit + 3); // 최대 3번 더 쓸 수 있는 성능 좋은 바퀴
    }
    
    @Override
    public void run(){ 
    	System.out.println("Kim Tire runs");
    }
}

자동차의 바퀴는 원래 Tire 클래스로 선언되어 있었다.
하지만, 자동차 바퀴를 성능이 좋은 JuTire, KimTire로 바꾸고 싶은 경우는 어떻게 해야할까?

  1. 자동 형 변환을 안쓰는 경우
// Car 클래스를 직접 수정해줘야 함
public class Car{
	JuTire first = new JuTire();
    KimTire second = new KimTire();
}
  1. 자동 형 변환을 쓰는 경우
// 필드 값에 JuTire 객체, KimTire 객체를 넣어주면 됨
public class executeClass{
	public static void main(String[] args){
    	Car c1 = new Car();
        
        c1.first = new JuTire();
        c1.second = new KimTire();
        
        c1.first.run(); // Ju Tire runs
		c1.second.run(); // Kim Tire runs
        // 하나의 메소드로 다양한 기능 --> 다형성
    }
}

3. 매개변수의 다형성

매개변수의 타입을 부모 클래스로 하고, 인수로 자식 클래스를 주면 자동으로 형 변환된다.

public class Driver{
	public void Drive(Vehicle vehicle){
    	vehicle.run();
    }
}

// Vehicle의 자식 클래스 taxi와 bus
// Vehicle vehicle = new taxi();
// Vehicle vehicle = new bus();
// 어떤 객체를 인수로 보내느냐에 따라서 run의 결과가 달라짐

4. 강제 형 변환 (다운 스케일)

강제 형 변환은 자동으로 형 변환된 자식 클래스를 복구하는 것이다.

강제 형 변환은 자식 --> 부모 형 변환이 발생한 후에만 사용할 수 있다.

Parent parent = new Child(); // Child 클래스의 객체를 참조하는 Parent 클래스 형 변수
// parent의 자료형은 Parent 클래스이다.
// parent는 부모 클래스의 필드와 메소드만 접근 가능하다.

자식 클래스 Child의 필드와 메소드에 접근하고 싶다면? 강제 형 변환을 한다.

Child child = (Child) parent; // Parent 자료형인 parent 변수를 Child 자료형으로 강제 형 변환

애초에 부모 클래스의 인스턴스를 참조하는 변수는 자식 클래스로 형 변환이 불가능하다.
Parent parent = new Parent(); // 부모 클래스의 인스턴스 생성
// Child child = (Child) parent; 이 코드는 실행 불가능

5. instanceof 연산자

객체의 타입을 모를 때는 instanceof 연산자를 사용한다.

if(parent instanceof Child){
	// 참조변수 instanceof 클래스
    // 이 코드는 parent가 참조하는 인스턴스가 Child 타입일 때만 동작함
    Child child = (Child) parent;
} else {
	System.out.println("Child형 변환 불가");
}


✏️ 추상 클래스

클래스의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다.

추상 클래스와 실체 클래스는 상속의 관계를 가지고 있다.

추상 클래스의 용도

    1. 공통된 필드와 메소드의 이름을 통일한다.
    2. 공통된 내용을 추상클래스에 작성하면 시간을 절약할 수 있다.
    3. 하위 클래스가 실행 내용을 채우도록 강제하는 추상 메소드를 선언할 수 있다.


추상 클래스의 선언

추상 클래스는 클래스 선언에 abstract 키워드를 붙여서 선언한다.

// 추상클래스 myClass
public abstract class MyClass { }

추상 메소드

추상 메소드는 하위 클래스에서 반드시 오버라이딩 해야 하는 메소드이다.

예를 들어, 추상 클래스 MyClass에서 turnOn이라는 추상 메소드를 선언했다고 하자.
실체 클래스인 TvClass와 AudioClass에서는 각각 추상 클래스를 상속받아서 turnOn이라는 메소드를 반드시 오버라이딩 해야 한다.

// 추상클래스 myClass
public abstract class MyClass { 
    public abstract void turnOn(); // 추상 메소드 선언
}
//실체 클래스 TvClass
public class TvClass extends MyClass{
    @Override
    public void turnOn(){
    	System.out.println("TV ON");
    }
}
//실체 클래스 AudioClass
public class AudioClass extends MyClass{
    @Override
    public void turnOn(){
    	System.out.println("AUDIO ON");
    }
}

실행 클래스는 다음과 같다.

// 실행 클래스 ExecuteClass
public class ExecuteClass{
	public static void main(String[] args){
    	// MyClass mine = new MyClass(); --> 에러!! 추상클래스는 인스턴스 생성 불가
        MyClass mine;
        
        mine = new TvClass();
        mine.turnOn(); // TV ON
        
        mine = new AudioClass();
        mine.turnOn(); // AUDIO ON
    }
}
  • 추상 클래스도 일반적인 필드, 메소드, 생성자를 선언할 수 있다.
  • 추상 메소드는 추상 클래스에서만 선언할 수 있다.

REFERENCE

혼자 공부하는 자바

profile
백엔드 개발자

0개의 댓글