본문 바로가기
Java/객체지향 프로그래밍 심화

1. 상속(Inheritance)

by mozzi329 2022. 7. 11.
728x90
 

 

     

     

    객체지향 프로그래밍의 4개의 기둥

    📌 Goal

    • 상위 클래스-하위 클래스의 상속 관계의 핵심을 이해하고, 그 장점을 설명할 수 있다.
    • extends 키워드를 사용하여 두 개 이상의 클래스 간 상속 관계를 정의할 수 있다.
    • 포함관계와 상속관계의 차이를 설명할 수 있다.
    • 상속 관계에서 사용할 수 있는 메서드 오버라이딩의 정의, 성립 조건, 장점을 이해하고 이를 활용할 수 있다.
    • super 와 super() 의 차이를 설명할 수 있다.
    • Object 클래스가 자바 클래스의 상속계층도에서 최상단에 위치한다는 사실을 이해할 수 있다.

     

    📌 상속(Inheritance)

    기존 클래스를 재사용하여 새로운 클래스를 작성하는 자바의 문법 요소

    기존 클래스와 새로운 클래스를 단순한 형태로 생각해보면, 두 클래스를 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버(필드, 메서드, 이너클래스)를 하위 클래스와 공유하는 것을 의미한다. 여기서 이 두 클래스를 서로 상속 관계가 있다고 하며, 하위 클래스는 상위 클래스가 가진 모든 멤버를 상속받는다. 그래서 하위 클래스의 멤버 개수는 언제나 상의 클래스의 멤버보다 많거나 같다.

     

    표현으로 생각했을 때 “~클래스로부터 상속받았다”라는 표현보다는 “~클래스로부터 확장되었다”는 표현이 그 역할과 기능을 생각했을 때 더 적절한 표현이다.

    부모 클래스와 자식 클래스

    위의 그림에는 총 4개의 클래스가 정의되어 있다. 각각은 부모클래스로부터 name, age, learn(), walk(), eat()이라는 멤버를 상속받는다. 이처럼 상속을 받아 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있어 코드의 중복을 제거할 수 있다.

     

    더 나아가, 상속은 다형적 표현이 가능하다는 장점이 있다.

    예시로 예를 들면, ‘프로그래머는 프로그래머이다'라는 문장은 참이다. 그와 동시에 ‘프로그래머는 사람이다' 또한 참이다. 즉 하나의 객체가 여러 모양으로 표현될 수 있다는 것을 다형성이라고 한다.

     

    사진을 실제 코드로 구현하자면 다음과 같다.

    class Person {
        String name;
        int age;
    
        void learn(){
            System.out.println("공부를 합니다.");
        };
        void walk(){
            System.out.println("걷습니다.");
        };
        void eat(){
            System.out.println("밥을 먹습니다.");
        };
    }
    
    class Programmer extends Person { // Person 클래스로부터 상속. extends 키워드 사용
        String companyName;
    
        void coding(){
            System.out.println("코딩을 합니다.");
        };
    }
    
    class Dancer extends Person { // Person 클래스로부터 상속
        String groupName;
    
        void dancing(){
    		    System.out.println("춤을 춥니다.");
    		};
    }
    
    class Singer extends Person { // Person 클래스로부터 상속
        String bandName;
    
        void singing(){
    		    System.out.println("노래합니다.");
    		};
        void playGuitar(){
    		    System.out.println("기타를 칩니다.");
    		};
    }
    
    public class HelloJava {
        public static void main(String[] args){
    
            //Person 객체 생성
            Person p = new Person();
            p.name = "김코딩";
            p.age = 24;
            p.learn();
            p.eat();
            p.walk();
            System.out.println(p.name);
    
            //Programmer 객체 생성
            Programmer pg = new Programmer();
            pg.name = "박해커";
            pg.age = 26;
            pg.learn(); // Persons 클래스에서 상속받아 사용 가능
            pg.coding(); // Programmer의 개별 기능
            System.out.println(pg.name);
    
        }
    }
    
    //출력값
    공부를 합니다.
    밥을 먹습니다.
    걷습니다.
    김코딩
    공부를 합니다.
    코딩을 합니다.
    박해커

    보이는 것처럼 Person클래스로부터 Programmer, Dancer, Singer 클래스가 확장되어 Person클래스에 있는 속성과 기능들을 사용할 수 있는 것을 확인할 수 있다. 또한 각각의 클래스의 개별적인 속성과 기능들은 객체 생성 이후 개별적으로 정의해주고 있다. 

     

    추가적으로 언급할 내용은 자바의 객체지향 프로그래밍에서는 단일 상속(Single Inheritance)만을 허용한다는 점이다. 이는 인터페이스(interface)라는 문법 요소를 통해 다중 상속과 비슷한 효과를 낼수 있는 방법이 존재한다.

     

    📌 포함 관계

    상속처럼 클래스를 재사용할 수 있는 방법으로 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미

     

    아래 예제를 살펴보자.

    public class Employee {
        int id;
        String name;
        Address address;
    
        public Employee(int id, String name, Address address) {
            this.id = id;
            this.name = name;
            this.address = address;
        }
    
        void showInfo() {
            System.out.println(id + " " + name);
            System.out.println(address.city+ " " + address.country);
        }
    
        public static void main(String[] args) {
            Address address1 = new Address("서울", "한국");
            Address address2 = new Address("도쿄", "일본");
    
            Employee e = new Employee(1, "김코딩", address1);
            Employee e2 = new Employee(2, "박해커", address2);
    
            e.showInfo();
            e2.showInfo();
        }
    }
    
    class Address {
        String city, country;
    
        public Address(String city, String country) {
            this.city = city;
            this.country = country;
        }
    }
    
    // 출력값
    1 김코딩
    서울 한국
    2 박해커
    도쿄 일본

    한 회사의 근로자(Employee)를 표현하기 위한 Employee 클래스의 멤버 변수로 근로자가 사는 개략적인 주소를 나타내는 Address 클래스가 정의되어있다. 원래라면 Address 클래스에 포함되어 있는 인스턴스 변수 city와 country를 각각 Employee클래스의 변수로 정의해주어야 하지만, Address 클래스로 해당 변수들을 묶어준다음 Employee 클래스 안에 참조변수를 선언하는 방법으로 코드의 중복을 없애고 포함관계로 재사용하고 있다.

     

    📌 메서드 오버라이딩

    상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것을 의미

     

    아래의 예제를 살펴보자.

    class Vehicle {
        void run() {
            System.out.println("Vehicle is running");
        }
    }
    
    public class Bike extends Vehicle { // Vehicle 클래스 상속
    	@Override // 가독성을 위해 Override 어노테이션을 붙여주자.
        void run() {
            System.out.println("Bike is running"); // 메서드 오버라이딩
        }
    
        public static void main(String[] args) {
            Bike bike = new Bike();
            bike.run();
        }
    }
    
    // 출력값
    "Bike is running"

    메서드 오버라이딩을 통해 Bike 메서드가 Vehicle 클래스로부터 상속받은 run() 메서드를 자신에 맞게 변경할 수 있다. 메서드 오버라이딩을 사용하면 하나의 run() 메서드의 이름으로 서로 다른 객체마다 다른 실행 결과 값을 반환할 수 있다.

     

    ✔️ 메서드 오버라이딩을 사용하기 위한 3가지 조건

    • 메서드 선언부(메서드 이름, 매개변수, 반환타입)가 상위클래스의 그것과 완전히 일치해야한다.
    • 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
    • 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.

     

    📌 super 키워드 vs super()

    ✔️ super 키워드

    상의 클래스의 객체를 호출한다. 상위 클래스의 존재를 상정하며 상속 관계를 전제로 한다.

     

    아래 예제를 살펴보자.

    public class Super {
        public static void main(String[] args) {
            Lower l = new Lower();
            l.callNum();
        }
    }
    
    class Upper {
        int count = 20; // super.count
    }
    
    class Lower extends Upper {
        int count = 15; // this.count
    
        void callNum() {
            System.out.println("count = " + count);
            System.out.println("this.count = " + this.count);
            System.out.println("super.count = " + super.count);
        }
    }
    
    // 출력값
    count = 15
    count = 15
    count = 20

    위에 예제에서 Lower클래스는 Upper클래스로부터 변수 count를 상속받는데, 공교롭게도 자신의 인스턴스 변수 count와 이름이 같아 둘을 구분할 방법이 필요하다. 두 개의 같은 이름의 변수를 구분하기 위한 방법이 바로 super 키워드이다. 만약 super키워드를 붙이지 않는다면, 자바 컴파일러는 해당 객체는 자신이 속한 인스턴스 객체의 멤버를 먼저 참조한다.

     

    반면 경우에 따라서 상위 클래스의 변수를 참조해야할 경우가 종종 있는데 그 경우 super 키워드를 사용하면 부모의 객체의 멤버 값을 참고할 수 있다. 즉, 상위 클래스의 멤버와 자신의 멤버를 구별하는 데 사용된다는 점을 제외한다면 this 키워드와 super 키워드는 기본적으로 같은 것이라 말할 수 있다.

     

    ✔️ super()

    상위 클래스의 생성자를 호출한다. 상위 클래스의 존재를 상정하며 상속 관계를 전제로 한다.

     

    아래 예제를 살펴보자.

    public class Test {
        public static void main(String[] args) {
            Student s = new Student();
        }
    }
    
    class Human {
        Human() {
            System.out.println("휴먼 클래스 생성자");
        }
    }
    
    class Student extends Human { // Human 클래스로부터 상속
        Student() {
            super(); // Human 클래스의 생성자 호출
            System.out.println("학생 클래스 생성자");
        }
    }
    
    // 출력값
    휴먼 클래스 생성자
    학생 클래스 생성자

    위에 코드에서 Human클래스를 확장하여 Student클래스를 생성했고, super() 메서드를 통해 상위 클래스 Human클래스의 생성자를 호출하고 있다. super()메서드 또한 this()와 마찬가지로 생성자 안에서만 사용가능하고, 반드시 첫 줄에 와야한다.

     

    만약 super()가 없는 경우에는 컴파일러가 생성자의 첫 줄에 자동으로 super()를 삽입한다. 이때 상위클래스에 기본생성자가 없으면 에러가 발생하게 된다.

     

    📌 클래스의 정점, Object 클래스

    자바 클래스 상속 계층도에서 가장 최상위에 위치한 클래스
    ※ 자바의 모든 클래스는 Object 클래스로부터 확장된다.

    실제로 자바 컴파일러는 컴파일링의 과정에서 다른 클래스로부터 아무런 상속을 받지 않는 클래스에 자동적으로 extends Object를 추가하여 Object 클래스를 상속받도록 한다.

    class ParentEx {  //  컴파일러가 "extends Object" 자동 추가
    
    }
    
    class ChildEx extends ParentEx {
    
    }

    위의 예시에서, ParentEx 클래스를 상속받아 ChildEx 클래스를 만들었을 때 상위클래스 ParentEx는 아무것도 상속하고 있지 않기에 컴파일러는 extends Object를 삽입한다. 앞서 설명한 것처럼 Object 클래스는 자바 클래스의 상속계층도에 가장 위에 위치하기 때문에 Object 클래스의 멤버들을 자동으로 상속받아 사용할 수 있다.

     

    ✔️ Object 클래스의 대표적인 메서드

    메서드명 변환타입 주요 내용
    toString() String 객체 정보를 문자열로 출력
    equals(Object obj) boolean 등가 비교 연산(==)과 동일하게 스택 메모리 값을 비교
    hashCode() int 객체의 위치정보 관련. Hashtable 또는 HashMap에서 동일 객체여부 판단
    wait() void 현재 쓰레드 일시정지
    notify() void 일시정지 중인 쓰레드 재동작

     

    📌 심화학습

    댓글