상속

박영준·2022년 12월 7일
0

Java

목록 보기
25/111

1. 정의

  • 클래스를 재사용하는 방법 中 하나!

  • 자식(클래스)이 상속받고싶은 부모(클래스)를 선택해서 물려받는다.

    • 자식 클래스 : 상속받는 클래스/하위 클래스/서브 클래스
    • 부모 클래스 : 상속을 해주는 클래스/상위 클래스/슈퍼 클래스
  • 상속의 사용조건

    1. IS-A 관계 성립할 때
    2. 재사용 관점 X
      기능의 확장 관점 O

    무작정 상속을 사용할 경우,
    클래스 간 결합도가 너무 높아져서, 유지 보수 효율 ↓

    IS-A

    • 포함 관계
    • 한 클래스 A가 다른 클래스 B의 자식 클래스 다.
    • 예시 : 햄스터는 동물이다. (소의 부모 클래스는 동물)

    HAS-A

    • 상속이 아닌 구성 (Composition) 관계
    • 한 객체가 다른 객체에 속한다.
    • 예시 : 컴퓨터 안에는 CPU 가 있다. (컴퓨터 객체가 CPU 객체를 구성함)

2. 특징

  1. 부모 클래스는 여러 개의 자식 클래스에게 상속 O
    자식 클래스는 여러 부모로부터 다중 상속 받는 것은 X

    • 단일 상속만 O.
      즉, 둘 이상의 클래스로부터 상속받지 X
      (자바에서는 단일 상속만 가능!)
  2. 부모 클래스는 자식 클래스에서 정의한 메소드나 필드를 사용하지 못한다. (자식 클래스는 부모 클래스에 영향 X)

    • 자식 = 자신 + 부모
    • 부모 = 자신
  3. 접근 제한자
    1) 부모 클래스에서 private 접근 제한을 갖는 필드, 메소드는 X
    2) 부모 - 자식 클래스가 다른 패키지에 있는 경우, default 접근 제한을 갖는 필드, 메소드
    참고: 캡슐화, 접근 제한자

3. 사용법

문법

public class 부모클래스 {
	...
}
public class 자식클래스 extends 부모클래스 {
	...
}
  • extends
    • 부모 클래스를 '확장(extends)'한다는 의미로 받아들이면 되겠다.
      • 따라서, 항상 '자식 클래스의 멤버 변수의 개수 >= 조상 클래스의 멤버 변수의 개수'
        (멤버 변수 : 인스턴스 변수, 클래스 변수)

1) 부모/자식

부모 클래스 CellPhone

public class CellPhone {

    // 필드
    String model;
    String color;
}

자식 클래스 DmbCellPhone

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

2) 조부모/부모/자식

// 조부모 클래스
class A {
	String aField = "클래스 A 필드";
	public void aMethod() {
		System.out.println(aField);
		// System.out.println("A : "+ bField); 	// 컴파일 에러(자식 필드 사용 불가)
	}
}

// 부모 클래스
class B extends A {
	String bField = "클래스 B 필드";
	public void bMethod() {
		System.out.println(aField); 	// 부모 클래스 필드 사용
		System.out.println(bField); 	// 본인 클래스 필드 사용
		// System.out.println("B : "+cField); 	// 컴파일 에러(자식 필드 사용 불가)
	}
}

// 자식 클래스
class C extends B {
	String cField = "클래스 C 필드";
	public void cMethod() {
		System.out.println(aField); 	// 조부모 클래스 필드 사용
		System.out.println(bField); 	// 부모 클래스 필드 사용
		System.out.println(cField); 	// 본인 클래스 필드 사용
	}
}

3) object 클래스

  • object 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상 클래스

  • 따로 extends object를 추가해주지 않아도, 컴파일러가 자동 추가한다

  • 메서드 : totring(), equals(Object o) 등...

4) super

(1) super

부모 클래스

class Parent {
	int x = 10;		// super.x
}

자식 클래스

class Child extends Parent {
	int x = 20;			// this.x -> 부모 클래스에서의 int x 와 이름이 동일하다
    
    ...
    
}
  • 자식 클래스에서 부모 클래스로부터 상속받은 멤버를 참조할 때 사용되는 참조변수
    • 상속받은 멤버와 자신의 멤버의 이름이 동일할 때, 구분하기 위해서 사용
    • this 같은 역할

(2) super(매개값, ...)

부모 클래스

class Point {
	int x;
    int y;
    
    Point (int x, int y) {
    	this.x = x;
        this.y = y;
    }
}

자식 클래스

class Point3D extends Point {
	int z;
    
    Point3D (int x, int y, int z) {
    	this.x = x;
        this.y = y;
        tihs.z = z;
    }
}

// super() 를 사용해서 고쳐보면
class Point3D extends Point {
	int z;
    
    Point3D (int x, int y, int z) {
    	super(x, y);		// 부모 클래스의 생성자인 Point (int x, int y) 를 호출해서 초기화
        tihs.z = z;			// 자신의 멤버를 초기화
    }
}
  • super(매개값, ...) 으로 부모 생성자 호출이 가능

    • this() 처럼 생성자 역할을 한다
    • 자식 생성자 '첫 줄'에만 위치 가능
    • 매개값 타입과 일치하는 부모 생성자만 호출 가능
  • 단, 생성자는 상속되지 않는다!

5) 오버로딩, 오버라이딩

참고: 오버로딩, 오버라이딩

6) 추상 클래스

(1) 정의

  • 객체: Bird, Insect, Fish
    특성: 필드, 메소드

  • 추상 클래스 : 이 클래스들의 공통적 특성을 추출하여 선언한 클래스
    실체 클래스 : 객체를 직접 생성할 수 있는 클래스

즉,
Bird.class, Insect.class, Fish.class 의 공통적인 필드와 메소드를 따로 선언한 Animal.class 만드는 것
= 추상 클래스

  • abstract 키워드 붙이기

  • new 연산자 사용 X
    → 상속을 위한 클래스이므로, 따로 객체 생성이 불가능
    상속을 통해, 자식 클래스 생성은 가능

  • 클래스 = "설계도"
    추상 클래스 = "미완성의 설계도"

    • 클래스가 미완성 메서드(추상 메서드)를 포함하고 있다는 의미
  • 추상화 ↔ 상속

    • "추상화" = 기존 클래스의 공통부분을 뽑아서, 부모 클래스를 만드는 것
    • "상속" = 자식 클래스를 만드는데, 부모 클래스를 사용하는 것
  • 하나의 메서드라도 추상 메서드가 되면, 해당 클래스는 추상 클래스가 되어야 함

    public abstract class Bird {		// 추상 클래스
    	public abstract void sing();		// 추상 메서드
    }
  • 그러나, 추상 메서드라고 해도, 일반 메서드도 사용 가능하다

    public abstract class Bird {		// 추상 클래스
    	public abstract void sing();		// 추상 메서드
      public void fly();		// 일반 메서드
    }

(2) abstract

abstract class EX {			// 추상 클래스
	abstract void move();		// 추상 메서드
}
  • "abstract" = "미완성의"

  • 추상 클래스 : 클래스 내에 추상 메서드가 선언됐음을 의미 -> 미완성 설계도 (인스턴스 생성 불가)

  • 추상 메서드 : 선언부만 작성 O. 구현부는 아직 작성 x

    • 즉, 설게만 해두고 O. 실제 수행될 내용은 아직 작성 X

(3) 필요성

  • 여러 종류의 TV를 만든다고 가정할 때
    각 종류의 TV들의 기본적인 설계는 동일할 것이다.(TV 라는 공통된 부분 = 미완성 설계도)
    이 미완성 설계도를 바탕으로, 각각의 TV의 종류에 따라 완성해나가는 편이 더 효율적인 방식이다.
  • 공통된 필드, 메소드의 이름 통일
    (단, 데이터와 기능일 동일함에도, 이름이 다른 경우도 있음)

  • 실체 클래스 작성 시, 시간 절약
    → 공통된 필드, 메소드 : 추상 클래스에 모두 선언
    → 다른 부분만 : 실체 클래스에 선언

(4) 사용법

① 추상 메서드

부모 클래스

abstract class Player {		// 추상 클래스
    abstract void play(int pos);		// 추상 메서드 
    abstract void stop();		// 추상 메서드 
}

자식 클래스

class AudioPlayer extends Player {

	void play(int pos) {		// 추상 메서드 구현
    	...
    }
    
    void stop() {		// 추상 메서드 구현
    	...
    }
}

자식 클래스

abstract class AbstractPlayer extends Player {

	void play(int pos) {		// 추상 메서드 구현
    	...
    }
}

② 추상 클래스

  1. 기존의 코드
    Marine 클래스
public class Marine {
    int x, y;       // 현재 위치
    
    void move(int x, int y) {
    }

    void stop() {
    }

    void simPack() {
    }

}

Tank 클래스

public class Tank {
    int x, y;       // 현재 위치
    
    void move(int x, int y) {
    }

    void stop() {
    }

    void changeMode() {
    }
}

Dropship 클래스

public class Dropship {
    int x, y;       // 현재 위치
    
    void move(int x, int y) {
    }

    void stop() {
    }

    void load() {
    }

    void unload() {
    }
}
  1. 추상 클래스로 작성된 코드
    Unit 추상 클래스(부모 클래스)
public class Unit {
    int x, y;
    
    abstract void move(int x, int y);
    
    void stop() {
    }
}

Marine 자식 클래스

public class Marine extends Unit {
    void move(int x, int y) {
    }

    void simPack() {
    }
}

Tank 자식 클래스

public class Tank extends Unit {
    void move(int x, int y) {
    }

    void changeMode() {
    }
}

Dropship 자식 클래스

public class Dropship extends Unit {
    void move(int x, int y) {
    }

    void load() {
    }

    void unload() {
    }
}
  • 각 자식클래스의 move 이동 방법은 지상/공중이므로 다르지만,
    stop 멈추는 것은 동일하므로(공통된 부분), 추상 클래스에 선언해주었다

② 추상 클래스의 오버라이딩 (재정의)

  1. 정의
  • Animal 추상 클래스 - sound() 메소드

    동물들은 소리를 낸다. → 공통된 특성
    동물들은 다양한 소리를 낸다. → 실체 클래스마다 차이점

  • 하위 클래스에 강제하고 싶은 메소드가 있을 경우, 해당 메소드를 추상 메소드로 선언
    → 자식 클래스는 추상 메소드를 재정의 해서 사용해야함 (그렇지 않을 시, 컴파일 에러)

    참고: 인터페이스 (interface) - 3) 추상 메소드 선언

  1. 예시
    부모 클래스

    public abstract class Animal {
    
        //필드
        public String kind;
    
        //메소드
        public void breathe() {
            ...
        }
    
        public abstract void sound();   //추상 메소드
    }

    자식 클래스 1

    public class Dog extends Animal {
        public Dog() {
            this.kind = "포유류";
        }
    
        //추상 메소드 재정의
            //이 부분이 없다면, 컴파일 에러 발생
        @Override
        public void sound() {
            ...
        }
    }

    자식 클래스 2

    public class Cat extends Animal {
        public Cat() {
            this.kind = "포유류";
        }
    
        //추상 메소드 재정의
        //이 부분이 없다면, 컴파일 에러 발생
        @Override
        public void sound() {
            ...
        }
    }

    AnimalExample

    public class AnimalExample {
        public static void main(String[] args) {
    
            // Dog, Cat 변수 호출
            Dog dog = new Dog();
            Cat cat = new Cat();
    
            dog.sound();
            cat.sound();
    
            System.out.println("-----");
    
            // 변수의 자동 타입 변환 (Dog, Cat 자식 -> Animal 부모)
            Animal animal = null;
    
            animal = new Dog();     //변수 animal 로 타입 변환
            animal.sound();     //자식 클래스 Dog 의 sound() 메소드 호출
    
            animal = new Cat();     //변수 animal 로 타입 변환
            animal.sound();     //자식 클래스 Cat 의 sound() 메소드 호출
    
            System.out.println("------");
    
            // 메소드의 다형성
            animalSound(new Dog());     // animalSound() 라는 새로운 메소드를 선언 (animalSound()는 잠깐 사용하는 새로운 그릇일 뿐)
            animalSound(new Cat());     // animalSound() 라는 새로운 메소드를 선언
        }
    
        // 자동 타입 변환
        // animalSound() 메소드 안에 있던 자식 객체 Dog, Cat 을 --> animalSound() 메소드 안의 부모 타입의 매개변수 animal 에 대입
        public static void animalSound(Animal animal) {
            // 부모 타입으로 자동 타입 변환되어 재정의된 sound() 메소드 호출
            animal.sound();
        }
    }

(5) 인터페이스 VS 추상 클래스

참고: 인터페이스 (interface) - 4. 인터페이스 VS 추상 클래스

7) 자동 타입 변환

참고: 타입 - 자동 타입 변환

(1)

부모 클래스

Class Animal {
	...
}

자식 클래스

Class Cat extends Animal {
	...
}  

AnimalExample

public class AnimalExample {

	...

  	/* Animal animal = new Cat(); 도 가능 → 상속을 통해 생성했기 때문 */
  	Cat cat = new Cat();	// 1. Cat 객체 생성
  	Animal animal = cat		// 2. cat 객체를 Animal 변수에 대입 → 자동으로 타입 변환

}

cat 변수 와 animal 변수 모두, 동일한 객체 Animal 을 참조

(2) 상위 타입

B b = new B();
C c = new C();
D d = new D();
E e = new E();
A a1 = b;
A a2 = c

B b1 = d;
C c1 = e;

B b3 = e;	//불가능: e를 b에 대입하지 못한다
C c2 = d;	//불가능: d를 c에 대입하지 못한다

D 객체 --> B 타입, A 타입으로 자동 타입 변환 O
E 객체 --> C 타입, A 타입으로 자동 타입 변환 O

D 객체 --> C 타입 자동 타입 변환 X (상속 관계가 아니므로)
E 객체 --> B 타입 자동 타입 변환 X

8) 강제 타입 변환

참고: 타입 - 강제 타입 변환

(1) 정의

  • 자동 타입 변환 이후에 → 강제 타입 변환 가능

  • 단, 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환 X

(2) 사용법

(부모 클래스(Parent), 자식 클래스(Child) 모두 있었다고 가정한다.)

ChildExample

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 = "yy";
        child,method3();
    }
}

객체의 타입 확인 : instanceof 활용

// 문법
boolean result = 객체 instanceof 타입

// 예시
public void method (Parent parent) {
	if (parent instanceof Child) {		// 객체: parent,  타입: Child
		Child child = (Child) parent;
	}
}

매개값 parent 가 Parent 객체인지, Child 객체인지 연산자로 확인 필요

만약,
우측(Child)로 객체 생성했다면 true
좍측(parent)로 객체 생성했다면 false

주의!
타입 확인하지 않고 강제 타입 변환 시, ClassCastException 발생

참고: Java 예약어 - 2) 클래스, 메소드, 변수 선언, 객체 생성과 관련

9) 다형성

참고: OOP (Object-Oriented Programming, 객체 지향 프로그래밍) - 4) 다형성 (Polymorphism)

(1) 정의

사용 방법은 동일하나, 다양한 객체를 이용해서 다양한 실행결과가 나오도록하는 성질

  • 필드 타입을 부모 타입으로 선언하면, 다양한 자식 객체들이 저장될 수 있다.
    → 필드 사용의 결과가 달라질 수 있음

(2) 사용법

① 필드의 다형성

  1. 객체 생성 : 부모 클래스
    Tire

    public class Tire {
    
        //필드
        public int maxRotation;     //최대 회전수
        public int accumulatedRotation;     //누적 회전수
        public String location;     //타이어 위치
    
        //생성자
        public Tire (String location, int maxRotation) {
            this.maxRotation = maxRotation;     //매개 변수 maxRotation 를 받아서, 필드 this.maxRotation 에 저장
            this.location = location;     //매개 변수 location 를 받아서, 필드 this.location 에 저장
        }
    
        //메소드
        public boolean roll() {
            ++accumulatedRotation;      //roll() 메소드를 1번 실행시킬때마다, accumulatedRotation 를 1씩 증가시킴
    
            //roll() 메소드의 실행결과 1
            if (accumulatedRotation < maxRotation) {        //그러다가, accumulatedRotation < maxRotation 인 경우라면
                System.out.println(location + " Tire 수명: " + (maxRotation - accumulatedRotation) + "회");
                return true;
    
            //roll() 메소드의 실행결과 2
            } else {        //그러다가, accumulatedRotation < maxRotation 가 아닌 경우(accumulatedRotation = maxRotation)라면
                System.out.println("*** " + location + " Tire 펑크 ***");
                return false;
            }
        }
    }

    Car

    public class Car {
    
        //필드
        //4개의 타이어를 가짐
        Tire frontLeftTire = new Tire ("앞왼쪽", 6);
        Tire frontRightTire = new Tire ("앞오른쪽", 6);
        Tire backLeftTire = new Tire ("뒤왼쪽", 6);
        Tire backRightTire = new Tire ("뒤오른쪽", 6);
    
        //생성자
    
        //메소드 1
        //각 Tire 객체의 roll() 메소드를 호출해서, roll() 메소드가 false 를 리턴한다면, stop() 메소드를 호출하고, 1을 리턴
        int run() {
            System.out.println("[자동차가 달립니다.]");
    
            if (frontLeftTire.roll() == false) {    //Tire 객체의 roll() 메소드를 호출해서, roll() 메소드가 false 를 리턴한다면
                stop();          //stop() 메소드를 호출하고
                return 1;       //1을 리턴
            }
    
            if (frontRightTire.roll() == false) {    //Tire 객체의 roll() 메소드를 호출해서, roll() 메소드가 false 를 리턴한다면
                stop();          //stop() 메소드를 호출하고
                return 2;       //2를 리턴
            }
    
            if (backLeftTire.roll() == false) {
                stop();
                return 3;
            }
    
            if (backRightTire.roll() == false) {
                stop();
                return 4;
            }
    
            return 0;
        }
    
        //메소드 2: 펑크 났을 때 실행
        void stop() {
            System.out.println("[자동차가 멈춥니다.]");
        }
    }
  2. 자식 클래스
    Tire 클래스의 자식 클래스 1

    public class HanKookTire extends Tire {
    
        //필드
    
        //생성자
        public HanKookTire (String location, int maxRotation) {
            super(location, maxRotation);
        }
    
        //메소드
        //roll() 메소드의 재정의로 인해, Tire 클래스에서의 roll() 메소드의 출력 결과와는 달라지게 됨
        @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;
            }
        }
    }

    Tire 클래스의 자식 클래스 2

    public class KumhoTire extends Tire {
    
        //필드
    
        //생성자
        public KumhoTire(String location, int maxRotation) {
            super(location, maxRotation);
        }
    
        //메소드
        //roll() 메소드의 재정의로 인해, Tire 클래스에서의 roll() 메소드의 출력 결과와는 달라지게 됨
        @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;
            }
        }
    }
  3. CarExample

    public class CarExample {
        public static void main(String[] args) {
    
            //Car 객체 생성
            Car car = new Car();
    
            //for 문을 통해, Car 객체의 run() 메소드 5번 실행
            for (int i = 1; i <= 5; i++) {
                int problemLocation = car.run();
    
                switch (problemLocation) {
                    case 1:
                        System.out.println("앞왼쪽 HanKookTire 로 교체");         //앞외쪽 Tire 가 펑크 났을 때,
                        car.frontLeftTire = new HanKookTire ("앞왼쪽", 15);        //HanKookTire 로 교체
                        break;
    
                    case 2:
                        System.out.println("앞오른쪽 KumhoTire 로 교체");         //앞오른쪽 Tire 가 펑크 났을 때,
                        car.frontRightTire = new KumhoTire ("앞오른쪽", 15);        //KumhoTire 로 교체
                        break;
    
                    case 3:
                        System.out.println("뒤왼쪽 HanKookTire 로 교체");
                        car.backLeftTire = new HanKookTire ("뒤왼쪽", 15);     //Car 객체의 backLeftTire 필드에 HanKookTire 객체를 대입 (= 자동 타입 변환)
                        break;                                                      //--> HanKookTire 클래스의 재정의된 메소드 roll() 를 실행
    
                    case 4:
                        System.out.println("뒤오른쪽 KumhoTire 로 교체");
                        car.backRightTire = new KumhoTire ("뒤오른쪽", 15);     //Car 객체의 backRightTire 필드에 KumhoTire 객체를 대입 (= 자동 타입 변환)
                        break;                                                      //--> KumhoTire 클래스의 재정의된 메소드 roll() 를 실행
                }
    
                System.out.println("------");       //for 문을 1회 돌린 후, 출력될 내용
            }
        }
    }

    모든 자동차는 타이어를 사용하는데, 한국 타이어(객체)와 금호 타이어(객체)의 성능은 다르게 나온다.

② 매개 변수의 다형성

  1. 객체 생성 : 부모 클래스
    Vehicle

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

    Driver

    public class Driver {
        public void drive(Vehicle vehicle) {
            //Vehicle 타입의 매개변수 vehicle 를 받아서, run() 메소드 실행
            //
            vehicle.run();      
        }
    }
  2. 자식 클래스
    Vehicle 클래스의 자식 클래스 1

    public class Bus extends Vehicle {
        @Override
        public void run() {			//Vehicle 클래스의 run() 메소드 재정의
            System.out.println("버스가 달립니다.");
        }
    }

    Vehicle 클래스의 자식 클래스 1

    public class Taxi extends Vehicle {
        @Override
        public void run() {			//Vehicle 클래스의 run() 메소드 재정의
            System.out.println("택시가 달립니다.");
        }
    }
  3. DriverExample

    public class DriverExample {
        public static void main(String[] args) {
    
            //객체 생성
            Driver driver = new Driver();
            Bus bus = new Bus();
            Taxi taxi = new Taxi();
    
            //Driver 객체의 driver() 메소드 호출 + Bus 객체 제공
            driver.drive(bus);		
    
            //Driver 객체의 driver() 메소드 호출 + taxi 객체 제공 --> Taxi 객체의 run() 메소드 실행
            driver.drive(taxi);		
        }
    }
  4. 실행결과
    버스가 달립니다.
    택시가 달립니다.

참고: 오버로딩, 오버라이딩 - 오버라이딩 (overriding)

4. 장점

  1. 코드 중복 ↓
    → 이미 잘 개발된 클래스를 재사용해서, 새로운 클래스를 만들기 때문

  2. 유지 보수 시간 최소화
    → 부모 클래스 수정은 곧 모든 자식 클래스들의 수정이기도 하기 때문

  3. 다형성 구현

  4. 통일성 있는 코드


참고: [JAVA/자바] 상속의 개념 및 부모/자식 클래스
참고: 객체지향 프로그래밍 제대로 이해하기

profile
개발자로 거듭나기!

0개의 댓글