본문 바로가기
JAVA/이재환의 자바 프로그래밍 입문

[Java] Ch.12 클래스의 상속

by ♡˖GYURI˖♡ 2023. 10. 20.

상속

자바 = 객체지향 언어

객체지향 언어의 가장 큰 특징 중 하나가 바로 상속

상속

  • 클래스가 가지고 있는 멤버를 다른 클래스에게 계승시키는 것
  • 상속한 멤버는 자식 클래스에서 정의하지 않아도 사용 가능
  • 자식 클래스 내에서 멤버를 추가로 정의해서도 사용 가능
  • private으로 접근 제한이 되어 있는 멤버들은 서브 클래스로 상속되지 않음

 

상속의 장점

  • 클래스 간의 전체 계층 구조를 파악하기 쉬움
  • 재사용성 증대
  • 확장 용이
  • 유지보수 용이

 

▼상속 관련 다양한 호칭

상속 대상 클래스 호칭   상속을 받은 클래스 호칭
슈퍼 클래스 서브 클래스
부모 클래스 자식 클래스
기반 클래스 파생 클래스
조상 클래스 자손 클래스
상위 클래스 하위 클래스

 

class Marine extends Unit
{

}

 

class Unit
{
    String name;
    int hp;
    
    void printUnit()
    {
        System.out.println("이름 : " + name);
        System.out.println("HP : " + hp);
    }
}

class Marine extends Unit
{
    int attack;
    
    void printMarine()
    {
        printUnit();
        System.out.println("공격력 : " + attack);
    }
}

class Medic extends Unit
{
    int heal;
    
    void printMedic()
    {
        printUnit();
        System.out.println("치유량 : " + heal);
    }
}

public class MyTerran
{
    public static void main(String[] args)
    {
        Marine unit1 = new Marine();
        unit1.name = "마린";
        unit1.hp = 100;
        unit1.attack = 20;
        
        Medic unit2 = new Medic();
        unit2.name = "메딕";
        unit2.hp = 120;
        unit2.heal = 10;
        
        unit1.printMarine();
        System.out.println();
        unit2.printMedic();    
    }
}

 

 

자바와 다중 상속

자바는 여러 클래스를 동시에 상속하는 다중 상속을 지원하지 않음

예를 들어 두 개 이상의 상위 클래스에 같은 이름의 메서드가 정의되어 있다면, 다중 상속을 받는 하위 클래스는 어떤 클래스의 메서드를 상속받아 사용해야 할지 헷갈리게 됨

다중 상속을 지원하는 C++ 같은 언어는 이를 문법적으로 구분해 사용하지만, 자바는 이런 모호함 자체를 없애기 위해 다중 상속 대신 단계별 상속을 사용함

 

 

오버라이딩

상속된 메서드와 동일한 이름, 동일한 매개변수를 가지는 메서드를 정의하여 메서드를 덮어쓰는 것

반환값의 형도 반드시 같아야 함

 

오버라이딩의 목적

  • 상속받은 부모 클래스 메서드의 기능 변경
  • 상속받은 부모 클래스 메서드에 기능 추가
class Unit
{
    String name;
    int hp;
    
    void doMove()
    {
        System.out.println("이동속도 10으로 이동");
    }
}

class Marine extends Unit
{
    int attack;
    
    void doMove()
    {
        super.doMove();
        System.out.println(attack + " 공격");
    }
}

class Medic extends Unit
{
    int heal;
    
    void doMove()
    {
        System.out.println("이동속도 8으로 이동");
        System.out.println(heal + " 치유");
    }
}

public class MyTerran
{
    public static void main(String[] args)
    {
        Marine unit1 = new Marine();
        unit1.name = "마린";
        unit1.hp = 100;
        unit1.attack = 20;
        
        Medic unit2 = new Medic();
        unit2.name = "메딕";
        unit2.hp = 120;
        unit2.heal = 10;
        
        unit1.doMove();
        System.out.println();
        unit2.doMove();    
    }
}

 

 

상속이 제한되는 final

final은 필드나 메서드, 클래스에 붙이는 예약어

final int MAX_NUM = 100;

final class Marine
{

}

 

▼final의 위치별 의미

사용 위치 설명
변수 final 변수는 상수를 의미
메서드 final 메서드는 하위 클래스에서 오버라이딩 불가
클래스 final 클래스는 상속 불가

 

final 상수 때처럼 클래스도 "이 클래스가 마지막이야! 더 이상 상속하면 안돼!"라는 의미로 final을 클래스 앞에 붙여준 것이라 생각하면 됨

(클래스의 일부 메서드를 상속 제한하려면 메서드에 접근 제한자 private을 사용)

 

 

추상 클래스

구상 메서드 : 구체적인 기능을 가지며 이런 점에 초점을 맞춰 메서드를 표현하는 것

추상 메서드 : 구체적인 처리 내용을 기술하지 않고, 호출하는 방법만을 정의한 메서드

 

// 구상 메서드
public int adder(int n1, n2)
{
	return n1 + n2;
}

// 추상 메서드
abstract public int adder(int n1, n2);

 

추상 클래스 

  • 추상 메서드를 가진 클래스 (하나라도 있으면 abstract 표시)
  • 상속받은 클래스의 기능을 미리 지정하기 위해서 사용
  • '이 클래스를 상속받은 클래스는 반드시 이런 기능이 있어야 함'
  • 앞으로 어떤 클래스가 상속받을지 모르므로 그 기능을 구체적으로 미리 만들어놓을 수 없을 때 사용
abstract class Unit
{
	String name;
    int hp;
    
    abstract void doMove();
}
abstract class Unit
{
    abstract void doMove();
}

class Marine extends Unit
{
    void doMove()
    {
        System.out.println("마린은 두 발로 이동합니다.");
    }
}

class Zergling extends Unit
{
    void doMove()
    {
        System.out.println("저글링은 네 발로 이동합니다.");
    }
}

public class MyStarcraft
{
    public static void main(String[] args)
    {
        Marine unit1 = new Marine();
        unit1.doMove();
        
        Zergling unit2 = new Zergling();
        unit2.doMove();
    }
}

 

인터페이스

  • 상속 관계가 아닌 클래스에 기능을 제공하는 구조
  • 추상 클래스처럼 추상 메서드로 기능을 제공
  • 인터페이스끼리 상속도 가능
  • 인터페이스가 일반 클래스를 상속할 수는 없음
  • 인터페이스에 한해 다중 상속 가능

 

인터페이스 구현

추상 클래스의 구성 & 인터페이스의 구성 비교

// 추상 클래스

abstract class Unit
{
    String name;	// 멤버 변수
    int hp;
    
    void printUnit()	// 구상 메서드
    {
    	System.out.println("이름 : " + name);
        System.out.println("HP : " + hp);
    }
    abstract void doMove();	// 추상 메서드
}
// 인터페이스

interface A
{
	public static final int a = 2;	// 스태틱 상수 정의
    public abstract void say();		// 추상 메서드
    public default void desc()		// 디폴트 메서드
    {
    	System.out.println("기능이 구현된 메서드입니다.");
    }
}

 

interface A
{
	int a = 2;
    void greet();
}

interface B extends A
{
	void bye();
}
interface A extends X, Y, Z
{

}

 

 

인터페이스 구현

  • 클래스에서 인터페이스를 이용하도록 하는 것
  • 인터페이스는 implements 예약어를 이용해서 코드를 작성함
  • 이후는 추상 클래스의 사용 방법과 동일
  • 인터페이스의 메서드는 추상 메서드이므로 implements 예약어를 사용했다면 반드시 추상 메서드를 구상 메서드로 오버라이딩해서 구현해주어야 함
  • 인터페이스는 다중 구현 가능
  • 상속돠 인터페이스를 동시에 사용한다면 상속 → 인터페이스 순으로 표시

 

class B implements A
{
	public void say()
    {
    }
}
class B implements X, Y, Z
{

}
class B extends A implements X, Y, Z
{

}

 

interface Greet
{
    void greet();
}

interface Talk
{
    void talk();
}

class Morning implements Greet, Talk
{
    public void greet()
    {
        System.out.println("안녕하세요!");
    }

    public void talk()
    {
        System.out.println("날씨 좋네요.");
    }
}

public class Ex01_Meet
{
    public static void main(String[] args)
    {
        Morning morning = new Morning();
        morning.greet();
        morning.talk();
    }
}

 

 

인터페이스와 추상 클래스의 차이

인터페이스와 추상 클래스는 둘 다 추상 메서드를 사용한다는 점에서 똑같은 것 같은데 왜 따로 존재하는 것일까?

클래스 간의 상속 관계를 통해 건물이라는 본질을 유지하게 하려면 추상 클래스를 통한 상속 기능 사용

단지 기능만을 구현하려면 인터페이스 사용

 

// 건물의 기본 설계도
abstract class Building
{
    int health;
    abstract void doBuild();
}

// 건물이 날 수 있는 기능 구현. 건물의 일반적인 기능 아님.
interface Fly 
{
    void flyBuilding();
}

// 인간형 유닛을 생산하는 건물. 여차하면 날아서 이동 가능.
class Barracks extends Building implements Fly
{
    void doBuild()
    {
        System.out.println("인간형 유닛 생산 건물을 짓습니다.");
    }
    
    void doMakeMarine()
    {
        System.out.println("총쏘는 유닛을 생산합니다.");
    }

    // 모든 건물이 날면 안되므로 인터페이스로 나는 기능 제공
    public void flyBuilding()
    {
        System.out.println("건물이 날아서 이동하게 합니다.");
    }
}

// 기갑형 유닛을 생산하는 건물. 여차하면 날아서 이동 가능.
class Factory extends Building implements Fly
{
    void doBuild()
    {
        System.out.println("기갑형 유닛 생산 건물을 짓습니다.");
    }
    
    void doMakeTank()
    {
        System.out.println("탱크 유닛을 생산합니다.");
    }

    // 모든 건물이 날면 안되므로 인터페이스로 나는 기능 제공
    public void flyBuilding()
    {
        System.out.println("건물이 날아서 이동하게 합니다.");
    }
}

// 인간형 유닛을 숨겨서 보호하는 건물. 날면 안 됨.
class Bunker extends Building
{
    void doBuild()
    {
        System.out.println("인간형 유닛이 숨을 건물을 짓습니다.");
    }
    
    void doDefense()
    {
        System.out.println("숨은 유닛을 적의 공격으로부터 보호합니다.");
    }
}

public class Ex02_Starcraft 
{
    public static void main(String[] args) 
    {
        Barracks barracks = new Barracks();
        barracks.doBuild();
        barracks.doMakeMarine();
        barracks.flyBuilding();

        Factory factory = new Factory();
        factory.doBuild();
        factory.doMakeTank();
        factory.flyBuilding();

        Bunker bunker = new Bunker();
        bunker.doBuild();
        bunker.doDefense();
    }
}

 

 

디폴트 메서드

 

 

 

 

다형성

하나의 객체와 메서드가 많은 형태를 가지고 있는 것

하위 클래스 객체를 상위 클래스형의 변수에 대입하여 사용 가능

이 경우 실제 객체는 Sub 클래스의 설계도를 이용해 힙에 만들어져 있지만 스택에 만들어진 변수는 Super의 설계도를 이용

그러므로 obj 변수는 설계도상 name은 몰라서 못 쓰겠지만 나머지 price나 getPrice() 메서드 사용 가능

 

하지만 상위 클래스의 객체를 하위 클래스형의 변수로 대입할 수는 없음

실제 객체는 Super 클래스의 설계도를 이용해 힙에 만들어져 있음

그런데 스택에 만들어진 변수가 Sub의 설계도를 이용하게 된다면, name이라는 변수에 접근하려고 할 때 100% 에러 발생

 

'자식 클래스의 객체는 부모 클래스형의 변수에 대입할 수 있다'

abstract class Calc 
{
    int a = 5;
    int b = 6;

    abstract void plus();
}

class MyCalc extends Calc 
{
    void plus()  { System.out.println(a + b); }
    void minus() { System.out.println(a - b); }
}

public class Ex01_Polymorphism1
{
    public static void main(String[] args)
    {
        MyCalc myCalc1 = new MyCalc();
        myCalc1.plus();
        myCalc1.minus();
        
        // 하위클래스 객체를 상위 클래스 객체에 대입
        Calc myCalc2 = new MyCalc();
        myCalc2.plus();
        // 다음 메서드는 설계도에 없다. 사용할 수 없다.
        //myCalc2.minus();
    }
}

 

abstract class Human 
{
    abstract void print();
}

class Man extends Human 
{
    void print() 
    {
        System.out.println("남자 생성");
    }
}

class Woman extends Human 
{
    void print() 
    {
        System.out.println("여자 생성");
    }
}

public class Ex02_Polymorphism2
{
    public static Human humanCreate(int kind) 
    {
        if (kind == 1) {
            //Human m = new Man();
            //return m;
            return new Man();
        } else {
            //Human w = new Woman();
            //return w;
            return new Woman();
        }
    }

    public static void main(String[] args)
    {
        // 생성된 객체가 남자인지 여자인지 중요하지 않고
        // 난 생성된 객체의 프린트 기능만 쓸 것이다.
        // 남자이면 남자애가 가진 기능을 할 것이고,
        // 여자이면 여자애가 가진 기능을 할 것이다.

        Human h1 = humanCreate(1);
        h1.print();
        
        Human h2 = humanCreate(2);
        h2.print();
    }
}

 

 

instanceof 연산자

객체가 지정한 클래스형의 객체인지를 조사하는 연산자

boolean bCheck = obj instanceof MyClass;

지정한 인터페이스를 오브젝트가 구현하고 있는지를 조사할 수도 있음

boolean bCheck = obj instanceof MyInterface;

 

interface Cry
{
    void cry();
}

class Cat implements Cry
{
    public void cry()
    {
        System.out.println("야옹~");
    }
}

class Dog implements Cry
{
    public void cry()
    {
        System.out.println("멍멍!");
    }
}

public class Ex03_instanceof
{
    public static void main(String[] args)
    {
        Cry test1 = new Cat();
//        Cry test1 = new Dog();

        if (test1 instanceof Cat)
        {
            test1.cry();
        }
        else if (test1 instanceof Dog) 
        {
            System.out.println("고양이가 아닙니다.");
        }
    }
}

 

abstract class Animal 
{
    abstract void doMove();
}

class Tiger extends Animal 
{
    void doMove() 
    {
        System.out.println("호랑이는 산을 달립니다.");
    }
}

class Lion extends Animal 
{
    void doMove() 
    {
        System.out.println("사자는 평원을 달립니다.");
    }
}

public class Ex04_Polymorphism3
{
    public static void animalChoose(Animal obj) // (1)
    {
        if (obj instanceof Tiger) {
            Tiger tiger = (Tiger)obj;
            tiger.doMove();
        } else {
            Lion lion = (Lion)obj;
            lion.doMove();
        }
    }

    public static void main(String[] args)
    {
        Tiger tiger = new Tiger();
        animalChoose(tiger);
        
        Lion lion = new Lion();
        animalChoose(lion);
    }
}

 

(1) : 매개변수로 어떤 형태의 객체를 참조하는 변수가 들어올지 알 수 없기 때문에 아예 상위 클래스로 지정, 이러면 하위 클래스를 매개변수로 넘겨도 자동으로 형변환되어 매개변수로 들어오게 됨

(이후 원래의 형태로 다시 형변환하여 사용)