Java 파트7-1. 상속

1. 상속


  • 객체 지향 프로그램에서 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있다.
  • 이미 개발된 클래스를 재사용하여 새로운 클래스를 만들기에 중복되는 코드를 줄일 수 있다.
  • 부모 클래스의 한번의 수정으로 모든 자식 클래스까지 수정되는 효과가 있어 유지보수 시간이 줄어든다.

그림1

2. 클래스 상속


  • 자식 클래스 선언 시 부모 클래스를 선택
  • extends 뒤에 부모 클래스 기술
  • 여러 개의 부모 클래스를 상속할 수 없음 -> 한 개의 부모 클래스만 상속 가능
  • 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외
  • 부모와 자식 클래스가 다른 패키지에 존재할 경우 default 접근 제한된 필드와 메소드 역시 제외
class 자식클래스명 extends 부모클래스명 {

}

// 예시
// 부모 클래스
package sec07.exam01.pack1;

public class CellPhone {
	String model;
	String color;
	private int num;
	public int warranty;
	
	void turnOnDmb() {
		System.out.println("티비가 켜졌다.");
	}
	
	void turnOffDmb() {
		System.out.println("티비가 꺼졌다.");
	}
}

// 자식 클래스
package sec07.exam01.pack1;

public class DmbCellPhone extends CellPhone{
	
	// 생성자
	DmbCellPhone(String model,String color){
		// 상속되었으므로 필드 선언 없이 사용 가능
		this.model = model;
		this.color = color;		
	}
	
}

// 실행 메인 클래스
package sec07.exam01.pack1;

public class CellPhoneExam {

	public static void main(String[] args) {
		DmbCellPhone ex1 = new DmbCellPhone("galaxy","red");
		
		// DmbCellPhone은 CellPhone으로부터 상속받은 내용을 사용가능
		System.out.println(ex1.model);
		ex1.turnOnDmb();
		
		// num은 private이라 상속이 안됬으므로 컴파일에러
		System.out.println(ex1.num); 
		

	}

}

// 다른 패키지
package sec07.exam01.pack2;

// 다른 패키지에서 있던 것을 사용을 원하므로 import
import sec07.exam01.pack1.DmbCellPhone;

public class Another {

	public static void main(String[] args) {
		// model, color은 default 접근 제한이므로 다른 패키지에서 상속이 불가능하다.
		// 다른 패키지이므로 사용하고 싶다면 public을 붙였어야했다.
		DmbCellPhone ex1 = new DmbCellPhone("galaxy","red");

	}

}


3. 부모 생성자 호출


자식 객체를 생성할 때 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성된다. 위에서 상속받은 자식객체를 DmbCellphone demCellPhone = new DmbCellPhone(); 와 같이 작성했는데 사실은 자식 생성자 안에 부모의 생성자를 호출하는 super()라는 코드가 컴파일 단계에서 자동으로 컴파일러가 넣어 준다. 따라서 먼저 부모 생성자가 호출되서 부모 객체가 만들어진다. 즉, 자식 생성자 호출 -> super -> 부모 생성자 호출과 부모 객체 생성 -> 자식 객체 생성
만약에 부모 생성자가 매개변수가 있다면 명시적으로 부모 생성자에 매개값을 넣어줘야 한다.

// 부모 클래스
package sec07.exam01.pack3;

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

// 자식 클래스
package sec07.exam01.pack3;

// Student에 밑줄이 그어지면서 컴파일 오류가 발생
public class Student extends People{ 
	
}
// 이유
package sec07.exam01.pack3;

public class Student extends People{ 
	// 기본적 생성자를 작성하지 않으면 자식 클래스는 아래와 같이 생성자가 생성된다.
	public Student() {		
    // 부모 생성자를 보면 인자가 필요한데 인자가 없으므로 컴파일 에러가 생긴 것이다.
		super(); 
	}
	
}

// 해결
 package sec07.exam01.pack3;

public class Student extends People {
	public int studentNo;

	// 자식 클래스 생성자에 인자를 받아서 그 인자를 부모 생성자를 호출하는 super의 인자로 대입해준다.
	public Student(String name, String ssn, int studentNo) {		
		super(name, ssn);
		// 추가적 학생 정보 저장
		this.studentNo = studentNo;
	}
}

// 메인 실행 클래스
package sec07.exam01.pack3;

public class StudentExam {

	public static void main(String[] args) {
		Student student = new Student("홍길동","123-445",10);
		System.out.println("name : "+student.name);
		System.out.println("ssn : "+student.ssn);
		System.out.println("studentNo : "+student.studentNo);

	}
}


부모 클래스(super class)의 기본 생성자와 super

위에서 설명했듯이, 상속받은 자식 클래스(sub class)의 인스턴스가 만들어질 때, 부모의 생성자를 호출한다. 그런데 이때, 부모의 생성자가 기본 생성자(인자가 없는)가 없고, 파라미터가 있는 생성자일 경우에는 어떻게 될까? 오류가 난다. 기본적으로 호출되는 부모 생성자는 특별한 지시가 없다면 기본 생성자를 호출한다. 하지만 super을 이용하면 부모 클래스(super class)의 생성자를 선택할 수 있다. 자식 클래스의 생성자 안에 super()을 사용하면 된다. 주의해야할 점은 반드시 생성자의 첫 줄에 super을 사용해야 한다는 점이다.

class A{
	public A(){	}
	public A(int x){}

}

class B extends A{
	public B(){	}
	public B(int x){
		super(x);
	}
}

b = new B(5);

B의 인스턴스를 만들면 파라미터가 있는 B의 생성자가 호출된다. super의 파라미터 값으로 생성자의 인지를 넣어주면 부모 클래스 A의 기본 생성자가 아니라 파라미터가 1개 있는 생성자를 호출하게 된다.


4. 메소드 재정의


  • 부모 클래스의 메소드가 자식 클래스에서 사용하기에 부적합할 경우 자식 클래스에서 수정하여 사용
  • 메소드 재정의 방법
    • 부모 메소드와 동일한 시그니처를 가져야 함
    • static, private, final 메서드는 서브 클래스에서 오버라이딩할 수 없다.
    • 부모가 가진 접근제한자 보다 넓은 범위는 가능하나 더 좁은 범위로는 재정의 할 수 없다.
      • 즉, 부모가 default인 경우 자식이 public으로 수정이 가능하나, 자식이 private으로 수정은 불가능
    • 새로운 예외를 throws 할 수 없음
  • 동적 바인딩 : 실행할 메서드를 컴파일 시에 결정하지 않고 실행 시(run time)에서 결정한다.
    • 메소드가 재정의될 경우 부모 객체 메소드가 숨겨지며, 자식 객체에서 메소드를 호출하면 재정의된 자식 메소드가 호출된다.
  • @Override는 재정의가 제대로 되었는지 컴파일단계에서 검사해주는 기능을 한다.
    • 재정의한 메소드 위에 작성해주면 개발자가 메소드를 재정의한 것이라 인지하고 부모에 똑같은 메소드가 있는지 확인해주고 없다면 컴파일 에러를 발생시킨다.
  • 이클립스 기준 Ctrl + Space 누르면 부모의 내용을 쉽게 가져와 수정할 수 있다.(복붙의 시간을 줄여준다.)

그림2

// 부모 클래스
package sec07.exam01.pack4;

public class Calculator {
	double areaCircle(double r) {
		System.out.println("Calculator 객체의 areaCircle() 실행");
		return 3.14159 * r * r;
	}
}

// 자식 클래스
package sec07.exam01.pack4;

public class Computer extends Calculator{	
	// 재정의
	// 부모가 default이므로 재정의시 public은 가능
 	@Override
	double areaCircle(double r) {
		System.out.println("Calculator 객체의 areaCircle() 실행");
		return Math.PI * r * r;
	}
}

// 실행 메인 클래스
package sec07.exam01.pack4;

public class ComputerExam {
	public static void main(String[] args) {
	
		int r = 10;
		
		Calculator calculator = new Calculator();
		System.out.println("원 면적: "+ calculator.areaCircle(r));
		
		Computer computer = new Computer();
		System.out.println("원 면적: "+ computer.areaCircle(r));
				
	}	
}

부모 메소드 호출

메소드가 재정의가 되면 자식 객체 내부에서는 항상 재정의된 메소드가 호출되지만, 경우에 따라서 부모 메소드를 호출해야 하는 경우가 있다. 부모 메소드를 호출하려면 자식 내부에서만 가능하다. 명시적으로 super 키워드 를 붙여서 super.메소드명 으로 부모 메소드를 호출한다.

super.부모메소드();

그림3

어떤 메소드가 실행될 때 부모에 있는 내용에다가 뒤에 뭔가 더 실행하도록 꾸미고 싶을 때 사용된다.

// 부모 클래스
package sec07.exam01.pack5;

public class AirPlane {
	public void land() {
		System.out.println("착륙");
	}
	public void fly() {
		System.out.println("비행");
	}
	public void takeOff() {
		System.out.println("이륙");
	}

}

// 자식 클래스
package sec07.exam01.pack5;

public class SuperSonicAirPlane extends AirPlane{
	public static final int NORMAL = 1;
	public static final int SUPERSONIC = 2;
	
	public int flyMode = NORMAL;
	
	// ctrl + space로 빠르게 불러오기
	@Override
	public void fly() {
		if (flyMode == SUPERSONIC) {
			System.out.println("초음속비행");
		} else {
			// 부모의 메소드 호출
			super.fly();
			// 자식 내부에서는 super.을 어디서든 사용 가능
			// 자식을 벗어나서는 super. 사용 불가!!
		}
	}
}

// 메인 실행 클래스
package sec07.exam01.pack5;

public class SuperSonicExam {

	public static void main(String[] args) {
		SuperSonicAirPlane sa = new SuperSonicAirPlane();
		 sa.takeOff();
		 sa.fly();
		 sa.flyMode = SuperSonicAirPlane.SUPERSONIC;
		 sa.fly();
		 sa.flyMode = SuperSonicAirPlane.NORMAL;
		 sa.fly();
		 sa.land();
	}
}


5. final 클래스와 final 메소드


final 키워드

  • 해당 선언이 최종 상태이며 수정될 수 없음 을 의미
  • 클래스 및 메소드 선언 시 final 키워드를 사용하면 상속과 관련됨

final 클래스

  • final 클래스느 부모가 될 수 없다. -> 어떠한 클래스도 final 클래스를 상속받을 수 없다.
// 예시
public final class 클래스명 {}
public final class String{} // 우리가 문자열 저장하기 위해 사용하는 String이 final로 선언되어있다.
public class NewString extends String{} // 불가능! final은 부모 클래스가 될 수 없다.

재정의할 수 없는 final 메소드

  • 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없음 -> 상속받은 자식 클래스는 부모의 final 메서드를 재정의없이 그대로 사용해야 한다.
public final 리턴타입 메소드명(매개인자) {..}


6. 정리하기


  • 상속 : 부모 클래스의 필드와 메소드를 자식 클래스에서 사용할 수 있도록 한다.
  • 메소드 재정의 : 부모 메소드를 자식 클래스에서 다시 정의하는 것을 의미
  • final 클래스 : final 클래스는 부모 클래스로 사용할 수 없다.
  • final 메소드 : 자식 클래스에서 재정의할 수 없는 메소드

cf) 메서드 오버로딩 vs 메서드 오버라이딩

요소메서드 오버로딩메서드 오버라이딩
선언같은 클래스나 상속 관계에서 동일한 이름의 메서드 중복 작성서브 클래스에서 슈퍼 클래스에 있는 메서드와 동일한 이름의 메서드 재작성
관계동일한 클래스 내 혹은 상속 관계상속 관계
목적이름이 같은 여러 개의 메서드를 중복 작성하여 사용의 편리성 향상, 다형성 실현슈퍼 클래스에 구현된 메서드를 무시하고 서브 클래스에 새로운 기능의 메서드를 재정의하려는 목적, 다형성 실현
조건메서드 이름은 반드시 동일, 매개변수의 타입이 다르거나, 개수가 달라야함메서드 이름, 매개변수 타입, 개수, 리턴 타입이 모두 동일하여야함
바인딩정적 바인딩 -> 호출될 메서드는 컴파일 시에 결정동적 바인딩 -> 실행 시간에 오버라이딩된 메서드 찾아 호출

본 포스팅은 ‘혼자 공부하는 자바’를 읽고 공부한 내용을 바탕으로 작성하였습니다.


© 2021. By Backtony