ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 10. 자바 상속 (Inheritance)
    자바(Java) 강의 2019. 4. 6. 14:50

    자바는 객체 지향 프로그래밍(Objected Oriented Programming) 언어이다. 이는 자바가 객체 지향 프로그래밍을 지원하기 위해 특별한 문법을 제공한다는 뜻이다. 이 포스트에서는 객체 지향 프로그래밍을 위한 문법 중 하나인 상속(Inheritance)을 사용하는 법에 대해 알아보도록 하겠다.

    이전 포스트

    목표

    • 프로젝트 구조
    • 상속 (Inheritance)
    • extends
    • 연습하기

    프로젝트 구조

    이 포스트의 내용을 실습하기 위해 아래와 같은 자바 프로젝트 구조를 따랐다.

    JavaTutorial
    ├── JavaTutorial.iml
    └── src
        ├── Main.java
        └── animal
            ├── Animal.java
            ├── Cat.java
            └── Dog.java
    
    2 directories, 5 files

    패키지 - animal

    상속 (Inheritance) 

    상속은 어떤 문제를 해결하기위해 만들어졌을까?

    우리는 이전에 클래스와 오브젝트를 하면서 Cat과 Dog이라는 클래스를 만들었다. 동물 다마고치 게임을 만든다고 생각해보자. Cat 클래스는 아래처럼 만들 수 있었다.

    package animal;
    
    public class Cat {
        public String name;
        public int weight;
        public String gender;
        public String color;
    
        Cat(String catName) {
            name = catName;
        }
    
        public void eat(int portion) {
            String behavior = name + "(이)는 " + portion + " 만큼의 밥을 먹었다. 야옹야옹!";
            System.out.println(behavior);
        }
    }

    또 Dog이라는 클래스는 아래처럼 만들 수 있다.

    package animal;
    
    public class Dog {
        public String name;
        public int weight;
        public String gender;
        public String color;
    
        Dog(String dogName) {
            name = dogName;
        }
    
        public void eat(int portion) {
            String behavior = name + "(이)는 " + portion + " 만큼의 밥을 먹었다. 멍뭉멍뭉!";
            System.out.println(behavior);
        }
    }

    뭔가 비슷하지 않은가? 그렇다 두 클래스는 대부분의 멤버 변수가 똑같다. 왜인가? 개와 고양이 모두 동물에 속하며 동물이 가지는 속성은 개와 고양이가 모두 가지고 있기 때문이다. 만약 원숭이여도 위의 속성은 마찬가지일 것이다. 따라서 우리는 동물이 추가될 때마다 코드를 복사 붙여 넣기 해야 할 것이다. 그러면 같은 코드가 계속해서 반복될 것이다. 예를 들어서 우리는 이 다마고치의 동물에 모종(장모종/단모종)이라는 멤버 변수를 추가한다고 하자. 또 우리는 100개의 동물을 다마고치에서 지원한다고 하자. 이런 경우 우리는 100개의 코드에 각각 fur(모종)을 추가해야 한다. 자바에서는 이런 점을 돕기 위해 '상속' 문법을 제공한다. 

    extends

    package animal;
    
    public class Animal {
        public String name;
        public int weight;
        public String gender;
        public String color;
    
        public void eat(int portion) {
            String behavior = name + "(이)는 " + portion + " 만큼의 밥을 먹었다.";
            System.out.println(behavior);
        }
    }

    위처럼 Animal이라는 클래스에 다른 클래스와 공통이 되는 변수와 메서드를 정의한다.

    package animal;
    
    public class Dog extends Animal{
    
        public Dog(String dogName) {
            name = dogName;
        }
    
    }

    그리고 위처럼 extends <상속할_클래스_이름> 을 클래스 이름 옆에 적어주면 상속할 클래스의 멤버 변수와 메서드를 상속받을 수 있다. 이는 마치 코드를 복사 붙여 넣기 하는 것과 같은 효과를 가진다. 따라서 위처럼 name변수를 선언하지 않았어도, Animal안에 name변수가 있기 때문에 이를 자유롭게 사용할 수 있다.

    import animal.Dog;
    
    public class Main {
        public static void main(String[] args) {
            Dog dog = new Dog("Larry");
            dog.weight = 10;
            dog.gender = "F";
            dog.color = "Black";
            dog.eat(10);
        }
    }

    dog는 이제 Animal형이기도 하고 Dog형이기도 하다. Dog 클래스가 Animal 클래스를 상속받기 때문이다. 이 때 Dog를 Animal의 서브 클래스(자식 클래스), Animal을 Dog의 수퍼 클래스(부모 클래스)라고 한다. Dog가 Animal형이기도 하고 Dog형이기도 하다는 건 무슨 뜻인가?

    import animal.Animal;
    import animal.Dog;
    
    public class Main {
        public static void main(String[] args) {
            Animal dog = new Dog("Larry");
            dog.weight = 10;
            dog.gender = "F";
            dog.color = "Black";
            dog.eat(10);
        }
    }

    위처럼 Animal 형으로 dog 변수를 선언하고 Dog 오브젝트를 생성해 할당 할 수 있다는 뜻이다. 그렇다면 차이점은 무엇일까? 예를 들어서 Dog 클래스에 아래와 같은 메서드를 추가한다고 해보자.

    package animal;
    
    public class Dog extends Animal{
    
        public Dog(String dogName) {
            name = dogName;
        }
    
        public void bark() {
            System.out.println("멍! 멍!");
        }
    }

    이제 메인 메서드로 돌아가 bark라는 메서드를 불러보자. Animal 형으로 선언 했을 때 bark 메서드를 부를 수 있었나? 없었을 것이다. Dog형으로 바꾸면 가능한가?

    import animal.Animal;
    import animal.Dog;
    
    public class Main {
        public static void main(String[] args) {
            Dog dog = new Dog("Larry");
            dog.weight = 10;
            dog.gender = "F";
            dog.color = "Black";
            dog.bark();
        }
    }

    그렇다면 Animal형일 때는 왜 안되고, Dog형 일때는 가능했을까? 

    Dog형으로 선언된 dog는 무슨 의미인가? "dog라는 변수는 Dog 형의 오브젝트를 담을 것이다."라는 뜻이다. 따라서 Dog라는 오브젝트가 가진 public 멤버 변수와 메서드에 접근할 수 있다. Animal형으로 선언된 dog는 무슨 의미인가? dog라는 변수에 Animal 형의 오브젝트를 담을 것이라는 뜻이다. Dog 오브젝트(new Dog())도 Animal 오브젝트이기 때문에 Animal형으로 선언된 변수에 할당할 수 있다. 하지만 Animal형으로 선언된 변수는 이 변수에 들어오는 오브젝트를 Animal형으로 간주한다. 

    이전의 변수와 자료형에서 어떤 변수의 자료형은 그 변수를 어떻게 '해석'할 것인지에 대한 명세라고 했었다. 여기서는 dog에 들어오는 오브젝트를 Dog클래스로 해석할 것인지, Animal클래스로 해석할 것인지 dog변수의 형을 보고 결정한다는 뜻이다. 따라서 Animal dog로 선언된 변수는 Animal형으로 해석하므로 Dog형에 있는 bark라는 메서드를 볼 수 없다.

    연습하기

    1. Cat클래스가 Animal형을 상속받도록 클래스를 리팩토링(Refactoring - 기능은 같지만 코드를 더 효율적이게 수정하는 작업) 해 보아라.

    2. Animal 클래스 변수들의 접근 제어자(Access Modifier)를 변경 해 보고 각 접근 제어자를 사용할 때마다 어떤 차이가 있는지 알아보아라.

    3. Animal을 상속받은 Cat과 Dog클래스와 맨 처음 만든 Cat, Dog 클래스의 차이점이 무엇인가? eat이라는 메서드의 출력 부분에 차이점이 있을 것이다. Animal을 상속받은 Cat이 맨 처음 상속 부분에 나온 것처럼 eat메서드를 콜 하면 "xx(이)는 10만큼 밥을 먹었다. 야옹야옹!"으로 출력하고 dog.eat(10)은 "xx(이)는 10만큼 밥을 먹었다. 멍뭉멍뭉!"이렇게 출력하게 하려면 어떻게 해야 할까? -> 다음 포스트에서 다룰 내용이니 한번 생각해보도록 하자.

    이번 포스트에서는 상속의 기본에 대해 알아보고 클래스를 상속하는 방법에 대해 알아보았다. 클래스를 왜 상속하는가? 바로 코드를 재사용하기 위해서이다. 코드를 재사용하면 뭐가 좋은가? 코드를 덜 짜도 된다. 따라서 유지보수가 쉬워진다. 어떤 코드를 상속해야 하는가에 대해서는 객체 지향 디자인과 밀접한 관련이 있으므로 이후 객체 지향 디자인/프로그래밍을 설명할 때 알아보도록 하겠다. 그때까지 어떤 코드는 상속하면 좋고 어떤 코드는 상속하면 안 될지 스스로 생각해 보길 바란다.

    다음 포스트: 11. 자바 상속(Inheritance) - this와 super

    댓글

f.software engineer @ All Right Reserved