[혼공자] 07-1. 상속

Benjamin·2023년 3월 10일
0

혼공자

목록 보기
21/27

07. 상속

07-1. 상속

객체지향 프로그래밍에서 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있다.
부모 클래스 = 상위 클래스
자식 클래스 = 하위 클래스, 파생 클래스

상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여준다.

예를 들어, field1, field2, method1(), method2() 를 가지는 클래스를 작성한다고 가정하자.
field1, method1()을 가지고있는 클래스가 있다면, 4개를 모두 처음부터 작성하기보다 field2, method2()만 추가작성하는게 효율적이고 개발 시간을 절약해준다.

상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들도 수정되는 효과를 가져오기때문에 유지 보수 시간을 최소화할 수도 있다.

클래스 상속

자식이 부모를 선택한다.
자식 클래스를 선언할 때 어떤 부모 클래스를 상속받을 것인지 결정하고, 선택된 부모 클래스는 extends뒤에 기술한다.

class 자식클래스 extends 부모클래스 {
	//필드
    //생성자
    //메소드
}

상속 특징

  1. 여러개의 부모 클래스를 상속할 수 없다. extends 뒤에는 단 하나의 부모 클래스만 와야한다.
  2. 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.
    그리고 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다.

아래는 예시이다.

package sec01.exam01;

public class CellPhone {
	String model;
    String color;
    
    void powerOn() {System.out.println("전원을 켭니다.");}
}
package sec01.exam01;

public class DmbCellPhone extends CellPhone {
	int channe;
    
    DmbCellPhone(String model, String color, int channel) {
    	this.model = model; //CellPhone 클래스로부터 상속받은 필드
        this.color = color; //CellPhone 클래스로부터 상속받은 필드
        this.channel = channel;
    }
}
package sec01.exam01;

public class DmcCellPhoneExample {
	public static void main(String[] args) {
    	DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10);
        dmbCellPhone.powerOn();
        
    }
}

부모 생성자 호출

현실에서 부모없는 자식 없듯이, 자바에서도 자식객체를 생성하면 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성된다.
다음 코드는 DmbCellPhone 객체만 생성하는것처럼 보이지만 내부적으로 부모인 CellPhone 객체가 먼저 생성되고 자식인 DmbCellPhone 객체가 생성된다.
DmbCellPhone dmbCellPhone = new DmbCellPhone();

이것을 메모리로 보면 다음과 같다.

부모객체를 생성하기 위해 부모 생성자를 어디서 호출하는걸까?
자식 생성자에 숨어있다. 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다.

예를 들어, DmbCellPhone의 생성자가 명시적으로 선언되지 않았다면 컴파일러는 다음과같이 기본 생성자를 생성한다.

public DmbCellPhone() {
	super();
}

첫 줄에 super()가 추가된것을 볼 수 있는데, super()는 부모의 기본 생성자를 호출한다.

public CellPhone() {
}

만약 직접 자식 생성자를 선언하고, 명시적으로 부모 생성자를 호출하고 싶다면 다음과 같이 작성하면 된다.

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

super(매개값,...)는 매개값의 타입과 일치하는 부모 생성자를 호출한다.
만약 매개값의 타입과 일치하는 부모 생성자가 없을 경우 컴파일 에러가 발생한다.

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

package sec01.exam02;

public class People {
	public String name;
    publuc String ssn;
    
    public Poeple(String name, String ssn) {
    	this.name = name;
        this.ssn = ssn;
    }
}

People 클래스는 기본 생성자가 없고, name, ssn을 매개값으로 받아 객체를 생성시키는 생성자만 있다.
그렇기때문에 People을 상속하는 Student 클래스는 생성자에서 super(name, ssn)으로 People 클래스의 생성자를 호출해야한다.

package sec01.exam02;

public class Student extends People {
	public int studentNo;
    
    public Student(String name, String ssn, int studentNo) {
    	super(name, ssn); //부모 생성자 호출 
        this.studentNo = studentNo;
    }
}

부모생성자 호출하는 라인을 주석처리하면, "Implicit super constructor People() is undefined. Must explicitly invoke another constructor" 컴파일 에러가 발생한다.
부모의 기본 생성자가 없으니 다른 생성자를 명시적으로 호출하라는 의미이다.

메소드 재정의

어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수도 있다.
이 경우 상속된 일부 메소드는 자식 클래스에서 다시 수정해서 사용해야한다.
이런 경우를 위해 메소드 재정의(오버라이딩)기능이 있다.

메소드 재정의 방법

자식 클래스에서 부모 클래스의 메소드를 다시 정의하는것을 말한다.
규칙에 주의해서 작성해야한다.

  • 부모의 메소드와 동일한 시그너처(리턴 타입, 메소드 이름, 매개 변수 목록)를 가져야한다.
  • 접근 제한을 더 강하게 재정의할 수 없다. (반대는 가능하다)
  • 새로운 예외(Exception)를 throws할 수 없다.

메소드가 재정의 되었다면 부모 객체의 메소드는 숨겨진다.
자식 객체에서 메소드를 호출하면 재정의 된 자식 메소드가 호출된다.

package sec01.exam03;

public class Calculator {
	double areaCircle(double r) {
    	return 3.14159*r*r;
    }
}
package sec01.exam03;

public class Computer extends Calculator {
	@Override
    double areaCircle(double r) { //재정의 
    	return Math.PI*r*r;
    }
}

@Override 어노테이션은 생략해도 좋으나, 이것을 붙여주면 areaCircle() 메소드가 정확히 재정의된 것인지 컴파일러가 확인하기 때문에 개발자의 실수를 줄여준다.
예를 들어, areaCircl() 처럼 끝에 e를 빼먹으면 컴파일 에러가 발생한다.

부모 메소드 호출

메소드를 재정의하면, 부모 메소드는 숨겨지고 재정의된 자식 메소드만 사용된다.
그러나 자식 클래스 내부에서 재정의된 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super 키워드를 붙여서 부모 메소드를 호출할 수 있다.

super.부모메소드();

super은 부모 객체를 참조하고있기떄문에 부모 메소드에 직접 접근할 수 있다.

final 클래스와 final 메소드

final 키워드는 클래스,필드,메소드를 선언할 때 사용할 수 있는데, 해당 선언이 최종 상태이고 결코 수정될 수 없음을 뜻한다.
어디에 쓰냐에따라 해석이 조금 달라진다.

이전에 살펴본것처럼 필드를 선언할 때 final이 지정되면 초기값 설정 후 더 이상 값을 변경할 수 없다.

클래스와 메소드를 선언할 때 final 키워드가 지정되면 상속과 관련이 있다는 의미이다.
어떤 효과가 날까?

상속할 수 없는 final 클래스

클래스를 선언할 때 final 키워드를 class 앞에 붙이면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 된다.
즉 final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다는것이다.
public final class 클래스 {}

대표적으로 String클래스가 있다.
public final class String {...}
그래서 public class NesString extends String{...} 과 같이 자식 클래스를 만들 수 없다.

재정의할 수 없는 final 메소드

메소드를 선언할 때 final 키워드를 붙이면 최종적인 메소드이므로 재정의할 수 없는 메소드가 된다.
즉, 부모 클래스를 상속해서 자식 클래스를 선언할 때 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다는 것이다.
public final 리턴타입 메소드( [매개변수,...]) {...}

protected 접근제한자

상속과 관련있는 protected가 어떤 역할을 하는지 알아보자.

같은 패키지에서는 default와 같이 접근 제한이 없지만, 다른 패키지에서는 자식 클래스만 접근을 허용한다.

protected는 필드와 생성자, 메소드 선언에 사용될 수 있다.

다음 예시를 보자.

package sec01.exam07.pack1;

public class A {
	protected String field;
    
    protected A() {
    }
    
    protected void method() {
    }
}
package sec01.exam07.pack1;

public class B {
	public void method() {
    	A a= new A(); //접근 가능 
        a.field = "value"; //접근 가능 
        a.method(); //접근 가능 
    }
}

B클래스는 A클래스와 동일한 패키지에 있다.
따라서 B클래스의 생성자와 메소드에서는 A클래스의 protected 필드, 생성자, 메소드에 얼마든지 접근 가능하다.

package sec01.exam07.pack2;

import sec01.exam07.pack1.A;

public class C {
	publuc void method() {
    	//A a = new A(); //접근 불가능 
        //a.field = "value"; //접근 불가능 
        //a.method(); //접근 불가능 
    }
}

C클래스는 A클래스와 다른 패키지에 있다.
따라서 C클래스의 생성자와 메소드에서는 A클래스의 protected 필드, 생성자, 메소드에 접근할 수 없다.

package sec01.exam07.pack2;

import sec01.exam07.pack1.A;

public class D extends A {
	public D() {
    	super(); //접근 가능
        this.field = "value"; //접근 가능
        this.method(); //접근 가능
    }
}

D클래스는 A클래스와 다른 패키지에 있다.
하지만 D는 A의 자식 클래스이다. 따라서 A클래스의 protected 필드, 생성자, 메소드에 접근 가능하다.
단, new 연산자를 사용해 생성자를 직접 호출할수는 없다. 자식 생성자에서 super()로 A생성자를 호출할 수 있다.

궁금한 점

D 클래스의 생성자에서 아래코드부분을 보면

this.field = "value"; //접근 가능
this.method(); //접근 가능

this. 이렇게 되어있는데, 꼭 이게 필요한건가? 왜 책에서 썼을까?

package sec01.exam07.pack2;

import sec01.exam07.pack1.A;

public class D extends A {
	public D() {
    	super(); //접근 가능
        field = "value"; //접근 가능
        System.out.println(this.field);
        method(); //접근 가능
    }
}

이렇게 this.없이 실행해도 정상적으로 잘 동작한다.


출처
혼자 공부하는 자바

0개의 댓글