JAVA

JAVA 마스터가 되기 위한 여정-7

나는나는용 2023. 2. 21. 00:44
728x90

7. 상속

7-1. 상속

상속의 장점:
1. 이미 잘 개발된 클래스의 재사용 ➞ 새 클래스의 코드중복 감소.
2. 부모 클래스만 수정해도 자식들 모두 수정되므로 유지 보수 시간 최소화.

클래스 상속

자식이 부모를 선택.

  • 여러 개의 부모 클래스 상속 불가.
  • 부모 클래스에서 private접근제한인 필드/메소드는 상속 불가.
  • 부모 자식 클래스가 다른 패키지에 있다면 default접근제한인 필드/메소드는 상속 불가.

package sec01.exam01;
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 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.sendVoice("여보세요");
		dmbCellPhone.receiveVoice("안녕하세요! 전 홍길동인데요");
		dmbCellPhone.sendVoice("아~예. 방가");
		dmbCellPhone.hangUp();
		//dmbCellPhone클래스의 메소드 호출
		dmbCellPhone.turnOnDmb();
		dmbCellPhone.changeChannelDmb(12);
		dmbCellPhone.turnOffDmb();
	}
}

실행결과

부모 생성자 호출

자식 객체를 생성하면 부모 객체가 먼저 생성됨.
모든 객체는 클래스의 생성자를 호출해야만 생성되고, 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출됨.
super()는 부모의 기본 생성자를 호출하는데, 만약 매개값이 있다면
super(매개값,---)의 형태를 띄며, 이는 매개값의 타입과 일치하는 부모 생성자를 호출함.
어떠한 생성자보다도 super()생성자가 자식 생성자의 첫 줄에 위치해야 함.


부모 클래스

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

실행결과

메소드 재정의

= 오버라이딩
부모 클래스의 모든 메소드가 자식 클래스에서 상속하여 사용하기 적합한 것은 아님.
➥ 자식 클래스에서 일부 메소드를 수정하여 사용해야함.

메소드 재정의 방법

규칙

  • 부모의 메소드와 동일한 시그너처를 가질것.
    • 시그너처: 리턴 타입, 메소드 이름, 매개 변수 목록
  • 접근 제한을 더 강하게 재정의 불가.
    • 더 완화는 가능.
  • 새로운 예외를 throws 불가. (10장에서 계속)
  • 재정의하는 자식클래스 첫줄에 @Override 어노테이션 추가 권고.

메소드 재정의 완료 후 부모의 메소드는 숨겨짐.

tip
재정의 메소드 자동 생성 방법
1. 자식클래스에서 재정의 메소드를 작성할 위치로 입력 커서 둠
2. source-override/implement methods
3. 부모 클래스에서 재정의될 메소드 선택하고 ok

부모 메소드 호출

재정의 한 뒤에 만약 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super키워드를 붙여서 호출 가능.

super.부모메소드();	//부모 메소드에 직접 접근이 가능

super변수, 부모메소드 재정의, 재정의 부모메소드 직접사용

//부모
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 {
			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();
	}
}

실행결과

final 클래스와 final 메소드

final 필드: 초기값 설정 후 더 이상 값 변경 불가
그렇다면, 클래스와 메소드에 붙는다면?
 상속과 관련됨

상속할 수 없는 final 클래스

final 클래스: 최종적인 클래스. 상속할 수 없는 클래스. 부모 클래스가 불가함. ex) String 클래스.

재정의할 수 없는 final 메소드

final 메소드: 최종적인 메소드. 재정의할 수 없는 메소드. 자식클래스에서 재정의 불가함.

추가

protected 접근 제한자

public과 default 접근 제한자의 중간쯤.
같은 패키지라면 접근에 제한이 없음.
다른 패키지라면 자식 클래스만 접근 가능.

'필드', '생성자', '메소드' 선언에 사용될 수 있음.

7-2. 타입 변환과 타형성

다형성: 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행 결과가 나오도록 하는 성질.
ex) 동일한 타이어 사용방법, 다양한 타이어 종류, 다양한 주행 성능

다형성의 구현을 위해, 메소드 재정의와 타입 변환이 필요함.

자동 타입 변환

(기본 타입 변환은 2장에서)
클래스에서의 타입 변환은 상속 관계에 있는 클래스 사이에서 발생.
자식은 부모 타입으로 자동 타입 변환이 가능.
프로그램 실행 도중 자동적으로 변환 발생.

조건
부모타입 변수 = 자식타입;
 자식은 부모의 특징과 기능을 상속 ➤ 부모와 동일하게 취급될 수 있음

ex) 고양이 객체는 동물 객체

Cat cat = new Cat();
Animal animal = cat;	//Animal animal = new Cat(); 도 가능.

여기서 animal변수와 cat변수는 다른 타입이지만 같은 Cat 객체 참조.
∴ 같은 번지를 참조하므로 cat==animal; 은 true 이다.

바로 위의 부모가 아니더라도 상속 계층에서의 상위 타입이라면 자동 타입 변환이 가능.
ex) '고양이 ➞ 동물 ➞ 생물' 이라면, '고양이 ➞ 생물' 가능.

부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능.
그 ! 런 ! 데 !
메소드가 자식 클래스에서 재정의되었다면 자식 클래스의 메소드로 호출됨.
ex)
메인에서 메소드2 호출시 자식 메소드2가 호출되고, 메소드3 호출 불가
부모-메소드1, 메소드2
자식-메소드2(재정의),메소드3
메인-자동타입변환, 메소드1, 메소드2, 메소드3

필드의 다형성

자동 타입 변환이 필요한 이유:
그냥 자식 쓰지, 왜 굳이 부모로 변환해?
 다형성 구현을 위해.

필드 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가 달라질 수 있음.
자동타입변환된 자식이 재정의한 메소드를 실행하므로, 부모의 메소드를 직접 수정하지 않고 필드값 교체(자식교체)만으로도 다양한 실행 결과를 얻게 됨.

다형성 구현의 기술적 조건:

  • 클래스 상속 = 부모의 필드와 메소드를 사용 : '동일한 사용 방법'
  • 부모 메소드 재정의 = 메소드 실행 내용 변경 : '더 우수한 실행 결과'
  • 타입 변환

예제
Tire 클래스
타이어의 누적 회전수가 최대 회전수(타이어 수명)에 도달하면 펑크,
타이어의 위치는 앞의 왼/오른쪽, 뒤의 왼/오른쪽.

package sec02.exam03;
public class Tire {
	//필드
	public int maxRotation;
	public int accumulatedRotation;
	public String location;
	// 생성자
	public Tire(String location, int maxRotation) {
		this.location = location;
		this.maxRotation = maxRotation;
	}
	public boolean roll() {
		++accumulatedRotation;
		if (accumulatedRotation < maxRotation) {
			System.out.println(location + " Tire 수명: " + (maxRotation - accumulatedRotation) + "회");
			return true;
		} else {
			System.out.println("*** " + location + " Tire 펑크 ***");
			return false;
		}
	}
}

Car 클래스
자동차는 4개의 타이어를 가지며 각 위치에서의 수명은 다름.
만약 최대 회전수를 돌아 펑크가 난 바퀴가 생기면 주행을 멈추고 펑크가 난 바퀴의 번호를 반환.

package sec02.exam03;
public class Car {
	// 필드
	Tire frontLeftTire = new Tire("앞 왼쪽", 6);
	Tire frontRightTire = new Tire("앞 오른쪽", 2);
	Tire backLeftTire = new Tire("뒤 왼쪽", 3);
	Tire backRightTire = new Tire("뒤 오른쪽", 4);
//생성자
//메소드
	int run() {
		System.out.println("자동차 주행 시작");
		if (frontLeftTire.roll() == false) {
			stop();
			return 1;
		}
		if (frontRightTire.roll() == false) {
			stop();
			return 2;
		}
		if (backLeftTire.roll() == false) {
			stop();
			return 3;
		}
		if (backRightTire.roll() == false) {
			stop();
			return 4;
		}
		return 0;
	}
	void stop() {
		System.out.println("자동차 정지");
	}
}

HankookTire, KumhoTire 클래스
Tire 클래스 상속받음.
부모 클래스의 생성자에 매개값이 있으므로, '타이어의 위치'와 '최대 회전수'를 매개값으로 받아, 부모 클래스 생성자를 호출할 때 넘김.
부모(Tire)의 roll()메소드 재정의하여 다른 내용 출력.

package sec02.exam03;
public class HankookTire extends Tire {
	// 필드
	// 생성자
	public HankookTire(String location, int maxRotation) {
		super(location, maxRotation);
	}
	// 메소드
	@Override
	public boolean roll() {
		++accumulatedRotation;
		if (accumulatedRotation < maxRotation) {
			System.out.println(location + " HankookTire 수명: " + (maxRotation - accumulatedRotation) + "회");
			return true;
		} else {
			System.out.println("*** " + location + " HankookTire 펑크 ***");
			return false;
		}
	}
}
package sec02.exam03;
public class KumhoTire extends Tire {
	// 필드
	// 생성자
	public KumhoTire(String location, int maxRotation) {
		super(location, maxRotation);
	}
	// 메소드
	@Override
	public boolean roll() {
		++accumulatedRotation;
		if (accumulatedRotation < maxRotation) {
			System.out.println(location + " KumhoTire 수명: " + (maxRotation - accumulatedRotation) + "회");
			return true;
		} else {
			System.out.println("*** " + location + " KumhoTire 펑크 ***");
			return false;
		}
	}
}

CarExample 실행클래스

package sec02.exam03;
public class CarExample {
	public static void main(String[] args) {
		Car car = new Car();
		for (int i = 1; i <= 5; i++) {
			int problemLocation = car.run();
			switch (problemLocation) {
			case 1:
				System.out.println("앞왼쪽 HankookTire로 교체");
				car.frontLeftTire = new HankookTire("앞왼쪽", 15);	//Tire의 자식
				break;
			case 2:
				System.out.println("앞오른쪽 KumhoTire로 교체");
				car.frontRightTire = new KumhoTire("앞오른쪽", 13);	//Tire의 자식
				break;
			case 3:
				System.out.println("뒤왼쪽 HankookTire로 교체");
				car.backLeftTire = new HankookTire("뒤왼쪽", 14);	//Tire의 자식
				break;
			case 4:
				System.out.println("뒤오른쪽 KumhoTire로 교체");
				car.backRightTire = new KumhoTire("뒤오른쪽", 17);	//Tire의 자식
				break;
			}
			System.out.println("-------------------------");
		}
	}
}

실행결과
타이어 교체를 한 뒤에 반복문을 돌 때에는 Car의 run메소드가 실행될 때 Tire의 roll이 아닌, HankookTire와 KumhoTire에서 재정의한 roll()이 실행됨.

매개변수의 다형성

메소드를 호출할 때, 매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정.
매개 변수의 타입이 클래스일 경우, 해당 클래스의 객체뿐만 아니라 자식 객체까지도 매개값으로 사용 가능.
'매개값의 자동 타입 변환' + '메소드 재정의'  "매개 변수 다형성"


예시
부모 클래스 - Vehicle

package sec02.exam04;
public class Vehicle {
	public void run() {
		System.out.println("차량이 달립니다.");
	}
}

부모Vehicle클래스를 이용하는 클래스

package sec02.exam04;
public class Driver {
	public void drive(Vehicle vehicle) {
		vehicle.run();
	}
}

자식클래스

package sec02.exam04;
public class Bus extends Vehicle {
	@Override
	public void run() {
		System.out.println("버스가 달립니다.");
	}
}
package sec02.exam04;
public class Taxi extends Vehicle {
	@Override
	public void run() {
		System.out.println("택시가 달립니다.");
	}
}

실행클래스

package sec02.exam04;
public class DriverExample {
	public static void main(String[] args) {
		Driver driver = new Driver();
		Bus bus = new Bus();
		Taxi taxi = new Taxi();
		driver.drive(bus);
		driver.drive(taxi);
	}
}

실행결과

강제 타입 변환

부모 타입을 자식 타입으로 변환.

조건 : 자식타입 ➞ 부모타입 ➔ 자식타입
자식타입 변수 = (자식타입) 부모타입;
ex) 자동 타입 변환 후 강제 타입 변환

Parent parent = new Child();	//자동 타입 변환
Child child = (child) parent;	//강제 타입 변환

기껏 부모로 바꿔줬구만, 자식으로는 왜 또 바꿔쓰는건데?
 자동 타입 변환하면 부모에 선언된 필드와 메소드만 사용 가능.
자식에 선언된 필드와 메소드를 꼭 사용해야할 때, 강제 타입 변환을 하여 부모로 된 것을 다시 자식 타입으로 변환한 다음 자식 필드와 메소드 사용.


예시
부모 클래스

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.field2="data2"; parent.method3();
		 */
		Child child = (Child) parent;
		child.field2 = "yyy";
		child.method3();
	}
}

실행결과

객체 타입 확인

instanceof 연산자
어떤 객체가 어떤 클래스의 인스턴스인지 확인.
주로 언제 확인하지?
➥ 부모변수가 참조하는 객체가 부모객체인지 자식객체인지(강제타입변환을 할 수 있는 상태인지 아닌지.)

사용 방법
좌항의 객체가 우항의 타입이라면 true 반환.

boolean result = 좌항(객체) instanceof 우항(타입)

주로 매개값의 타입을 조사할 때 사용하는데, 타입을 확인하지 않고 강제 타입 변환을 시도한다면, 안전하지 않은 경우 예외 발생 가능.
예외 발생시 프로그램이 종료될 수 있으므로 '객체 타입 확인' 권장.

public void method(Parent parent){
	if(parent instanceof Child){
    	Child child = (Child) parent;
    }
}

7-3. 추상클래스


추상 실체 클래스 추상 클래스
실체간에 공통되는 특성을 추출한 것. 객체를 직접 생성할 수 있는 클래스 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스

추상 클래스와 실체 클래스는 상속 관계.
➠실체 클래스는 추상 클래스의 모든 특성 물려받고, 추가 특성 가질 수 있음.
*특성: 필드&메소드.

추상클래스 용도

  • 공통된 필드와 메소드의 이름을 통일한 목적
    여러 사람이 설계할 경우, 같은 기능에 이름을 달리 지을 수 있음.
     추상 클래스에 필드와 메소드를 선언하고, 실체 클래스에서 이를 상속 ➜ 필드와 메소드 이름 통일 가능.
  • 실체 클래스를 작성할 때 시간 절약
    공통되는 것들은 추상 클래스를 상속해서 쓰고, 다른 점만 실체 클래스에 선언하여 시간 절약.

추상 클래스 선언

클래스 선언시 abstract 키워드.
new연산자로 객체생성불가, 상속을 통해 자식 클래스만 만들 수 있음.
필드, 생성자, 메소드 선언 가능.

객체 생성이 불가한데, 생성자가 왜 필요해?
 new 연산자로 직접 생성자를 호출하지는 못하지만, 상속은 한다고 그랬잖아! 자식 객체가 생성될 때 super(---)를 호출하면, 자식이 상속하는 '부모'인, '추상 클래스'의 객체를 생성하므로 추상 클래스도 생성자가 필수!!


예시
추상클래스

package sec03.exam01;
public abstract class Phone {
	// 필드
	public String owner;
	// 생성자
	public Phone(String owner) {
		this.owner = owner;
	}
	// 메소드
	public void turnOn() {
		System.out.println("폰 전원을 켭니다.");
	}
	public void turnOff() {
		System.out.println("폰 전원을 끕니다.");
	}
}

실체클래스

package sec03.exam01;
public class SmartPhone extends Phone {
	//생성자
	public SmartPhone(String owner) {
		super(owner);
	}
	// 메소드
	public void internetSearch() {
		System.out.println("인터넷 검색을 합니다.");
	}
}

실행클래스

package sec03.exam01;
public class PhoneExample {
	public static void main(String[] args) {
		//Phone phone = new Phone(); -> 추상클래스는 new연산자로 객체 생성 불가.
		//자식 생성
		SmartPhone sp = new SmartPhone("홍길동");
		sp.turnOn();
		sp.internetSearch();
		sp.turnOff();
	}
}

실행결과

추상 메소드와 재정의

추상클래스의 목적: 실체 클래스의 멤버(필드&메소드)를 통일하는 것.

추상메소드 작성 이유:
메소드 선언만 통일하고, 실행내용은 실체클래스마다 다를 때.
메소드 선언(공통특징)을 추상클래스에서 하지 않고, 메소드 작성 자체를 실체에서 하려다가 까먹는 경우를 방지하기 위해.
그렇다고해서, 실행내용이 다 다른데... 그냥 디폴트값으로 실행내용 아무거나 써? 어떻게 실행 내용을 작성해?
 추상 메소드가 이~래~서~ 필요하다!

[public / protected] abstract 리턴타입 메소드이름(매개변수,---);

하위 클래스가 반드시 실행 내용을 채우도록 강제하고 싶은 메소드가 있을 경우, 해당 메소드를 추상 메소드로 선언할 것.
자식 클래스가 이를 재정해서 실행 내용 작성하지 않는다면 에러.


예시
추상클래스 + 추상메소드

package sec03.exam02;
public abstract class Animal {
	// 필드
	public String kind;
	// 메소드
	public void breathe() {
		System.out.println("숨을 쉽니다.");
	}
	// 추상메소드
	public abstract void sound(); // {실행부}를 생략함.
}

자식클래스 + 추상메소드 재정의

package sec03.exam02;
public class Dog extends Animal {
	public Dog() {
		this.kind = "포유류";
	}
	@Override
	public void sound() {
		System.out.println("멍멍");
	}
}
package sec03.exam02;
public class Cat extends Animal {
	public Cat() {
		this.kind = "포유류";
	}
	@Override
	public void sound() {
		System.out.println("야옹");
	}
}

실행클래스

package sec03.exam02;
public class AnimalExample {
	public static void main(String[] args) {
		Dog dog = new Dog();
		Cat cat = new Cat();
		dog.sound();
		cat.sound();
		System.out.println("------------");
		Animal animal = null;// 으엥
		animal = new Dog();
		animal.sound();
		animal = new Cat();
		animal.sound();
		System.out.println("------------");
		animalSound(new Dog());
		animalSound(new Cat());
	}
	public static void animalSound(Animal animal) {
		animal.sound();
	}
}

실행결과

728x90