본문 바로가기
👨‍💻 2. 웹개발_Back end/2-1 Java

[JAVA] 07-1 상속

by 달님🌙 2021. 8. 4.
반응형

 

클래스 상속

 

- 형태 : class 자식클래스 extends 부모클래스 { 필드, 생성자, 메소드 }

- 단 하나의 부모클래스만 올 수 있다.

- 부모클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 불가

- 다른 패키지인 경우, default 접근 제한을 갖는 필드와 메소드 또한 상속 불가

 

클래스 상속 예제

 

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("전화를 끊습니다.");}
}

 

public class DmbCellPhone extends CellPhone {
	//필드
	int channel;
	
	//생성자
	DmbCellPhone(String model, String color, int channel) {
		this.model = model;
		this.color = color;
		this.channel = channel;
	}
	
	//메소드
	void turnOnDmb() {
		System.out.println("채녈 " + channel + "번 DMB 방송 수신을 시작합니다.");
	}
	void changeChannelDmb() {
		this.channel = channel;
		System.out.println("채녈 " + channel + "번으로 바꿉니다.");
	}
	void turnOffDmb() {
		System.out.println("DMB 방송 수신을 멈춥니다.");
	}
}

 

public class DmbCellPhoneExample {
	public static void main(String[] args) {
		
		//DmbCellPhone 객체 생성 
		DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10);
		
		//CellPhone 클래스로부터 상속받은 필드
		System.out.println("모델 : " + dmbCellPhone.model);
		System.out.println("색상 : " + dmbCellPhone.color);
		
		//DmbCellPhone 클래스의 필드
		System.out.println("채널 : " + dmbCellPhone.channel);
		
		//CellPhone 클래스로부터 상속받은 메소드 호출
		dmbCellPhone.powerOn();
		dmbCellPhone.bell();
		dmbCellPhone.sendVoice("여보세요.");
		dmbCellPhone.receiveVoice("안녕하세요 저는 홍길동인데요");
		dmbCellPhone.sendVoice("아,네ㅎㅎ");
		dmbCellPhone.hangUp();
		
		//dmbCellPhone 메소드 호출
		dmbCellPhone.turnOnDmb();
		dmbCellPhone.changeChannelDmb();
		dmbCellPhone.turnOffDmb();
	}
}

 

모델 : 자바폰
색상 : 검정
채널 : 10
전원을 켭니다.
벨이 울립니다.
자기: 여보세요.
상대: 안녕하세요 저는 홍길동인데요
자기: 아,네
전화를 끊습니다.
채녈 10번 DMB 방송 수신을 시작합니다.
채녈 10번으로 바꿉니다.
DMB 방송 수신을 멈춥니다.

 

부모 생성자 호출

 

<암시적 생성자>

- 위 예제를 보면 자식 객체인 DmbCellPhone 객체만 생성한 것 처럼 보이지만 사실은 내부에서 부모인 CellPhone 객체가 먼저 생성되었다.

- 그렇다면 부모 객체가 생성되기 전에 부모 생성자 호출을 먼저 했다는 것인데.. 그것은 바로 자식생성자의 맨 첫줄에서 호출된다.

- 컴파일러가 자동으로 super(); 를 써주어 부모의 기본 생성자를 호출한다. 

- 부모의 생성자가 선언이 되어야 호출도 가능한 것인데 우리가 부모의 생성자를 선언하지 않고 호출했는데도 에러가 난 이유는 컴파일러가 부모의 기본 생성자를 만들었기 때문이다.

 

<명시적 생성자>

- super(매개값, ...);을 통해 일치하는 타입의 부모 생성자를 찾아서 호출한다.

- 부모 클래스에 기본 생성자 대신 매개변수가 있는 생성자를 만든 경우에는 반드시 super에다가도 매개값을 넣어주어야한다.

- 또한 반드시 위치는 자식 생성자 첫 줄에 위치해야한다.

 

부모 생성자 호출 예제

 

public class People {
	//필드
	public String name;
	public String ssn;
	
	//생성자 
	public People(String name, String ssn) { //기본 생성자가 아닌 명시적 생성자만 있음
		this.name = name; //super로 생성자 호출해야함
		this.ssn = ssn; 
	}
}

 

public class Student extends People{
	public int studentNo;
	
	public Student(String name, String ssn, int studentNo) {
		super(name, ssn); // 부모 생성자 호출하기위해 매개값으로 넘김, 이렇게 자식 생성자의 맨 첫 줄에 작성
//		this.name = name;
//		this.ssn = ssn; -> 이렇게 두 줄 처럼 쓰게 되면 에러 발생
		this.studentNo = studentNo ; // 
	}
}

 

public class StudentExample {
	public static void main(String[] args) {
		Student student = new Student("홍길동", "123456-1234567", 1);
		System.out.println("name: " + student.name); // name: 홍길동
		System.out.println("ssn: " + student.ssn); // ssn: 123456-1234567
		System.out.println("studentNo: " + student.studentNo); // studentNo: 1
	}
}

 

 

메소드 재정의 (오버라이딩)

 

- 부모의 메소드를 상속받아 사용하려고 할 때 자식 클래스가 사용하기에 부적합할 수가 있다.

- 자식 클래스가 메소드를 알맞게 사용하고자 할때 상속된 메소드를 수정할 수가 있는데

- 이런 경우에 제공되는 기능이 바로 메소드 오버라이딩이다. 

-  메소드가 오버라이딩 되면 원래 있던 부모 객체의 메소드는 숨겨지게된다. 

- 따라서 자식 객체에서 해당 메소드를 호출하면 자동으로 오버라이딩된 메소드가 호출된다.

 

- 규칙1  : 부모와 동일한 시그니처(리턴타입, 메소드이름, 매개변수들)을 가져야한다

- 규칙2 : 접근 제한을 더 강하게 재정의할 수는 없다. (부모가 default인데 자식에서 private을 사용할 수 없음)

- 규칙3 : 새로운 예외(Exception)를 throw할 수 없다.

 

 

 

메소드 재정의 예제 - 원의 넓이 구하기

 

public class Calculator { // 부모 클래스
	//메소드
	double areaCircle(double r) {
		System.out.println("Calculator 객체의 areaCircle() 실행");
		return 3.14159 * r * r;
	}
}

 

public class Computer extends Calculator{ // 자식 클래스
	@Override // 메소드 재정의 (오버라이딩)
	double areaCircle(double r) { 
		System.out.println("Computer 객체의 areaCircle() 실행");
		return Math.PI * r * r; // Math = 자바 표준 API
	}
//	@Override
	double areaCircl(double r) { // @Override를 쓰면 오타를 찾아서 컴파일에러를 낸다.
								// @Override를 안쓰면 그냥 새로운 메소드로 받아들임
		System.out.println("Computer 객체의 areaCircle() 실행");
		return Math.PI * r * r;
	}
}

 

public class ComputerExample { // 자식 클래스 실행
	public static void main(String[] args) {
		int r = 10;
		
		Calculator calculator = new Calculator(); // 덜 정확한 넓이 계산 메소드
		System.out.println("원면적: " + calculator.areaCircle(r) + "\n");
		
		Computer computer = new Computer(); // 정확한 넓이 계산 메소드
		System.out.println("원면적: " + computer.areaCircle(r));
	}
}

 

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

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

 

이클립스에서 재정의 메소드 자동 생성

 

재정의 메소드 작성할 위치 커서 > [Source] > [Override/Implement Methods] > 부모클래스에서 재정의될 메소드 선택

 

재정의되지 않은 원래의 부모 메소드 호출하는 법

 

- 방법 : super.부모메소드(); 로 호출한다.

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

 

부모 메소드 호출 예제 - 비행기 

 

public class Airplane {
	//메소드
	public void land() {
		System.out.println("착륙합니다.");
	}
	public void fly() {
		System.out.println("일반비행합니다.");
	}
	public void takeOff() {
		System.out.println("이륙합니다.");
	}
}

 

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) { // 값이 SUPERSONIC일 경우에는 초음속비행
			System.out.println("초음속비행합니다.");
		} else { // 그렇지 않은 경우에는 부모 클래스의 fly() 메소드를 호출
			super.fly();
		}
	}
}

 

public class SupersonicAirplaneExample {
	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(); // 착륙합니다.
	}
}

 

final 키워드

 

- final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있음

- 해당 선언이 최종이며 더이상 수정할 수 없음을 뜻함

- final 필드 : 앞에서 이미 한 개념으로, 필드 앞에 final이 지정되면 초기값 설정 후 더이상 값을 변경할 수 없다.

- final 클래스와 final 메소드 : 상속과 관련된 개념으로, 아래에서 자세히 다루어보자

 

final 클래스 - 상속 불가능

 

- 클래스 앞에 final이 붙으면 최종적인 클래스임을 의미하며 상속할 수 없는 (부모가 될 수 없는) 클래스이다.

- 대표적인 final 클래스로는 String 클래스가 있다.

 

- String 클래스 선언 형태

public final class String {}

 

- 아래와 같이 String 클래스를 부모 클래스로 지정할 수 없다.

public class newString extends String {} // 불가능

 

- final 클래스 - Member 클래스 예제

public final class Member {} // final 키워드가 나오면

 

public class VeryImportantPerson extends Member {} // 상속할 수 없다.

- 이렇게 사용 불가능하다. 

 

final 메소드 - 재정의 불가능

 

- 메소드 선언시 final을 붙이면 이 메소드는 최종 메소드가 되서 더이상 수정이 불가능하다.

- 이 말은 재정의를 할 수 없는 메소드가 된다는 말이다.

- 자식클래스가 부모클래스를 상속하여 부모클래스의 final 메소드를 재정의하려고 할 때 오류가 난다.

 

- final 메소드 - Car 클래스 예제

public class Car {
	//필드
	public int speed;
	
	//메소드
	public void speedUp() {speed += 1;}
	
	//final 메소드
	public final void stop() {
		System.out.println("차를 멈춘다.");
		speed = 0;
	}
}

 

public class SportsCar extends Car {
	@Override
	public void speedUp() {speed += 10;}
	
	@Override
	public void stop() { // stop() 메소드는 final로 선언되었기 때문에 메소드 재정의가 불가능하다.
		System.out.println("스포츠카를 멈춤");
		speed = 0;
	}
 }

 

- final 메소드 재정의를 시도했을 때 발생되는 에러 내용

 

 

protected 접근 제한자

 

- protected는 필드와 생성자, 메소드 선언에서 사용이 가능하다.

- 같은 패키지에 있는 클래스는 접근 제한이 업지만 다른 패키지에 있는 클래스는 자식 클래스만 접근이 가능하다.

- 같은 패키지에 있는 클래스, 다른 패키지에 있는 클래스, 다른 패키지에 있지만 상속을 받은 클래스 이 세가지 경우에 대해 알아보자

 

0. 우선 pack1패키지에 A 클래스를 하나 만든다.

package Exercise.ch07.sec1.pack1;

public class A {
	protected String field; // protected 필드 선언
	protected A() {} // protected 생성자 선언
	protected void methode() {} // protected 메소드 선언
}

 

1. A 클래스와 같은 pack1 패키지에 있는 클래스 B 생성

package Exercise.ch07.sec1.pack1;

public class B {
	public static void main(String[] args) {
		A a = new A(); // 생성자에 접근 가능 (객체 생성)
		a.field = "value"; // 필드에 접근가능 (필드 값 변경)
		a.methode(); // 메소드 실행 가능
	}
}

 

2. 다른 패키지 pack2에 있는 C 클래스 생성

package Exercise.ch07.sec1.pack2;

public class C {
	public static void main(String[] args) {
		A a = new A(); // 다른 패키지에 있어서 접근 불가능
		a.field = "value"; // 다른 패키지에 있어서 접근 불가능
		a.method(); // 다른 패키지에 있어서 접근 불가능
	}
}

 

3. 다른 패키지 pack2에 있지만 A 클래스로부터 상속을 받은 D 클래스 생성

package Exercise.ch07.sec1.pack2;

import Exercise.ch07.sec1.pack1.A;

public class D extends A{
	// 자식 클래스 생성자
	public D() {
		// A a = new A(); // new 연산자를 통해 생성자 직접 호출은 불가능하다
		super(); // supe()로 A생성자 호출 가능
		this.field = "value"; // 다른 패키지지만 D가 A의 자식이므로 필드에 접근 가능
		this.methode(); // 다른 패키지지만 D가 A의 자식이므로 메소드에 접근 가능
	}
}

 

반응형

댓글