발걸음/일지

[JAVA] 객체지향 프로그래밍 심화(feat. 상속)

리꾸므 2022. 11. 6. 21:16

학습 목표

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

 

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

  • 상위 클래스의 멤버(필드, 메서드, 이너클래스)를 하위 클래스와 공유하는 것을 의미. 이 두 클래스를 서로 상속관계 있다고하며, 하위클래스는 상위 클래스가 가진 모든 멤버를 상속받는다
  • 하위 클래스 멤버 개수는 언제나 상위 클래스와 비교했을때 같거나 많다.
  • "~클래스로부터 상속받았다"보다 "~클래스로부터 확장되었다"라는 표현이 역할과 기능을 생각했을때 더 적절하다.
  • 클래스 상속시 extends 키워드를 사용, 클래스명 다음에 extends 상위클래스명을 사용하여 정의
  • 자바의 객체지향 프로그래밍에서는 단일 상속(single inheritance)만을 허용
class Person {
    String name;
    int age;

    void learn(){
        System.out.println("공부를 합니다.");
    };
    void walk(){
        System.out.println("걷습니다.");
    };
    void eat(){
        System.out.println("밥을 먹습니다.");
    };
}

class teacher extends Person { // Person 클래스로부터 상속. extends 키워드 사용 
    String schoolName;

    void teach(){
        System.out.println("공부를 가르칩니다.");
    };
}

class player extends Person { // Person 클래스로부터 상속
    String teamName;

    void playTennis(){
		    System.out.println("테니스를 칩니다.");
		};
}

class Singer extends Person { // Person 클래스로부터 상속
    String bandName;

    void singing(){
		    System.out.println("노래합니다.");
		};
}

public class HelloJava {
    public static void main(String[] args){

        //Person 객체 생성
        Person p = new Person();
        p.name = "수바기";
        p.age = 28;
        p.learn();
        p.eat();
        p.walk();
        System.out.println(p.name);

        //Programmer 객체 생성
        Teacher pt = new Teacher();
        pg.name = "박교사";
        pg.age = 26;
        pg.learn(); // Persons 클래스에서 상속받아 사용 가능
        pg.teach(); // Programmer의 개별 기능
        System.out.println(pg.name);

    }
}

//출력값
공부를 합니다.
밥을 먹습니다.
걷습니다.
수바기
공부를 합니다.
공부를 가르칩니다.
박교사

사람클래스가 상위클래스이며 교사, 가수, 선수 클래스가 상위클래스로부터 특정 속성과 기능을 내려받는 하위클래스가 된다.

왜 상속을 사용할까?

  • 코드를 재사용하여 보다 적은양의 코드로 새로운 코드를 작성, 코드의 중복을 제거할 수 있다.
  • 다형적 표현이가능하다.(객체지향적 설계수행하는데 있어 매우 중요한 개념)
    • ex: "프로그래머는 프로그래머이다 = 참, 프로그래머는 사람이다 = 참" 객체가 여러모양으로 표현되는 것 다형성

 


 

포함 관계

  • 포함(composite)은 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.
  • 클래스 간의 관계가 '~은 ~이다(IS-A)'관계인지 '~은 ~을 가지고 있다(HAS-A) 관계인지 문장을 만들어보면 상속관계를 맺어줄 것인지 포함관계를 맺어줄것인지 판별 할 수 있다.
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 클래스에 인스턴스 변수 city, country를 각각 정의해주야 하지만  Address 클래스로 해당 변수를 묶은 다음 Employee 클래스 안에 참조변수 선언하는 방법으로 코드 중복을 없애고 포함관계를 사용하고 있다.
  • 'Employee는 Address이다.'라는 문장은 성립하지 않는 반면, 'Employee는 Address를 가지고 있다.'는 올바른 문장으로 성립된다. 이 경우에는 상속보다 포함관계가 적합하다.
  • Car 클래스와 SportCar 클래스가 있을 때 'SportsCars는 Car를 가지고 있다.'라는 문장보다 'SportsCar는 Car이다.'라는 문장이 훨씬 자연스럽다. 이 경우 포함관계보다 상속이 적합하다.

 


 

메서드 오버라이딩 : 상위클래스부터 상속받은 메서드와 동일한 이름의 메서드를 재정의

class Vehicle {
    void run() {
        System.out.println("Vehicle is running");
    }
}

public class Bike extends Vehicle { // Vehicle 클래스 상속
    void run() {
        System.out.println("Bike is running"); // 메서드 오버라이딩
    }

    public static void main(String[] args) {
        Bike bike = new Bike();
        bike.run();
    }
}

// 출력값
"Bike is running"

메서드 오버라이딩 예시코드

  •  메서드 오러라이딩 사용시 세 가지 조건이 필요하다.
    1. 메서드의 선언부(이름, 매개변수, 반환타입)이 상위클래스의 그것과 완전히 일치해야한다.
    2. 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넒어야 한다.
    3. 예외는 상위클래스의 메서드보다 많이 선언할 수 없다.

 


 

super 키워드와 super()

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

super 키워드 예시코드

  • 공통적으로 모두 상위 클래스의 존재를 상정하며 상속관계를 전제한다.
  • Lower 클래스는 자신의 인스턴스 변수 count와 이름이 동일한 변수 count를 상속, 두 개의 같은 이름의 변수를 구분하기 위한 방법이 바로  super 키워드이다.
  • super 키워드 없을시 객체는 자신이 속 한 인스턴스 객체 멤버를 먼저 참조한다.
  • super키워드 사용하면 부모의 객체의 멤버 값을 참고할 수 있다.
  • 상위 클래스 멤버와 자신의 멤버를 구별하는데 사용된다는 점을 제외하면 this와 uper는 기본적으로 같다

 

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("학생 클래스 생성자");
    }
}

// 출력값
휴먼 클래스 생성자
학생 클래스 생성자

 

  • super()메서드 또한 this()와 마찬가지로 생성자 안에서만 사용가능하며, 반드시 첫 줄에 와야 한다.
  • 모든 생성자의 첫 줄에는 반드시 this() 또는 super()가 선언되어야 한다
  • super()가 없을 시 컴파일러가 생성자 첫 줄에 자동삽입한다. 이때 상위클래스에 기본생성자가 없으면 에러가 발생
  • 클래스를 만들 때는 자동으로 기본 생성자를 생성하는 것이 필요

 


Object 클래스 : 최상위에 위치한 상위클래스

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

}

class ChildEx extends ParentEx {

}

ParentEX는 아무것도 상속하고있지 않기에 컴파일러는 extends Object를 삽입한다.

  • 모든 클래스는 Object클래스로부터 확장된다
  • Object클래스는 상속계층도 가장 위에 위치하기에 Object클래스의 멤버들을 자동상속받아 사용 가능하다.
메서드명 반환 타입 주요 내용
toString() String 객체 정보를 문자열로 출력
equals(Object obj) boolean 등가 비교 연산(==)과 동일하게 스택 메모리값을 비교
hashCode() int 객체 위치정보 관련, Hashtable 또는 HashMap에서 동일 객체 여부 판단
wait() void 현재 쓰레드 일시정지
notify() void 일시정지 중인 쓰레드 재동작