본문 바로가기
Java/이것이 자바다

상속

by k-mozzi 2023. 3. 8.
반응형
Preface

 

11월 중순에 시작했던 상속 파트를 이제서야 마쳤다.

 

상속 자체는 어려운 부분이 없었지만, 다형성을 코드로 구현하는 것이 조금 복잡했다.

 

책의 초반부에서 다형성에 관한 내용을 글로만 읽었을 땐 해당 특성이 무엇을 의미하는 것인지 정확히 알 수 없었는데

 

직접 코드로 구현해보니 다형성이 어떤 특성이고 왜 객체지향 프로그래밍의 장점으로 소개되는지 조금은 이해할 수 있었다.


 

1. 상속의 개념

 

 

- 부모 클래스는 상위 클래스, 자식 클래스는 하위 클래스 또는 파생 클래스라고 부른다.

 

 

- 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.

 

 

- 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다.

 

 

- 상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들의 수정 효과를 가져올 수 있다.

 

 

- 자바는 다중 상속을 허용하지 않는다.

→ 여러 개의 부모 클래스를 상속할 수 없다.

 

 

- 상속 예제

package ch7;

class CellPhone {

	// 필드
	String model;
	String color;

	// 생성자

	// 메소드
	void powerOn() {
		System.out.println("turn on");
	}

	void powerOff() {
		System.out.println("turn off");
	}

	void bell() {
		System.out.println("bell rings");
	}

	void sendVoice(String message) {
		System.out.println("me: " + message);
	}

	void receiveVoice(String message) {
		System.out.println("counterpart: " + message);
	}

	void hangUp() {
		System.out.println("end call");
	}

}

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 " + 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("model: " + dmbCellPhone.model);
		System.out.println("color: " + dmbCellPhone.color);

		// DmbCellPhone의 필드
		System.out.println("channel: " + dmbCellPhone.channel);

		// CellPhone으로부터 상속받은 메소드 호출
		dmbCellPhone.powerOn();
		dmbCellPhone.bell();
		dmbCellPhone.sendVoice("Hello");
		dmbCellPhone.receiveVoice("Hi, this is Kim");
		dmbCellPhone.sendVoice("Oh~ Hi Kim");
		dmbCellPhone.hangUp();

		// DmbCellPhone의 메소드 호출
		dmbCellPhone.turnOnDmb();
		dmbCellPhone.changeChannelDmb(12);
		dmbCellPhone.turnOffDmb();

	}

}

 

 

- 모든 객체는 클래스의 생성자를 호출해야 생성된다.

 

 

- 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다.

 

 

- super( )는 부모의 기본 생성자를 호출한다.

 

 

- 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 존재한다면 자식 생성자에서 반드시 부모 생성자 호출을 위해 super(매개값, ...)를 명시적으로 호출해야 한다.

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

package ch7;

class People {
	public String name;
	public String ssn;

	public People(String name, String ssn) {
		this.name = name;
		this.ssn = ssn;
	}
}

class Student extends People {
	public int studentNo;

	public Student(String name, String ssn, int studentNo) {
		super(name, ssn);
		this.studentNo = studentNo;
	}
}

public class StudentExample {

	public static void main(String[] args) {

		Student student = new Student("KIM", "1234-5678", 1);
		System.out.println("name: " + student.name);
		System.out.println("ssn: " + student.ssn);
		System.out.println("studentNo: " + student.studentNo);

	}

}

 

 

- 메소드 오버라이딩(@Override): 상속된 메소드의 내용이 자식 클래스에 맞지 않을 경우, 자식 클래스에서 동일한 메소드를 재정의하는 것

→ 부모 객체의 메소드는 숨겨지므로 자식 객체에서 메소드를 호출하면 오버라이딩 된 자식 메소드가 호출된다.

 

 

- 메소드 오버라이딩 규칙

1) 부모의 메소드와 동일한 리턴 타입, 메소드 이름, 매개 변수 리스트를 가져야 한다.

2) 접근 제한을 약하게 할 순 있지만, 더 강하게 오버라이딩 할 순 없다.

3) 새로운 예외를 throws할 수 없다.

 

 

- 자식 클래스 내부에서 오버라이딩 된 부모 클래스의 메소드를 호출해야 한다면 명시적으로 super 키워드를 붙여 부모 메소드를 호출할 수 있다.

→ super는 부모 객체를 참조하므로 부모 메소드에 직접 접근할 수 있다.

super.부모메소드();
package ch7;

class Airplane {
	public void land() {
		System.out.println("착륙합니다.");
	}

	public void fly() {
		System.out.println("일반비행합니다.");
	}

	public void takeOff() {
		System.out.println("이륙합니다.");
	}
}

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();
			// 부모 클래스의 메소드 호출
		}
	}
}

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 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.

→ 최종적인 클래스이므로 상속할 수 없는 클래스가 되기 때문이다.

public final class 클래스명 {...}

 

 

- final 메소드는 최종적인 메소드이므로 오버라이딩 할 수 없다.

 

 

- protected 접근 제한자: 같은 패키지에서는 default와 같이 접근 제한이 없지만, 다른 패키지에서는 자식 클래스만 접근을 허용한다.

→ 다른 패키지에 있는 자식 클래스인 경우 new 연산자를 사용해서 부모 생성자를 직접 호출할 순 없지만, super( )로 부모 생성자를 호출하는 것은 가능하다.

 

 

- 다형성: 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질

 

 

- 자바는 부모 클래스로 타입 변환이 가능하다.

→ 부모 타입에 모든 자식 객체가 대입될 수 있다.

 

 

- 클래스 타입 변환: 상속 관계에 있는 클래스 사이에서 발생

1) 자식 타입은 부모 타입으로 자동 타입 변환이 가능하다.

2) 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.

 

 

- 자동 타입 변환(Promotion): 자식은 부모의 특징과 기능을 상속받으므로 부모와 동일하게 취급될 수 있다는 것

부모클래스 변수 = 자식클래스타입;
Cat cat = new Cat();
Animal animal = cat; // Animal animal = new Cat();

// cat과 animal 변수는 타입만 다를 뿐 동일한 Cat 객체를 참조한다.

 

 

- 부모 타입으로 자동 타입 변환된 후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.

→ 단, 메소드가 자식 클래스에서 오버라이딩 되엇다면 자식 클래스의 메소드가 대신 호출된다.

package ch7;

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

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("[자동차가 멈춥니다.]");
	}
}

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

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

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("앞왼쪽 한국타이어로 교체");
				car.frontLeftTire = new HankookTire("앞왼쪽", 15);
				break;
			case 2:
				System.out.println("앞오른쪽 금호타이어로 교체");
				car.frontRightTire = new KumhoTire("앞오른쪽", 13);
				break;
			case 3:
				System.out.println("뒤왼쪽 한국타이어로 교체");
				car.backLeftTire = new HankookTire("뒤왼쪽", 14);
				break;
			case 4:
				System.out.println("뒤오른쪽 금호타이어로 교체");
				car.backRightTire = new KumhoTire("뒤오른쪽", 17);
				break;
			}
			System.out.println("---------------------------");
		}
	}

}

 

 

- 여러 개의 객체를 배열로 관리하면 인덱스로 표현할 수 있어 제어문 사용 시 더욱 효율적이다.

 

 

- 메소드를 호출할 때 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 타입 객체를 지정할 수도 있다.

→ 매개 변수의 다형성

 

 

- 매개 변수의 타입이 클래스일 경우, 해당 클래스의 객체뿐만 아니라 자식 객체까지도 매개값으로 사용할 수 있다.

package ch7;

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

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

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

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

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

}

 

 

- 강제 타입 변환(Casting): 부모 타입을 자식 타입으로 변환하는 것

→ 자식 타입이 부모 타입으로 자동 변환된 후, 다시 자식 타입으로 변환하는 경우에만 강제 타입 변환을 사용할 수 있다.

자식클래스 변수 = (자식클래스) 부모클래스타입(자식 타입이 부모 타입으로 변환된 상태);

 

 

- instanceof 연산자: 어떤 객체가 어떤 클래스의 인스턴스인지 확인할 수 있는 연산자

→ 매개값의 타입을 조사할 때 주로 사용한다. (강제 타입 변환 가능 유무를 확인하기 위해)

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

 

 

- 클래스의 종류

1) 실체 클래스: 객체를 직접 생성할 수 있는 클래스

2) 추상 클래스: 클래스들의 공통적인 특성을 추출해서 선언한 클래스

 

 

- 추상 클래스가 부모이고 실체 클래스가 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을 물려받고, 추가적인 특성을 가질 수 있다.

 

 

- 추상 클래스는 객체를 직접 생성해 사용할 수 없다.

→ new 연산자를 사용해서 인스턴스를 생성시킬 수 없다.

 

 

- 추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다.

→ extends 뒤에만 올 수 있는 클래스이다.

 

 

- 추상 클래스의 용도

1) 실체 클래스들의 공통된 필드와 메소드 이름을 통일할 목적

2) 실체 클래스를 작성할 때 시간을 절약

 

 

- 추상 클래스를 선언할 땐 클래스 선언부에 abstract 키워드를 붙여야 한다.

1) new 연산자를 이용해 객체를 생성할 순 없고, 상속을 통해 자식 클래스만 만들 수 있다.

2) new 연산자로 직접 생성자를 호출할 수는 없지만, 자식 객체가 생성될 때 super(...)를 호출해서 추상 클래스 객체를 생성하므로 반드시 생성자가 있어야 한다.

package ch7;

abstract class Phone {
	public String owner;

	public Phone(String owner) {
		this.owner = owner;
	}

	public void turnOn() {
		System.out.println("turn on the phone");
	}

	public void turnOff() {
		System.out.println("turn off the phone");
	}
}

class SmartPhone extends Phone {
	public SmartPhone(String owner) {
		super(owner);
	}

	public void internetSearch() {
		System.out.println("search for the internet");
	}
}

public class PhoneExample {

	public static void main(String[] args) {
//		Phone phone = new Phone();

		SmartPhone smartphone = new SmartPhone("Kim");

		smartphone.turnOn();
		smartphone.internetSearch();
		smartphone.turnOff();
	}

}

 

 

- 추상 메소드: 추상 클래스에서만 선언할 수 있는 메소드로, 메소드의 선언부만 있고 실행 내용인 중괄호가 없는 메소드

1) abstract 키워드가 붙어 있다.

2) 자식 클래스에서 추상 메소드를 필수로 재정의(오버라이딩)해서 실행 내용을 작성해야 한다.

public abstract class Animal {
	public abstract void sound();
}

// 자식 클래스에서 sound() 메소드를 반드시 재정의해야 한다.

 

728x90
반응형

댓글