본문 바로가기
자바

[Java] 자바 상속의 특징 - extends, super, 오버라이딩, instanceof, 추상 클래스와 메소드, final

by 책 읽는 개발자_테드 2021. 8. 12.
반응형

상속이란?

상속은 부모가 자식에게 물려주는 행위다.

객체 지향 프로그램에서도 부모(상위) 클래스의 멤버를 자식(하위) 클래스에 물려주어 자식 클래스가 갖고 있는 것처럼 사용할 수 있다.

 

상속의 장점

- 코드 중복 감소(이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만드므로)

- 유지 보수 시간 감소(부모 클래스의 수정으로 모든 자식 클래스들의 수정 효과를 가져오기 때문에 )

 

자바에서 상속을 구현하는 방법

: 자바의 extends 예약어

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

 

아래는 Computer 클래스와 이를 상속하는 Mac 클래스 코드다.

public class Computer {
   int price;
   public void setPrice(int price){
       this.price = price;
   }
   public int getPrice() {
       return price;
   }
}
public class Mac extends Computer{
   boolean touchPad;
   boolean hasTouchPad(){
       return touchPad;
   }
}

 

위 코드를 다음과 같이 사용할 수 있다.

public class Main {
   public static void main(String[] args){

       Mac myComputer = new Mac();
       myComputer.price = 2_000_000;       //Computer에서 물려받은 필드
       int price = myComputer.getPrice();  //Computer에서 물려받은 메스도

       myComputer.touchPad = true;                     //Mac에 추가한 필드
       boolean touchPad = myComputer.hasTouchPad();    //Mac에 추가한 메소드
   }
}

 

UML(Unified Modeling Language)를 통한 상속 표현

- 속이 빈 삼각형 화살표로 자식 -> 부모 클래스를 가리키기 <-> 참조는 보통 그냥 직선으로 관계 표시

자바 상속의 제한

 

✏️  다중 상속 불가능

 

✏️ 상속을 해도 부모 클래스의 모든 필드와 메소드들을 물려받는 것은 아니다. 다음과 같은 경우 상속이 제한된다.

 

1. 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드

2. 부모 클래스와 자식 클래스가 다른 패키지에 존재하며, default 접근 제한을 갖는 필드와 메소드

 

super 키워드, 부모 생성자 호출

 

자바에서 자식 객체를 생성하면, 부모 객체가 먼저 생성된 후 자식 객체가 생성된다.

 

예시

Mac myCumputer = new Mac();

모든 객체는 클래스의 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아니다. 그렇다면 부모 생성자는 어디서 호출된 것일까?

부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다. 

 

부모 생성자가 명시적으로 호출되지 않으면 컴파일러는 자식 생성자의 첫 줄에 super()라는 메소드를 추가한다.

super() 메소드는 부모의 기본 생성자를 호출한다.

(기본생성자: 프로그래머가 정의한 생성자가 없는, 자동적으로 컴파일러에 의해 생성되는 생성자)

public Mac(){
	super();
}

 

명시적으로 부모 생성자를 호출하고 싶다면 다음과 같이 작성한다. 이때 super()는 반드시 자식 클래스의 생성자 첫 줄에 선언한다.

자식클래스(매개변수, ...){
	super(매개값, ...);
}

 

주의사항

부모 클래스에 기본 생성자가 없고, 매개 변수가 있는 생성자만 있다면 자식 생성자의 첫 줄에  반드시 부모 생성자 호출을 위해 super(매개값, ...)를 명시적으로 호출해야 한다. 그렇지 않으면 컴파일 에러가 발생한다.

public class Computer {

   int price;

   Computer(int price){
       this.price = price;
   }

   public int getPrice() {
       return price;
   }

}

public class Mac extends Computer{

   boolean touchPad = true;

   Mac(int price){
       super(price);
   }

   boolean hasTouchPad(){
       return touchPad;
   }

}

 

메소드 오버라이딩

상속받은 부모 클래스의 메소드를 재정의하여 사용하는 것 (덮어 씀)

 

사용이유

부모 클래스의 어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수 있다. 이럴때 메소드 오버라이딩 기능을 사용하여 상속된 일부 메소드를 자식 클래스에서 수정해서 사용할 수 있다. 메소드가 오버라이딩되면 부모 객체의 메소드는 숨겨진다. 그리고 자식 객체에서 메소드를 호출하면 오버라이딩된 자식 메소드가 호출된다.

 

예시

public class Computer {
   public void getInfo() {
       System.out.println("컴퓨터입니다.");
   }
}
public class Mac extends Computer{
   @Override
   public void getInfo() {
       System.out.println("맥 컴퓨터입니다.");
   }
}
public class Main {
   public static void main(String[] args){
       Mac myComputer = new Mac();
       myComputer.getInfo();
   }
}

 

오버라이딩된 메소드 위의 @Override 어노테이션은 생략할 수 있지만, 이것을 붙여주면 해당 메소드가 정확히 오버라이딩된 것인지 컴파일러가 체크하여 개발자의 실수를 줄여준다.

 

메소드를 오버라이딩 규칙

1. 부모의 메소드와 동일한 시그니처(메소드 이름과 매개 변수 리스트 조합), 리턴타입을 갖는다

2. 접근 제한을 더 강하게(축소) 오버라이딩할 수 없다

(부모가 public인데 오버라이딩하는 자식이 private일 수는 없다. 반대(확대)는 가능)

3. 새로운 예외(Exception)를 throws할 수 없다

 

오버라이딩된 부모 메소드 호출

 

자식 클래스 내부에서 오버라이딩된 부모 클래스의 메소드를 호출해야 한다면, 부모 객체를 참조하고 있는 super 키워드를 사용한다.

 

예시

public class Mac extends Computer{
   @Override
   public void getInfo() {
       System.out.println("맥 컴퓨터입니다.");
   }
   public void getParentInfo(){
       super.getInfo();
   }
}
public class Main {
   public static void main(String[] args){
       Mac myComputer = new Mac();
       myComputer.getParentInfo ();
   }
}

 

 

참조 자료형의 형 변환

public class Computer {
   public void getInfo() {
       System.out.println("컴퓨터입니다.");
   }
}
public class Mac extends Computer{
   @Override
   public void getInfo() {
       System.out.println("맥 컴퓨터입니다.");
   }
}

위 코드 처럼 상속 관계가 성립되면, 부모 클래스로 선언된 변수를 자식 클래스로 초기화할 수 있다. 

Computer computer = new Mac();

역은 성립하지 않는다. 반대로 해보면 컴파일 에러가 발생한다. 자식 클래스에서 호출할 수 있는 필드를 부모 클래스에서 호출할 수 있다는 보장이 없기 때문이다.

 

물론 불가능한 것은 아니다. 자식 클래스로 선언된 변수를 부모 클래스로 초기화 하려면 다음과 같은 과정을 거쳐야한다. 

1. 부모 클래스 변수에 자식 클래스 객체를 대입

2. 위의 부모 클래스 변수를 형변환하여 자식 클래스 변수를 초기화

, 부모  타입의 실제 객체를 자식 타입으로 만든다.

public class ComputerExam {
    public static void main(String args[]){
        Mac mac = new Mac();
        Computer computer = mac;
        Mac mac2 = (Mac)computer;
    }
}

 

instanceof

- 인스턴스의 실제 타입을 알아보는 자바 예약어

- 사용법: instanceof 앞에 객체를 뒤에 클래스(타입)을 지정하여 사용하며, boolean 형태의 true와 false로 결과를 나타냄

- 주의사항: '자식클래스의 객체 instanceof 부모 클래스'를 확인할 경우 true를 반환

 

예시

    public static void main(String args[]){
        Computer computer = new Computer();
        Mac mac = new Mac();

        System.out.println(computer instanceof Computer);
        System.out.println(computer instanceof Mac);
        System.out.println(mac instanceof Computer);
        System.out.println(mac instanceof Mac);
    }
}

결과

 

다형성(Polymorphism)

 

단어의 형태소를 분석해보면 poly(많은), morphs(형태)라는 뜻이다.

즉, 형태가 다양하다는 의미다. 

()https://scshim.tistory.com/55

 

final 키워드

- 해당 선언이 최종 상태이고 수정될 수 없음을 의미

- 클래스, 필드, 메소드 선언 시에 사용

 

final 키워드가 클래스, 메소드, 필드  선언에 사용될 경우 해석이 조금씩 달라진다.

 

✏️ 클래스를 선언에 사용되는 final 키워드: 이 클래스는 최종적인 클래스이므로 더 이상 상속할 수 없다. 즉, final 클래스는 자식 클래스를 만들 수 없다.

 

public final class 클래스{}

 

✏️메소드를 선언할 때 final 키워드:  이 메소드는 최종적인 메소드이므로 오버라이딩 할 수 없다.

 

public final 리턴타입 메소드 (매개변수, ...) {}

 

✏️필드 선언 시때 final 키워드: 초기값 설정 후, 더이상 값을 변경할 수 없다.

 

public class Computer {

   final int price; // final 필드는 선언시 초기화 해야함, 오류 발생
   final double version = 1.1;

   public void setPrice(int price){
       this.price = price; // final 필드는 변경 불가, 오류 발생
   }

}

 

 

추상 클래스

 

사전적 의미로 추상(abstract)은 실체 간에 공통되는 특성을 추출한 것을 말한다. 객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면, 이 클래스들의 공통적인 특성을 추출해 선언한 클래스를 추상 클래스라고 한다. 

 

추상 클래스와 실체 클래스는 상속 관계를 갖는다. 추상 클래스가 부모, 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을 물려받고 추가적인 특성을 가질 수 있다. 여기서 특성이란 필드와 메소드를 말한다.

 

추상 클래스를 선언할 때는 클래스 선언에 abstract를 붙여야 한다.

 

public abstract class 클래스{
	//필드
    	//생성자
    	//메소드
}

 

추상 클래스도 일반 클래스처럼 필드, 생성자, 메소드 선언을 할 수 있다. 하지만 객체를 직접 생성해서 사용할 수는 없다. 즉, new 연산자를 사용해서 인스턴스를 생성하지 못한다.

 

Computer myComputer = new Computer(); // 오류

 

new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super()를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 한다.

 

public abstract class Computer {

   // 필드
   public String owner;

   //생성자
   public Computer(String owner){
       this.owner = owner;
   }

   //메소드
   public void turnOn(){
       System.out.println("전원 On");   
   }

}

 

추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다. 즉, extends 뒤에만 올 수 있다.

 

public class Mac extends Computer{

}

 

추상 클래스의 용도

 

✏️1.  실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적

 

실체 클래스를 설계하는 사람이 여러 사람일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있다. 

 

예를 들어 소유자 이름을 저장하는 필드를 Mac에서는 owner로, Window에서 user라고 할 수 있다. 그리고 전원을 켜다라는 메소드를 Mac에서는 turnOn으로 설계하고, SmartPhone에서는 powerOn()이라고 설계할 수 있다.

 

이것 보다는 Computer라는 추상 클래스에 owner 필드와 turnOn() 메소드를 선언하고, Mac과 Window에 Computer를 상속함으로써 필드와 메소드 이름을 통일할 수 있다.

 

✏️2. 실체 클래스를 작성할 때 시간을 절약

 

공통적인으로 사용되지만 미리 만들어 놓아도 전혀 문제가 없는 필드와 메소드는 추상 클래스에 모두 선언해 두고, 실체 클래스마다 다른 점만 구현하면 코드 작성 시간을 절약할 수 있다.

 

추상 메소드와 오버라이딩

 

추상 클래스를 상속하는 모든 실체들이 가지고 있는 메소드의 실행 내용이 동일하다면 추상 클래스에 메서드를 작성하는 것이 좋을 것이다.  하지만 메소드의 선언만 통일화하고, 실행 내용은 실체화 클래스마다 달라야 하는 경우가 있다. 이런 경우 추상 클래스는 추상 메소드를 선언할 수 있다.

 

[public | protected] abstract 리턴타입 메소드명 (매개변수, ...);

 

일반 메소드 선언과의 차이점은 abstract 키워드가 붙어 있고 메소드 중괄호 {}가 없다는 점이다. 다음은 Computer 추상 클래스를 상속한 Mac, Window 실체 클래스가 추상 메소드 turnOn()을 재정의하는 예제이다.

 

public abstract class Computer {

   public abstract void turnOn();
}

 

public class Window extends Computer{

   @Override
   public void turnOn() {
       System.out.println("윈도우 컴퓨터를 켭니다.");

   }

}

 

public class Mac extends  Computer{

   @Override
   public void turnOn() {
       System.out.println("맥 컴퓨터를 켭니다.");
   }
   
}

 

이렇게 정의한 메소드를 다양한 방식으로 호출할 수 있다. 

 

1. Mac, Window 객체를 저장하는 변수로 호출한다.

2. 부모타입(Computer)을  자식타입(Mac, WIndow)으로 변환하여 호출한다.

3. 부모 타입 매개 변수에 자식 객체를 대입해서 메소드의 다형성을 적용해 호출한다.

 

public class Main {

   public static void main(String[] args){

       Mac mac = new Mac();
       Window window = new Window();
       mac.turnOn();
       window.turnOn();

       //변수의 자동 타입 변환성
       Computer computer = null;
       computer = new Mac();
       computer.turnOn();
       computer = new Window();
       computer.turnOn();
      
       //메소드의 다형성
       tunOnCumputer(new Mac());
       tunOnCumputer(new Window());
      
   }

   public static void tunOnCumputer(Computer computer){
       computer.turnOn();

   }

}

 

 

출처: 이것이 자바다, 자바의 신

반응형

댓글