[Java]상속

Devlog·2024년 3월 13일

Java

목록 보기
17/41

✔️ 클래스 상속

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

ex)
// Car 클래스를 상속해서 SportsCar 클래스 설계시, 작성
class SportsCar extends Car {

}

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

👩‍💻 부모 · 자식 클래스
// CellPhone.java
// 부모클래스
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("전화를 끊습니다.");
    }
}
// DmbCellPhone.java
// 자식 클래스
public class DmbCellPhone extends CellPhone{
	//필드
	int channel;
	
	//생성자
	DmbCellPhone(String model, String color, int channel){
		this.model = model; // CellPhone 클래스로부터 상속받은 필드
		this.color = color; // CellPhone 클래스로부터 상속받은 필드
		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 방송 수신을 멈춥니다.");
	}
}
// 실행 클래스
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(12);
		dmbCellPhone.turnOffDmb();
	}
}

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

✔️ 부모 생성자 호출

: 자식 객체를 생성하면, 부모 객체가 먼저 생성되고 그다음에 자식 객체가 생성

> CellPhone.java - 부모 클래스
> DmbCellPhone.java - 자식 클래스

→ 내부적으로 부모인 CellPhone 객체가 먼저 생성되고
  자식인 DmbCellPhone객체가 생성

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

ex) DmbCellPhone의 생성자가 명시적으로 선언되지 않았다면
public DmbCellPhone() {
	super(); // 부모의 기본 생성자를 호출함
}
이와 같은 기본 생성자를 생성함

public CellPhone() {

}
CellPhone.java 소스 코드에서도 CellPhone의 생성자가
선언되지 않았지만 컴파일러에 의해 기본 생성자 만들어지므로
문제 없이 실행됨

- 직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출시

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

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

👩‍💻 자식 객체 이용
/*
 * People 클래스는 기본 생성자가 없고
 * name과 sssn을 매개값으로 받아 객체를 생성시키는 생성자만 있음
 * 그렇기 때문에 People을 상속하는 Student 클래스는 생성자에서  
 * super(name, ssn)으로 People 클래스의 생성자를 호출해야함
 */
public class People {
	public String name;
	public String ssn;
	
	public People(String name, String ssn) {
		this.name	= name;
		this.ssn	= ssn;
	}
}
public class Student extends People{
	public int studentNo;
	
	// Student 클래스의 생성자는 name, ssn, studentNo를 매개값으로 받아서
	// name과 ssn은 다시 부모 생성자를 호출하기 위해 매개값으로 넘겨줌
	public Student(String name, String ssn, int studentNo) {
		// super(name, ssn)은 People 생성자인 
		// People(String name, String ssn)을 호출함
		// super(name, ssn)를 주석 처리하면
		// "Implicit super constructor People() is undefinded. 
        // Must explicitly invoke another constructor"라는 컴파일 에러 발생
		// 이 에러는 부모의 기본 생성자가 없으니 
        // 다른 생성자를 명시적으로 호출하라는 의미
		super(name, 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);
        // 부모에게 상속받은 필드 출력
		System.out.println("ssn : " + student.ssn);		
		System.out.println("studentNo : " + student.studentNo);
	}
}

✔️ 메소드 재정의(오버라이딩: Overriding)

: 부모 클래스의 모든 메소드가 자식 클래스에 맞게 설계되어 있다면
  가장 이성적인 속성이지만,
  어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수도 있음
  → 이 경우 상속된 일부 메소드는 자식 클래스에서 다시 수정해서 사용해야함

→ 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것

- 메소드 재정의 방법
: 부모의 메소드와 동일한 시그너처
  (리턴 타입, 메소드 이름, 매개 변수 목록)를 가져야 함
: 접근 제한을 더 강하게 재정의할 수 없음
  → 부모 메소드가 public 접근 제한을 가지고 있을 경우
  재정의하는 자식 메소드는 default나 private 접근 제한으로 수정할 수 없다는 뜻
  단, 반대는 가능함 부모 메소드가 default 접근 제한을 가지면
  재정의하는 자식 메소드는 default 또는 public 접근 제한을 가질 수 있음
: 새로운 예외(Exception)를 throws할 수 없음

: 메소드가 재정의되었다면 부모 객체의 메소드는 숨겨지기 때문에,
자식 객체에서 메소드를 호출하면 재정의된 자식 메소드가 호출

👩‍💻 메소드 재정의
/*
 * Calculator의 자식 클래스인 Computer에서 
 * 원의 넓이를 구하는 Calculator의 areaCircle()메소드를 사용하지 않고, 
 * 좀 더 정확한 원의 넓이 구하기
 */

// 부모 클래스
public class Calculator {
	double areaCircle(double r) {
		System.out.println
        ("Calculator 객체의 areaCircle() 실행");
		return 3.14159 * r * r;
	}
}
public class Computer extends Calculator{
	 // @Override 어노테이션은 생략해도 좋으나, 
     // 이것을 붙여주면 areaCircle() 메소드가 
     // 정확히 재정의된 것인지 컴파일러가 확인하기 때문에 실수를 줄여줌
    @Override
	double areaCircle(double r) {
		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));
		
        //재정의된 메소드 호출
		Computer computer = new Computer();
		System.out.println("원면적 : " + computer.areaCircle(r)); 
	}
}

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

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

1. 자식 클래스에서 재정의 메소드를 작성할 위치로 입력 커서를 옮긴다.
2. [Source] - [Override/Implement Methods]메뉴를 선택한다.
3. 부모 클래스에서 재정의될 메소드를 선택하고 [OK]버튼을 클릭한다.

- 부모 메소드 호출

super.부모메소드();

: 자식 클래스에서 부모 클래스의 메소드를 재정의하게 되면,
  부모 클래스의 메소드는 숨겨지고 재정의된 자식 메소드만 사용됨
  그러나 자식 클래스 내부에서 재정의된
  부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면
  명시적으로 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) {
			System.out.println("초음속비행합니다.");
		} else {
			super.fly(); // Airplane 객체의 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 클래스

public final class 클래스 { ··· }

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

ex) 자바 표준 API에서 제공하는 String 클래스
public final class String { ··· } 으로 선언됨

그래서 자식 클래스를 만들 수 없음
public class NewString extends String { ··· } (x)

- 재정의할 수 없는 final 메소드

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

: 부모 클래스를 상속해서 자식 클래스를 선언할 때
  부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없음


✔️ protected 접근 제한자

: public과 default 접근 제한의 중간쯤에 해당
: 같은 패키지에서는 default와 같이 접근 제한이 없지만
  다른 패키지에서는 자식 클래스만 접근을 허용함
: 필드와 생성자, 메소드 선언에 사용될 수 있음

public class A {
	protected String field;

	protected A() {
	}

	protected void method(){
	}
}
public class B {
	public void method() {
		A a = new A();
		a.field = "value";
		a.method();
	}
}

→ B클래스는 A 클래스와 동일한 패키지에 있을 때
  default 접근 제한과 마찬가지로
  B클래스의 생성자와 메소드에서는
  A 클래스의 protected 필드, 생성자, 메소드에 얼마든지 접근 가능

import A클래스 패키지

public class C {
	public void method() {
		A a = new A();
		a.field = "value";
		a.method();
	}
}

→ C클래스는 A 클래스와 다른 패키지에 있을 때
  A 클래스의 protected 필드, 생성자, 메소드에 접근 불가능

import A클래스 패키지

public class D extends A {
	public D() {
		super();
		this.field = "value";
		this.method();
	}
}

→ D클래스는 A 클래스와 다른 패키지에 있을 때
  C클래스와는 다르게 D는 A의 자식 클래스 이므로
  A 클래스의 protected 필드, 생성자, 메소드에 얼마든지 접근 가능
  단 new 연산자를 사용해서 생성자를 직접 호출할 수는 없고,
  자식 생성자에서 super()로 A생성자를 호출할 수 있음

0개의 댓글