[21일차] 상속

2023. 1. 19. 17:33데이터 엔지니어링 과정/java

목차
1. 상속
2. 타입 변환과 다형성

1. 상속

0. 상속 개념

  • 정의 : 부모가 자식에게 물려주는 행위
    ➡ 부모 클래스 (상위 클래스)가 자식 클래스(하위 클래스 또는 파생 클래스)에게 물려준다.
  • 효과
    ① 중복되는 코드를 줄여준다.
    ➡ 효율적이고 개발 시간을 절약해준다.
    ② 부모 클래스의 수정으로 모든 자식 클래스들도 수정되는 효과를 가져온다.
    ➡ 유지 보수 시간을 최소화 할 수 있다.

1. 클래스 상속

  • 상속 방법
    자식 클래스를 선언할 때 어떤 부모 클래스를 상속받을 것인지 결정하고, 선택된 부모 클래스는 extends 뒤에 기술
class 자식클래스 extends 부모 클래스 {
	//필드
    //생성자
    //메소드
}
  • 특징
    ① extends 뒤에는 단 하나의 부모 클래스만 와야 한다.
    ② 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.
    ③ 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면, default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다.
  • 💻 핸드폰 (CellPhone) 클래스 상속해서 DMB폰(DmbCellPhone) 클래스를 작성
package sec01.exam01;

public class CellPhone {
	//필드
	String model;
	String color;
	
	//생성자
	
	//메소드
	void powerOn() { System.out.println("전원을 켭니다."); }
	void popwerOff() {System.out.println("전원을 끕니다."); }
	void bell() {System.out.println("벨이 울립니다.");}
	void sendVoince(String message) {System.out.println("자기: " + message);}
	void receiveVoice(String message) {System.out.println("상대방: " + message);}
	void hangUp() {System.out.println("전화를 끊습니다.");}
}
package sec01.exam01;

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(int channel) {
		this.channel = channel;
		System.out.println("채널 " + channel + "번으로 바꿉니다.");
	}
	void turnOffDmb() {
		System.out.println("DMB 방송 수신을 멈춥니다.");
	}
}
package sec01.exam01;

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.sendVoince("여보세요");
		dmbCellPhone.receiveVoice("안녕하세요! 저는 홍길동인데요");
		dmbCellPhone.sendVoince("아~ 네 반갑습니다.");
		dmbCellPhone.hangUp();
		
		//DmbClassPhone 클래스의 메소드 호출
		dmbCellPhone.turnOnDmb();
		dmbCellPhone.changeChannelDmb(12);
		dmbCellPhone.turnOffDmb();
	}
}

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

2. 부모 생성자 호출

  • 모든 객체는 클래스의 생성자를  호출해야만 생성된다.
  • 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다.
    super()를 통해 부모의 기본 생성자 호출
  • super(매개값, ···)
    • 매개값의 타입과 일치하는 부모 생성자 호출
    • 만약 매개값의 타입과 일치하는 부모 생성자가 없을 경우 컴파일 에러 발생
  • 💻 부모 클래스 People , 자식 클래스 Student, 자식 객체 이용
package sec01.exam02;

public class People {
	//필드
	public String name;
	public String ssn;
	
	public People (String name, String ssn) {
		this.name = name;
		this.ssn = ssn;
	}
}
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;
	}
}
package sec01.exam02;

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);
	}
}

>>> name : 홍길동
>>> ssn : 123456-1234567
>>> studentNo : 1

3. 메소드 재정의

  • 정의
    : 부모의 메소드가 자식 클래스가 사용하기에 적합하지 않을 경우, 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것
  • 메소드 재정의 규칙
    ① 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)을 가져와야 한다.
    ② 접근 제한을 더 강하게 재정의 할 수 없다.
    ③ 새로운 예외를 throws 할 수 없다.
    ❗주의할점❗
    • 부모 메소드가 public 접근 제한을 갖고 있을 경우, 재정의하는 자식 메소드는 default나 private 접근 제한으로 수정할 수 없다.
    • 반대로, 부모 메소드가 default 접근 제한을 가지면 재정의하는 자식 메소드는 default 또는 public 접근 제한을 가질 수 있다.
  • 💻 Calcualator의 자식 클래스인 Computer에서 원의 넓이를 구하는 Calculator의 areaCircle() 메소드를 재정의
package sec01.exam03;

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

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

public class ComputerExample {
	public static void main(String[] args) {
		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));
	}
}

>>> Calcaulator 객체의 areaCircle() 실행
>>> 원면적 : 314.159
>>> 
>>> Computer 객체의 areaCircle() 실행
>>> 원면적: 314.1592653589793
  • 부모 메소드 호출
    : 자식 클래스 내부에서 재정의된 부모 클래스의 메소드를 호출해야하는 상황이라면,, 명시적으로 super 키워드를 붙여서 부모 메소드를 호출할 수 있다.
  • 💻 Airplane 클래스를 상속한 SupersonicAirplane 클래스의 초음속 비행 모드와 일반 비행 모드
package sec01.exam04;

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

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 {
			//Airplane 객체의 fly() 메소드 호출
			super.fly();
		}
	}
}
package sec01.exam04;

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();
	}
}

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

4. final 클래스와 final 메소드

  • 상속할 수 없는 final 클래스
    : 클래스를 선언할 때 final 키워드를 class 앞에 붙이면 이 클래스는 최종적인 클래스이다.
    ➡ 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.
  • 💻 Member 클래스를 선언할 때 final을 지정함으로써 VeryImportantPerson은 Member를 상속 불가
package sec01.exam05;

public final class Member {

}
package sec01.exam05;

//Member 상속 불가
public class VeryImportantPerson extends Member{
}
  • 재정의할 수 없는 final 메소드
    : 메소드를 선언할 때 final 키워드를 붙이면 최종적인 메소드이므로, 재정의할 수 없는 메소드가 된다.
    ➡ 부모 클래스를 상속해서 자식 클래스를 선언할 때,부모 클래스에서 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다.
  • 💻 Car 클래스의 stop() 메소드를 final로 선언하여 Car를 상속한 SportCar 클래스에서는 stop() 메소드 재정의 불가
package sec01.exam06;

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

public class SportsCar extends Car {
	@Override
	public void speedUp() { speed += 10;}
	
	//재정의 불가
	@Ovrride
	public void stop() {
		System.out.println("스포츠카를 멈춤");
		speed=0;
	}
}

5. protected 접근 제한자

  • 같은 페이지에서는 default처럼 접근 제한이 없다.
  • 다른 패키지에서는 자식 클래스만 접근을 허용한다.
  • 필드, 생성자, 메소드 선언에 사용될 수 있다.
  • 💻 protected 접근 제한자가 있는 A 클래스
package sec01.exam07.pack1;

public class A {
	protected String field;
	
	protected A() {
	}
	
	protected void method() {
	}
}
//A클래스와 동일한 패키지에 있는 B클래스

package sec01.exam07.pack1;

public class B {
	public void method() {
		A a = new A();
		a.field = "value";
		a.method();
	}
}
//A패키지와 다른 패키지에 있는 C클래스➡ A클래스의 protected 필드, 생성자, 메소드 접근 불가 

package sec01.exam07.pack2;

public class C {
	A a = new A();
	a.field = "value";
	a.method();
}
/*A클래스와 다른 패키지에 있지만, A클래스의 자식 클래스인 D클래스 
➡ A클래스의 protected 필드, 생성자, 메소드 접근 가능*/

package sec01.exam07.pack2;

import sec01.exam07.pack1.A;

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

 

2. 타입 변환과 다형성

0. 다형성

  • 정의 : 사용 방법은 동일하지만, 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질
  • 다형성 = 메소드 재정의 + 타입 변환

1. 자동 타입 변환

  • 타입 변환 : 타입을 다른 타입으로 변환하는 행위
  • 클래스의 타입 변환
    • 클래스의 변환은 상속 관게에 있는 클래스 사이에서 발생
    • 자식은 부모 타입으로 자동 타입 변환 가능
  • 자동 타입 변환 : 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것
    • 조건 : 부모 타입 변수 = 자식타입;
  • 💻 자동 타입 변환 예제 코드
package sec02.exam01;

classs A {}

class B extends A {}
class C extends A {}

class D extends B {}
class E extends C {}

public class PromotionExample {
	public static void main(String[] args) {
		B b = new B();
		C c = new C();
		D d = new D();
		E e = new E();
		
		A a1 = b;
		A a2 = c;
		A a3 = d;
		A a4 = e;
		
		B b1 = d;
		C c1 = e;
		
		//B b3 = e;
		//C c2 = d;  
	}
}
  • 부모 타입은 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능함
    • 변수는 자식 객체를 참조하지만, 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정됨
    • 예외
      메소드가 자식 클래스에서 재정의 되었다면 자식 클래스의 메소드가 대신 호출된다.
  • 💻 자동 타입 변환 후의 멤버 접근
package sec02.exam02;

public class Parent {
	public void method1() {
		System.out.println("Parent-method()");
	}
	public void method2() {
		System.out.println("Parent-method2()");
	}
}
package sec02.exam02;

public class Child  extends Parent{
	@Override
	public void method2() {
		System.out.println("Child-method2()");
	}
	
	public void method3() {
		System.out.println("Child-method2()");
	}
}
package sec02.exam02;

public class ChildExample {
	public static void main(String[] args) {
		Child child = new Child();
		
		Parent parent = child;
		parent.method1();
		parent.method2();
		//parent.method3();
	}
}

>>> Parent-method()
>>> Child-method2()

2. 강제 타입 변환

  • 정의 : 부모 타입을 자식 타입으로 변환하는 것
  • 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 강제 타입 변환 사용 가능
  • 💻 강제 타입 변환 코딩
package sec02.exam05;

public class Parent {
	public String field1;
	
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}
package sec02.exam05;

public class Child extends Parent {
	public String field2;
	
	public void method3() {
		System.out.println("Child-method3()");
	}
}
package sec02.exam05;

public class ChildExample {
	public static void main(String[] args) {
		Parent parent = new Child();
		parent.field1 = "data1";
		parent.method1();
		parent.method2();
		/*
		 * parent.filed2 = "data2";
		 * parent.method3();
		 */
		
		Child child = (Child) parent;
		child.field2 = "yyy";
		child.method3();
	}
}

>>> Parent-method1()
>>> Parent-method2()
>>> Child-method3()

3. 객체 타입 확인

  • instasnceof 연산자 사용
  • 💻 예시
package sec02.exam06;

public class Parent {

}
package sec02.exam06;

public class Child extends Parent{

}
package sec02.exam06;

public class InstanceofExample {

	public static void method1(Parent parent) {
		if (parent instanceof Child) {
			Child child = (Child) parent;
			System.out.println("method1 - Child로 변환성공");
		} else {
			System.out.println("method1 - Child로 변환되지 않음");
		}
	}
	
	public static void method2(Parent parent) {
		Child child = (Child) parent;
		System.out.println("method2 - Child로 변환 성공");
	}
	
	public static void main(String[] args) {
		Parent parentA = new Child();
		method1(parentA);
		method2(parentA);
		
		Parent parentB = new Parent();
		method1(parentB);
		method2(parentB);
	}
}

>>> method1 - Child로 변환성공
>>> method2 - Child로 변환 성공
>>> method1 - Child로 변환되지 않음
>>> Exception in thread "main" java.lang.ClassCastException