상속은 이미 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 코드의 중복을 줄인다.
상속을 해도 부모 클래스의 모든 필드와 메소드를 물려받는 것은 아니다. 부모 클래스에서 private접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다. 그리고 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다. 그 이외의 경우는 모두 상속의 대상이 된다.
부모 생성자 호출
부모 객체가 먼저 생성되고 자식 객체가 그 다음에 생성된다. 아래 코드는 DmbCellPhone 객체만 생성하는 것처럼 보이지만. 사실은 내부적으로 부모인 CellPhone 객체가 먼저 생성되고. DmbCellPhone객체가 생성된다.]
DmbCellPhone dmbCellPhone = new DmbCellPhone();
모든 객체는 클래스의 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아니다. 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다. 예를 들어 DmbCellPhone의 생성자가 명시적으로 선언되지 않았다면 컴파일러는 다음과 같은 기본 생성자를 생성해 낸다.
public DmbCellPhone() {
super();
}
super()는 부모의 기본 생성자를 호출한다. 즉 CellPhone클래스의 다음 생성자를 호출한다.
public CellPhone() {
}
메소드 재정의
메소드를 오버라이딩 할 때는 다음과 같은 규칙에 주의해서 작성해야 한다.
- 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 리스트)를 가져야 한다.
- 접근 제한을 더 강하게 오버라이딩할 수 없다.
- 새로운 예외(Exception)을 throws할 수 없다.
부모 메소드 호출(super)
자식 클래스 내부에서 오버라이딩된 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super키워드를 붙여서 부모 메소드를 호출할 수 있다.
package com.company.Inheritance;
public class Airplane {
public void land() {
System.out.println("착륙합니다.");
}
public void fly() {
System.out.println("일반비행합니다.");
}
public void takeOff() {
System.out.println("이륙합니다.");
}
}
package com.company.Inheritance;
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();
}
}
public static void main(String[] args) {
SupersonicAirplane sa = new SupersonicAirplane();
sa.takeOff();
sa.flyMode = SupersonicAirplane.SUPERSONIC;
sa.fly();
sa.flyMode = SupersonicAirplane.NORMAL;
sa.fly();
sa.land();
}
}
//이륙합니다.
//초음속비행합니다.
//일반비행합니다.
//착륙합니다.
또한 클래스와 메소드 선언 시에 final키워드가 지정되면 상속과 관련이 있다.
< 상속할 수 없는 finla 클래스 >
클래스를 선언할 떄 final클래스를 class앞에 붙이게 되면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 된다. 즉 final클래스는 부모 클래스가 될 수 없어서 자식 클래스를 만들 수 없다는 것이다.
final클래스의 대표적인 예는 자바 표준 API에서 제공하는 String클래스이다. String 클래스는 다음과 같이 선언되어 있다.
public class NewString extends String()이 안된다는 뜻이다.
< 오버라이딩할 수 없는 final 메소드 >
메소드를 선언할 떄 final키워드를 붙이게 되면 이 메소드는 최종적인 메소드이므로 오버라이딩을 할 수 없는 메소드가 된다. 즉 부모 클래스를 상속해서 자식 클래스를 선언할 떄 부모 클래스에 선언된 final메소드는 자식 클래스에서 재정의할 수 없다는 것이다.
자동 타입 변환
자동 타입 변환의 개념은 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급받을 수 있다는 것이다.
왜 쓰는지를 간단한 예제를 만들어 보면서 이해해 보자
< 필드의 다형성 >
package com.company.Inheritance;
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;
}
}
}
package com.company.Inheritance;
public class Car {
Tire[] tires = {
new Tire("앞왼쪽", 6),
new Tire("앞오른쪽", 2),
new Tire("뒤왼쪽", 3),
new Tire("뒤오른쪽", 4)
};
int run() {
System.out.println("[자동차가 달립니다]");
for(int i = 0; i < tires.length; i++) {
if(tires[i].roll() == false) {
stop();
return i + 1;
}
}
return 0;
}
void stop() {
System.out.println("[자동차가 멈춥니다]");
}
}
package com.company.Inheritance;
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 com.company.Inheritance;
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;
}
}
}
package com.company.Inheritance;
public class CarExample {
public static void main(String[] args) {
Car car = new Car();
for(int i = 1; i <= 5; i++) {
int problemLocation = car.run();
if(problemLocation != 0) {
System.out.println(car.tires[problemLocation].location + " HankookTire로 교체");
car.tires[problemLocation - 1] = new HankookTire(car.tires[problemLocation - 1].location, 15);
}
System.out.println("----------------------------");
}
}
}
Car객체의 Tire 필드에 HankookTire와 KumhoTire에서 메소드 오버라이딩된 roll()메소드가 실행된다.
< 매개 변수의 다형성 >
자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만 주로 메소드를 호출할 때 많이 발생한다. 메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 타입 객체를 지정할 수도 있다.
예시를 통해 이해해보자
package com.company.Inheritance.VehicleExample;
public class Vehicle {
public void run() {
System.out.println("차량이 달립니다.");
}
}
package com.company.Inheritance.VehicleExample;
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
package com.company.Inheritance.VehicleExample;
public class Bus extends Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
package com.company.Inheritance.VehicleExample;
public class Taxi extends Vehicle{
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
package com.company.Inheritance.VehicleExample;
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);
}
}
//버스가 달립니다.
//택시가 달립니다.
강제 타입 변환
package com.company.Inheritance.TypeCasting;
public class Parent {
}
package com.company.Inheritance.TypeCasting;
public class Child extends Parent{
}
package com.company.Inheritance.TypeCasting;
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; // ClassCastException이 발생할 가능성이 있음
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: com.company.Inheritance.TypeCasting.Parent cannot be cast to com.company.Inheritance.TypeCasting.Child
// at com.company.Inheritance.TypeCasting.InstanceofExample.method2(InstanceofExample.java:14)
// at com.company.Inheritance.TypeCasting.InstanceofExample.main(InstanceofExample.java:25)
InstanceofExample 클래스에서 method1()과 method2()를 호출할 경우, Child객체를 매개값으로 전달하면 두 메소드 모두 예외가 발생하지 않지만 Parent객체를 매개값으로 전달하면 method2에서는 ClassCastException이 발생한다.
추상 클래스
추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에 객체를 직접 생성해서 사용할 수 없다. 다시 말해서 추상 클래스는 new 연산자를 사용해서 인스턴스를 생성시키지 못한다.
추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다.
< 추상 메소드와 오버라이딩 >
추상 클래스는 실체 클채스가 공통적으로 가져야 할 필드의 메소드들을 정의해 놓은 추상적인 클래스이므로 실체 클래스의 멤버(필드, 메소드)를 통일화하는데 목적이 있다.
package com.company.Inheritance.Abstract;
public abstract class Animal {
public String kind;
public void breathe() {
System.out.println("숨을 쉽니다.");
}
public abstract void sound();
}
package com.company.Inheritance.Abstract;
public class Cat extends Animal{
public Cat() {
this.kind = "포유류";
}
@Override
public void sound() {
System.out.println("야옹");
}
}
package com.company.Inheritance.Abstract;
public class Dog extends Animal{
public Dog() {
this.kind = "포유류";
}
@Override
public void sound() {
System.out.println("멍멍");
}
}
package com.company.Inheritance.Abstract;
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();
}
}
//멍멍
//야옹
//------
//멍멍
//야옹
//------
//멍멍
//야옹
'School > Java Programming' 카테고리의 다른 글
Java Programming - 중첩 클래스 & 중첩 인터페이스 (0) | 2022.03.17 |
---|---|
Java Programming - Interface (0) | 2022.03.10 |
Java Programming - Class (0) | 2022.03.09 |
Java Programming - Array (0) | 2022.03.09 |
Java Programming - java의 동작 방식 (0) | 2022.03.03 |