JAVA/이것이 자바다

이것이 자바다 Ch 7 상속

본이qq 2022. 9. 8. 14:22

1. 상속이란?

- 부모(상위) 클래스의 멤버를 자식(파생) 클래스에게 물려주는 것

- 코드의 중복을 줄여주고 유지 보수 시간을 줄여준다. 

- 단 이전에 접근 제한자에서 공부했듯이 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 자식 클래스에서 이용할 수 없다. 마찬가지로 default 접근 제한을 갖는 부모클래스의 필드와 메서드도 다른 패키지에서 상속받는 자식클래스에서 이용할 수 없다. 

 

2. 클래스 상속 

- 하나의 자식 클래스는 하나의 부모 클래스만 상속할 수 있다 (다중 상속 불가)

class 자식클래스 extends 부모클래스{

	//필드
    //생성자
    //메소드
}

 

 

3.부모 생성자의 호출 

- 자식 객체를 생성하면, 눈에 보이지 않지만 내부적으로 부모 객체가 먼저 생성된 후에 자식 객체가 생성된다. 

- 모든 객체는 생성자를 호출해야지만 생성되는데, 그렇다면 부모 객체를 생성하기 위한 생성하기 위해 부모 생성자를 어디서 호출한걸까?

 --> 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다. super(); 가 숨어있음

- 부모 클래스의 생성자에 기본 생성자(매개변수가 없는)가 존재한다면 자식 클래스의 생성자 첫 줄에 super();를 명시하지 않아도 된다.

하지만, 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 있다면 자식 생성자에서 반드시 부모 생성자 호출을 위해 

super(매개값,...)를 명시적으로 호출해야한다. (자식 생성자 맨 첫줄에 위치하도록)

//부모 클래스
public class People{
	public String name;
    public String ssn;
    
    public People(String name, String ssn){
    	this.name = name;
        this.ssn = ssn;
       }
 }
//자식 클래스
public class Student extends People{
	public int studentNo;
    
    public Student(String name, String ssn, int studentNo){
    	super(name, ssn);
        this.studentNo = studentNo;
       }
  }

 

 

 

4. 메소드 재정의

- 부모 클래스의 메소드가 자식 클래스에 맞게 설계되어 있다면 가장 이상적인 상속이지만, 어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수 있다. 이 경우 상속된 일부 메서드는 자식 클래스에서 다시 수정해서 사용해야한다. --> 오버라이딩

 

1) 메소드 재정의 (@Override)

-상속된 메소드의 내용이 자식 클래스에 맞지 않을 경우, 자식 클래스에서 동일한 메소드를 재정의하는 것을 말한다. 

-메소드가 오버라이딩 되었다면 부모 객체의 메소드는 숨겨지고, 자식 객체에서 오버라이딩된 자식 메서드가 호출된다.

- 메소드 오버라이딩 시 주의할 점 

  • 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 리스트)를 가져야한다.
  • 접근 제한을 더 강하게 오버라이딩 할 수 없다.
  • 새로운 예외(Exception)를 throw 할 수 없다. 

- 자식 클래스에서 오버라이딩된 메소드 위에 @Override 어노테이션을 붙이면 메소드가 정확히 오버라이딩 되었는지 컴파일러가 체크하기 때문에 개발자의 실수를 줄여준다. 

 

2) 오버라이딩 후 부모 메서드 호출

- 자식 클래스 내부에서 오버라이딩되니 부모 클래스의 메소드를 호출해야하는 상황이 발생한다면 명시적으로 super 키워드를 붙여서 부모 메서드를 호출할 수 있다. super는 부모 객체를 참조하고 있기 때문에 부모 메서드에 직접 접근할 수 있다. 

super.부모메소드();

 

 

5. final 클래스와 final 메소드

1)상속할 수 없는 final 클래스

- 클래스를 선언할 때 class 앞에 final을 붙이게 되면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 된다. 즉 final 클래스는 부모 클래스가 될 수 없어서 자식 클래스를 만들 수 없다. 

2)Overriding 할 수 없는 final 메소드

- 부모 클래스의 final이 붙은 메소드는 자식 클래스에서 재정의할 수 없다. 

 

6.protected 접근 제한자

- protected 접근 제한자는 상속과 관련이 있다. 

- protected 접근 제한자는 public과 default 중간쯤에 해당한다. 

- 같은 패키지 내에서는 default 처럼 접근이 가능하지만 다른 패키지에서는 자식 클래스만 접근을 허용한다. 

package package1;

public class A{
	protected String field;
    
    protected A(){} //생성자
    
    protected void method();
    
}

-> 부모 클래스

package package1;

public class B{
	public void method(){
    	A a = new A(); // A클래스의 생성자 직접 호출 가능
        a.field = "value"
        a.method();
     }
 }

-> 같은 패키지 내에 있는 B클래스

 

import package1.A;

public class C {
	public void method(){
    	A a = new A(); //(x)
        a.field = "value"; //(x)
        a.method(); //(x)
       }
 }

-> 다른 패키지에 있는 C클래스 

 

package package2;
import package1.A;

public class D extend A {
	public D() {
    	super(); //(o)
        this.field = "value"; //(o)
        this.method(); //(o)
       }
 }

->클래스 A를 상속하고 있는 다른 패키지의 D 자식 클래스

 

 

6.타입 변환과 다형성 

 

- 다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말한다. 

- 코드 측면에서 보면 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해준다. 

-다형성을 위해 자바는 부모 클래스로 타입 변환을 허용한다 (즉, 부모 타입에 모든 자식 객체가 대입될 수 있다.)

-이것을 이용하면 객체의 부품화가 가능하다.

 (자동차의 경우 부모 클래스인 '타이어'를 상속한 모든 자식 타이어들은 어떤 것이든 상관없이 '타이어'를 상속했으므로 장착이 가능하다)

public class Car {
	Tire t1 = new HanKookTire(); 
    Tire t2 = new KumhoTire();
   }

--> HanKookTire와 KumhoTire 는 Tire 를 상속했기 때문에 Tire 변수에 대입할 수 있다. 

 

1) 자동 타입 변환(Promotion)

-자동 타입 변환(Promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다. 

- Animal 클래스와 Cat 클래스가 상속 관계에 있다고 가정하면 .. 

Cat cat = new Cat();
Animal animal = cat; //Animal animal = new Cat(); 도 가능하다.

--> cat 참조 변수와 animal 참조 변수는 자료형만 다를 뿐 cat == animal 연산을 하면 true가 나온다. 즉, 두 변수가 동일한 객체를 참조하고 있다는 의미이다. 

 

-부모 타입으로 자동 타입 변환된 후로는 (객체 타입은 부모 타입인데, 자식 인스턴스를 품고 있는 경우) 부모 클래스에 선언된 필드와 메소드에만 접근이 가능하다. 그러나 자식 클래스에서 메소드가 오버라이딩 되었다면, 자식 클래스의 메소드가 대신 호출된다

다형성과 관련이 있기 때문에 매우 중요한 성질이므로 꼭 기억하기 !!

 

2)필드의 다형성

- 그렇다면 왜 굳이 자동 타입 변환을 하는걸까? 그냥 자식 타입 변수를 사용하면 되는거 아닌가?

- 다형성이란 동일한 타입을 사용하지만, 다양한 결과가 나오는 것을 말한다. 

-자동차 클래스에 포함된 타이어 클래스를 생가해보자.

  자동차 클래스를 처음 설계할 때 사용한 타이어 객체는 언제든지 성능이 좋은 타이어로 교체될 수 있어야한다. 

  이것을 프로그램으로 구현하기 위해 상속과 타입 변환, 오버라이딩을 이용하는 것이다. 

  부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으니 사용 방법이 동일할 것이고, 자식 클래스는 부모의 메소드를 오버라이딩(재정의)해서 메소드의 실행 내용을 변경함으로써 더 우수한 실행 결과가 나오게 할 수 있다. 

class Car {
	//필드
    Tire frontLeftTire = new Tire();
    Tire frontRightTire = new Tire();
    Tire bakLeftTire = new Tire();
    Tire backRightTire = new Tire();
    
    //메소드
    void run(){...};
 }

-> Car 클래스는 4개의 Tire 필드를 갖고 있다. Car 객체를 생성하면 Tire 필드에 4개의 Tire 객체가 들어가게된다. 

-> 그런데 frontRigthTire 와 backLeftTire를 HankookTire와 KumhoTire로 교체할 필요성이 생겼다면?

Car myCar = new Car();
myCar.frontRightTire = new HankookTire();
myCar.backLeftTire = new KumhoTire();
myCar.run();

-> 타입이 Tire인 frontRightTire와 backLeftTire에 자식 객체인 HankookTire와 KumhoTire를 저장할 수 있다. 

 

if) 위에서 Car 클래스에 있는 run 메소드의 내용이 이렇다면..

void run(){
	frontLeftTire.roll();
    frontRightTire.roll();
    BackLeftTire.roll();
    BackRightTire.roll();
 }

-> frontRightTire와 backLeftTire 를 교체하기 전까지는 Tire 객체의 roll 메소드가 호출되지만 이제는 HanKookTire와 KumhoTire의 재정의된 roll 메소드가 호출된다. 

 

--->이와 같이 Car 클래스의 run() 메소드의 수정 없이도 자동 타입 변환을 이용하여 Tire 필드값을 교체함으로써 다양한 roll()메소드의 실행 결과를 얻게 된다. 이것이 바로 필드의 다형성이다. 

 

 

3) 하나의 배열로 객체 관리 

- 이전 예제에서 Car 클래스에 4개의 타이어 객체를 4개의 필드로 각각 저장했다. 동일한 타입의 값들은 배열로 관리하는 것이 유리하다. 

class Car{
	Tire[] tires = {
    	new Tire("앞왼쪽",6),
        new Tire("앞오른쪽",2),
        new Tire("뒤왼쪽",3),
        new Tire("뒤오른쪽",4)
       }
   }

-> 만약 앞오른쪽 타이어를 금호 타이어로 교체하고 싶다

-> tires[1] = new KumhoTire("앞오른쪽", 13);

 

 

 

4)매개변수의 다형성 

-자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다. 

- 메소드의 매개변수가 A 타입의 변수를 허용한다면, 자동 타입 변환을 통해 A타입을 상속하고 있는 자식 객체들이 모두 올 수 있다. 

- 또한, 매개변수로 자식 객체가 오고, 만약 그 자식 클래스가 부모 클래스의 메소드를 오버라이딩 했다면 해당 메소드 호출시 오버라이딩한 메소드의 내용이 호출된다. 

 

 

5)강제 타입 변환(Casting)

- 강제 타입 변환은 부모 타입을 자식 타입으로 변환하는 것을 말한다. 

-단, 자식 타입이 부모 타입으로 자동 변환 후, 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다. 

자식클래스 변수 = (자식 클래스) 부모클래스타입;

-> 여기서 부모클래스타입은 자식 인스턴스를 품고 있어야한다. (즉 자식 타입이 부모 타입으로 변환된 상태)

-자식 타입이 부모 타입으로 자동 변환되면, 부모 클래스의 필드와 메소드만 사용 가능하다는 제약이 따른다. 

 만약 꼭 자식 타입의 필드와 메소드를 사용해야 한다면 위와 같이 (자식 클래스)로 강제 형변환을 해주면 된다. 

 

 

6)객체 타입 확인(instanceof)

-부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하는 방법? instanceof 연산자

boolean result = 좌항(객체) instanceof 우항(타입)
public void method(Parent parent){
	if(parent instanceof Child){
    	Child child = (Child)parent;
     }
 }

 

 

 

7. 추상 클래스

1)추상 클래스의 개념

-사전적 의미로 추상은 실체 간의 공통되는 특성을 추출한 것을 말한다. 

-클래스에도 추상 클래스가 존재한다. 객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 하고, 이 클래스들의 공통적인 특성을 추출해서 선언한 클래스를 추상 클래스라고 한다. 

-추상 클래스(부모) - 실체 클래스(자식)  --> 실체 클래스는 추상 클래스의 모든 특성을 물려 받고, 추가적인 특성을 가질 수 있다. 

-추상 클래스는 new로 직접 객체 생성을 하지 못하고 오직 extends 뒤에만 올 수 있는 클래스이다. 

 

 

2)추상 클래스의 용도

-왜 추상클래스를 사용할까?

 -> 실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적

 -> 실체 클래스를 사용할 때 시간을 절약 

 

3)추상 클래스의 선언

- 클래스 선언 앞에 abstract를 붙이면 된다. 

-추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드를 선언할 수 있다. 

-new 연산자로 객체를 직접 생성하지는 못하지만, 자식 객체가 생성될때 super(); 를 호출해서 추상 클래스 객체를 생성하므로 생성자는 필수이다. 

 

 

public abstract class Phone{
	//필드
    public String owner;
    
    //생성자
    public Phone(String owner){
    this.owner = owner;
    }
    
    //메소드
    public void turnOn(){
    	System.out.println("폰 전원을 킵니다");
       }
       
    public void turnOff(){
    	System.out.println("폰 전원을 끕니다");
      }
    }

-> 추상 클래스 Phone

 

public class SmartPhone extends Phone {
	//생성자
    public SmartPhone(String owner){
    	super(owner);
       }
       
    //메소드
    public void internetSearch(){
    	System.out.println("인터넷 검색을 합니다.");
      }
  }
 }

-> 추상 클래스 Phone 을 상속하고 있는 실체 클래스 SmartPhone

 

 

public class PhoneExample{
	public static void main(String[] args){
    	//Phone phone = new Phone(); //에러
        
        SmartPhone smartphone = new SmartPhone("홍길동");
        
        smartphone.turnOn(); //추상 클래스의 메소드 사용 
        smartphone.internetSearch();
        smartphone.turnOff(); //추상 클래스의 메소드 사용 
     }
 }

 

 

4) 추상 메소드와 오버라이딩

-추상 클래스는 추상 메소드를 선언할 수 있다. 추상 메소드는 추상 클래스에서만 선언이 가능하다. 

- abstract 키워드가 붙어 있고 메소드 중괄호{} 가 없다. 

 

public abstract class Animal{
	public abstract void sound();
 }

-> Animal 추상 클래스를 생성하고 추상 메소드 sound를 선언했다. 

-> Animal 클래스를 상속하는 실체 클래스들은 무조건 sound 추상 메소드를 재정의해야한다.