Section1-객체지향 프로그래밍심화(상속성)

솜씨좋은 개발자·2022년 7월 20일
0

Section1

목록 보기
12/18

📖 학습 목표

  • 상속관계
  • 포함과 상속 관계
  • 메서드 오버라이딩
  • super 와 super()
  • Object 클래스

객체지향 4가지 특징(상속성, 캡슐화, 다형성, 추상화)

✍ 상속성

자바에서 상속이란 상위 클래스(기존의 클래스)를 재사용하여 하위 클래스(새로운 클래스)를 작성하는 문법이다.
다시 말해, 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버(필드, 메서드, 이너 클래스)를 하위 클래스에서 그대로 사용하여 상,하위 클래스 간의 멤버를 공유하는 것을 의미한다.
이러한 두 클래스관의 관계를 서로 상속 관계 있다고 하며, 하위 클래스는 상위 클래스가 가진 모든 멤버를 상속(그대로 사용가능)받게 된다.
상속을 이용하려면 하위클래스에서 상위클래스를 확장한다(상속받는다)라는 의미로 extends라는 키워드를 사용한다.
예를 들면, class Car extends Vehicle 이런 식으로 사용한다. Car라는 클래스가 Vehicle이라는 상위 클래스를 확장한다는 의미이므로, Car 클래스가 Vehicle클래스의 멤버는 그대로 사용하면서 Car클래스의 개별적인 멤버를 더 추가하는 방식으로 사용하는 것이다. 따라서 하위 클래스들의 공통적 특성을 상위 클래스의 요소로 넣는다.

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 클래스에 있는 속성과 기능들을 사용할 수 있는 것을 확인할 수 있다. 또한 각각의 클래스의 개별적인 속성과 기능들은 객체 생성 이후 개별적으로 정의해주고 있다.
이처럼 상속을 사용하는 이유는 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있어 코드의 중복을 제거할 수 있기 때문이다.

자바는 C++ 등 다른 객체지향언어와는 달리 상속할 때 단일 상속만 허용한다. 다만 자바에서도 인터페이스(interface)라는 문법 요소를 통해 다중 상속과 비슷한 효과를 낼 수 있는 방법이 존재한다.

✍ 포함관계

포함(composite)은 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.

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 클래스 안에 참조변수를 선언하는 방법으로 코드의 중복을 없애고 포함관계로 재사용하고 있다.
객체지향 프로그래밍에서 상속보다는 포함 관계를 사용하는 경우가 더 많은데, 이는 상속관계보다 포함관계가 결합도 측면에서 더 좋은 코드라고 볼 수 있기 때문이다.

관계 설정 기준
상속관계와 포함관계 중 어떤 것을 선택해야 하는지는
클래스 간의 관계가 ‘~은 ~이다(IS-A)’ 관계인지 ~은 ~을 가지고 있다(HAS-A) 관계인지 문장을 만들어 생각해보는 것이다.

위의 예시로 예를 들어보면, "Employee는 Address이다." 라는 문장은 성립하지 않는 반면, "Employee는 Address를 가지고 있다." 는 어색하지 않은 올바른 문장임을 알 수 있다. 따라서 이 경우에는 상속보다는 포함관계가 적합하다.
반면 Car 클래스와 SportCar라는 클래스가 있다고 할 때, "SportsCars는 Car를 가지고 있다." 라는 문장보다 "SportsCar는 Car이다." 라는 문장이 훨씬 더 자연스럽다. 따라서 이 경우에는 Car를 상위클래스로 하는 상속관계를 맺어주는 것이 더 적합하다고 할 수 있다.

✍ 메서드 오버라이딩

메서드 오버라이딩(Method Overriding)은 상속관계를 전제로 상위 클래스의 메서드와 이 상위 클래스를 상속받은 하위 클래스의 메서드의 이름을 동일하게 작성해 메서드를 재정의 하는 것을 말한다.

class CodeRunner{
    public static void main(String[] args){
    	Os os = new Os();
        Mac mac = new Mac();
        Windows windows = new Windows();
        
        os.run();
        mac.run();
        windows.run();
	}
}
class Os{
    void run(){
        System.out.println("OS를 실행합니다.");
    }
}

class Mac extends Os{
    void run(){
        System.out.println("Mac을 실행합니다.");
    }
}
class Windows extends Os{
    void run(){
        System.out.println("Windows를 실행합니다.");
    }
}
//출력값
OS를 실행합니다.
Mac을 실행합니다.
Windows를 실행합니다.

위 예제코드를 보면 Mac클래스는 Os 클래스를 상속받고 있는데 상위클래스의 run()이라는 메서드를 하위클래스에서 재정의함으로써 프로그램을 실행했을 때 재정의된 메서드가 실행된 것을 알 수 있다. run()메서드를 오버라이딩한다고 표현한다.
오버라이딩에는 3가지 조건이 있다.
1. 메서드의 선언부(메서드 이름, 매개변수, 반환타입)이 상위클래스의 그것과 완전히 일치해야한다.
2. 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
3. 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.

객체지향의 세 번째 특징인 다형성을 미리 맛보자면, 상속받은 두 객체를 OS 타입으로 선언할 수 있는데 참조 타입은 부모타입이지만 오버라이딩을 통해서 하위클래스는 각 클래스에 맞는 방식으로 메서드를 사용할 수 있다.

class CodeRunner{
    public static void main(String[] args){
        Os os = new Os();
        Os mac = new Mac();
        Os windows = new Windows();
    
    	os.run();
        mac.run();
        windows.run();
	}
}
class Os{
    void run(){
        System.out.println("OS를 실행합니다.");
    }
}

class Mac extends Os{
    void run(){
        System.out.println("Mac을 실행합니다.");
    }
}
class Windows extends Os{
    void run(){
        System.out.println("Windows를 실행합니다.");
    }
}
//출력값
OS를 실행합니다.
Mac을 실행합니다.
Windows를 실행합니다.

또한 모든 객체를 상위 클래스 타입 하나로 선언하면 다음과 같이 간편하게 배열로 선언하여 관리할 수 있다는 편리성이 있다.

class CodeRunner{
    public static void main(String[] args){
        Os[] os = new Os[]{ new Os(),new Mac(), new Windows()};
        for(Os os1 : os){
            os1.run();
        }
    }
}

class Os{
    void run(){
        System.out.println("OS를 실행합니다.");
    }
}

class Mac extends Os{
    void run(){
        System.out.println("Mac을 실행합니다.");
    }
}
class Windows extends Os{
    void run(){
        System.out.println("Windows를 실행합니다.");
    }
}

//출력값
OS를 실행합니다.
Mac을 실행합니다.
Windows를 실행합니다.

위의 예시코드처럼 배열로 한번에 관리하면 객체를 하나하나 만들지 않아도 한번에 같은 동작을 수행하게 할 수 있다.

super VS super()

super는 상위 클래스의 객체를 의미하고, super()는 상위 클래스의 생성자를 호출하는 것을 의미한다.

이 또한 상속관계를 전제로 사용할 수 있다.

public class Super {
    public static void main(String[] args) {
        Lower low = new Lower();
        low.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 키워드를 사용하면 부모의 객체의 멤버 값을 참고할 수 있다.

위의 예시에서 첫 번째 count는 자기에게서 가장 가까운 count 변수, 즉 15를 가리킨다.

두 번째 카운트 또한 자신이 호출된 객체의 인스턴스 변수를 가리키기 때문에 15를 출력한다.

마지막으로 super 키워드를 사용하여 호출한 count는 앞서 설명드렸던 것처럼 상위 클래스의 변수를 참조하여 숫자 20을 출력한다.

this()와의 차이는 this()가 같은 클래스의 다른 생성자를 호출하는데 사용하는 반해, 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 클래스를 생성하고, Student 생성자를 통해 상위 클래스 Human 클래스의 생성자를 호출하고 있다.

super() 메서드 또한 this()와 마찬가지로 생성자 안에서만 사용가능하고, 반드시 첫 줄에 와야 한다.

예제에서 super() 메서드는 Student 클래스 내부에서 호출되고 있고 상위 클래스 Human의 생성자를 호출하고 있다. 그리고 출력값으로 “휴먼 클래스 생성자”와 "학생 클래스 생성자"가 순차적으로 출력된다.

여기서 기억해야하는 가장 중요한 사실은 모든 생성자의 첫 줄에는 반드시 this() 또는 super()가 선언되어야 한다는 것이다.

만약 super()가 없는 경우에는 컴파일러가 생성자의 첫 줄에 자동으로 super()를 삽입한다.

이때 상위클래스에 기본생성자가 없으면 에러가 발생하게 된다.

✍ Object 클래스

Object 클래스는 자바의 클래스 상속계층도에서 최상위에 위치한 상위클래스이다.

toString() : String 반환 ->객체정보를 문자열로 출력

equals() : boolean 반환 ->스택메모리값 비교

wait() : void 반환 -> 현재 쓰레드 일시정지

notify() : void 반환 -> 일시정지 중인 쓰레드 재동작

profile
개발의 방으로

0개의 댓글

관련 채용 정보