[Java] 7-1. 상속

Woohyun Shin·2021년 7월 23일
0

Java

목록 보기
1/10

객체 지향 프로그래밍에서의 '상속(inheritance)'은 현실에서 부모가 자식에게 물려주듯 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있다.

이미 필요한 필드와 메소드를 가진 클래스가 있다면, 처음부터 모두 작성하는 것보다 필요한 멤버를 가진 클래스를 상속하여 추가 작성하는 것이 더 효율적이고 개발 시간을 절약해줌 !

  • 또한 상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들도 수정되는 효과가 있어 유지 보수 시간을 최소화.

클래스 상속

프로그램은 현실과는 다르게 자식이 부모를 선택함.

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

  1. 여러 개의 부모 클래스를 상속 X
  2. 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외됨. 또한 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외됨.
package sec03.exam02;

//부모 클래스


public class CellPhone{

//필드
String model;
String color;

//생성자

//메소드
void powerOn() {
		System.out.println("전원을 켭니다.");
	}
void powerOff() {
		System.out.println("전원을 끕니다.");
	}
void bell() {
		System.out.println("벨이 울립니다.");
	}
void sendVoice(String message) {
		System.out.println("자신: "+message);
	}
void receiveVoice(String message) {
		System.out.println("상대방: "+message);
	}
void hangUp() {
		System.out.println("전화를 끊습니다.");
	}
}
package sec03.exam02;

//자식 클래스

public class DmbCellPhone extends CellPhone{

//필드
int channel;
	
//생성자
DmbCellPhone(String model,String color,int channel){
		this.model=model; //CellPhone 클래스로부터 상속받은 필드
		this.color=color; //위와 동일
		this.channel=channel;
	}
	
//메소드
void turnOnDmb() {
		System.out.println("채널 "+channel+"번 DMB방송 수신을 시작합니다.");
	}
void changeChannelDmb(int channel) {
		this.channel=channel;
		System.out.println("채널 "+channel+"번으로 바꿉니다.");
	}
void turnOffDmb() {
		System.out.println("DMB방송 수신을 멈춥니다.");
	}	

}
package sec03.exam02;

public class DmbCellPhoneExample {

	public static void main(String[] args) {
		// TODO 자동 생성된 메소드 스텁
		
		DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰","검정",10);
		
		System.out.println("모델: "+dmbCellPhone.model);
		System.out.println("색상: "+dmbCellPhone.color);
		System.out.println("채널: "+dmbCellPhone.channel);
		
		dmbCellPhone.powerOn();
		dmbCellPhone.bell();
		dmbCellPhone.sendVoice("여보세요.");
		dmbCellPhone.receiveVoice("안녕하세요! 저는 우현이에요.");
		dmbCellPhone.sendVoice("우와 그러시구나.");
		dmbCellPhone.hangUp();
		
		dmbCellPhone.turnOnDmb();
		dmbCellPhone.changeChannelDmb(12);
		dmbCellPhone.turnOnDmb();
		
	}

}

모델: 자바폰
색상: 검정
채널: 10
전원을 켭니다.
벨이 울립니다.
자신: 여보세요.
상대방: 안녕하세요! 저는 우현이에요.
자신: 우와 그러시구나.
전화를 끊습니다.
채널 10번 DMB방송 수신을 시작합니다.
채널 12번으로 바꿉니다.

부모 생성자 호출

현실에서 부모가 있어야 자식이 있듯 자바에서도 자식 객체를 생성하면 부모 객체가 먼저 생성되고, 그 후에 자식 객체가 생성된다.

위 사진과 같이 자식 객체 DmbCellPhone만 생성하는 것처럼 보이지만, 사실 내부적으로 부모 객체인 CellPhone이 먼저 생성되고 자식인 DmbCellPhone 객체가 생성됨.

모든 객체는 클래스의 생성자를 호출해야만 생성되며, 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출됨

자식 클래스의 생성자가 명시적으로 선언되지 않았다면 컴파일러는 다음과 같은 기본 생성자를 생성한다.

public DmbCellPhone(){
	super();
}

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

만약 직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출하고 싶다면 super(메개값, ...)로 작성하면 된다.

이는 매개값의 타입과 일치하는 부모 생성자를 호출하고 만약 매개값의 타입과 일치하는 부모 생성자가 없을 경우 컴파일 에러가 발생 !

super(매개값, ...)가 생략되면 컴파일러에 의해 super()가 자동적으로 추가되므로 부모의 기본 생성자가 존재해야함.(반대로 매개 변수가 있는 생성자만 있다면 반드시 매개값을 넣어서 명시적으로 호출해야한다.)

super(매개값, ...)은 반드시 자식 생성자 첫 줄에 위치해야 한다 ! !

package sec03.exam02;

//부모 클래스

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

//자식 클래스

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

//자식 객체 이용

public class StudentExample {

	public static void main(String[] args) {
		// TODO 자동 생성된 메소드 스텁
		
		Student student = new Student("홍길동","111111-1122121",1);
		
		System.out.println("name : "+student.name);
		System.out.println("ssn : "+student.ssn);
		System.out.println("studentNo : "+student.studentNo);

	}

}

name : 홍길동
ssn : 111111-1122121
studentNo : 1

메소드 재정의

'메소드 재정의'란 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 말한다.

부모 클래스의 메소드가 자식 클래스에서 사용하기 적합하지 않거나 수정해서 사용해야만 한다면 메소드를 재정의를 해야할 필요가 있다.

메소드 재정의 방법

  1. 부모의 메소드와 동일한 시그너처(리턴 타입, 메소드 이름, 매개 변수 목록)를 가져야함.
  2. 접근 제한을 더 강하게 재정의할 수 없음.(큰 범위에서 작은 범위로 제한X, 단 반대는 가능)
  3. 새로운 예외(Exception)를 throws할 수 없음.(예외는 10장에서 학습)
package sec03.exam02;

//부모 클래스

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

}
package sec03.exam02;

//자식 클래스

public class Computer extends Calculator {
	
	@Override
	double areaCircle(double r) {
		System.out.println("Computer 객체의 areaCircle() 실행");
		return Math.PI*r*r;
	}

}

@Override는 생략해도 좋으나, 이것을 붙여주면 메소드가 정확히 재정의된 것인지 컴파일러가 확인하기 때문에 개발자의 실수를 줄여줌.

package sec03.exam02;

public class ComputerExample {

	public static void main(String[] args) {
		// TODO 자동 생성된 메소드 스텁
		Computer myCom=new Computer();
		
		int r=10;
		
		Calculator calculator = new Calculator();
		
		System.out.println("원 면적 : "+calculator.areaCircle(r));
		System.out.println();
		
		Computer computer = new Computer();
		System.out.println("원 면적 : "+computer.areaCircle(r)); //재정의된 메소드 호출
	}

}

Calculator 객체의 areaCircle() 실행
원 면적 : 314.159
Computer 객체의 areaCircle() 실행
원 면적 : 314.1592653589793

부모 메소드 호출

자식 클래스에서 부모 클래스의 메소드를 재정의하게 되면, 부모 클래스의 메소드는 숨겨지고 재정의된 메소드만 사용된다.

하지만 자식 클래스 내부에서 재정의된 부모 클래스의 메소드를 명시적으로 super.부모메소드(); 처럼 super키워드를 붙여서 호출할 수 있다.

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

package sec03.exam02;

public class Airplane {
	
	public void land() {
		System.out.println("착륙합니다.");
	}
	public void fly() {
		System.out.println("일반비행 합니다.");
	}
	public void takeOff() {
		System.out.println("이륙합니다.");
	}
}
package sec03.exam02;

public class SupersonicAirplane extends Airplane{
	
	public static final int NORMAL=1;
	public static final int SUPERSONIC=2;
	
	public int flyMode=NORMAL;
	
	@Override
	public void fly() {
		
		if(flyMode==SUPERSONIC) {
			System.out.println("초음속비행 합니다.");
		}
		else {
			super.fly(); //Airplane(부모)객체의 fly() 메소드 호출
		}	
	}
}
package sec03.exam02;

public class SupersonicAirplaneExample {

	public static void main(String[] args) {
		// TODO 자동 생성된 메소드 스텁
		
		SupersonicAirplane sa= new SupersonicAirplane();
		
		sa.takeOff();
		sa.fly();
		sa.flyMode = SupersonicAirplane.SUPERSONIC;
		sa.fly();
		sa.flyMode=SupersonicAirplane.NORMAL;
		sa.fly();
		sa.land();

	}

}

이륙합니다.
일반비행 합니다.
초음속비행 합니다.
일반비행 합니다.
착륙합니다.

final 클래스와 final 메소드

final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있는데 해당 선언이 최종 상태이며 결코 수정될 수 없음을 뜻한다.

final키워드는 필드를 선언하면 초기값 설정 후 더 이상 값을 변경할 수 없는 효과를 가지고 있지만, 클래스와 메소드를 선언할 때는 '상속'과 관련하게 된다.

상속할 수 없는 final 클래스

클래스 선언 시 final 키워드를 class 앞에 붙이면 최종적인 클래스가 되어 자식 클래스를 만들 수 없다. ex)String 클래스(public final class String{})

재정의할 수 없는 final 메소드

메소드 선언 시 final 키워드를 붙이면 최종적인 메소드가 되어 재정의할 수 없다.

profile
조급함보다는 꾸준하게

0개의 댓글