티스토리 뷰

!!! CAUTION !!!

이 글은 2일 넘게 약을 빨고, 만든 개념 부수기 콘텐츠입니다. 

오지게 긴 포스팅이니, 상당한 집중력을 요구할 수 있습니다.

 

 


이 콘텐츠는 아래와 같은 내용을 담고 있습니다.

  • 상속의 정의와 사용 목적
  • 특징
  • 상속에서 많이 쓰는 키워드
  • 오버 라이딩과 오버 로딩
  • 업 캐스팅과 다운 캐스팅

이 콘텐츠는 아래와 같은 목적을 갖고 있습니다.

 

  • 위와 같은 내용을 완벽하게 학습한다.
  • 크롬 창 여러 개 켜서 공부하지 않기 위해 한번에 때려 박아 이해시킨다.

 

 

상속이란?

 우리가 통상적으로 알고 있는 상속의 성질과 유사하다.

부모 클래스의 모든 자원과 메서드를 물려받아 자식 클래스에서 사용할 수 있게 하는 것이다.

문장을 다듬어 보면, "상위 클래스의 자원과 기능을 추가하거나 재정의 하여 새로운 클래스를 정의하는 것" 정도 되겠다.

 

상속 비유를 빙자한 참혹한 예제

 

 

그럼 왜 쓰는데?

 모든 요소가 필요에 의해 생겨나듯, 상속 역시 필요로 인해 생겼다.

예를 들어보자, 당신은 동물을 종류별로 정리하기 위해서 사자, 호랑이, 강아지, 고양이 4가지 클래스를 만들었다. 그러고 생각해보니, 이 친구들은 모두 움직일 수 있다. 그래서 움직이는 것을 표현하기 위해 기능을 하나씩 심어주었다. 그리고 나서 참 뿌듯해했다. 이것을 본 당신의 상관이 동물의 종류가 적다고 96개 동물을 더 추가하라고 요구했다.  당신은 짜증이 날 것이다. 동물마다 다른 기능을 넣는 것도 일인데, 같은 기능(move(), eat() 같은 공통적인 부분 말이다.)을 96번이나 복사해서 넣어야 하기 때문이다.

상속이 없다면, 같은 행동도 하나하나 복사해서 넣어야 한다.

 

 만약, 저기에 메서드가 한 개가 아니라, 100개의 메서드라면? 또한 기능 중 하나인 move() 메서드에서 "움직인다"를 "움직입니다"으로 바꿔달라는 요구사항이 나온다면? 당신은 이것을 하나하나 추가하고, 하나하나 고쳐야 할 것이다.  개체가 추가될 때에도 문제가 될뿐더러, 유지 보수는 생각만 해도 끔찍한 상황이 될 것이다. 이러한 점을 쉽게 해결할 수 있는 것이 상속의 개념이다. (이러한 불편함에서 상속이 나오지 않았을까?)

 

유지보수하기가 너무나도 힘들다.

 상속을 이용하면 상위 클래스가 가지고 있는 자원과 기능을 다시 쓰지 않아도, 마치 쓴 것처럼 그대로 이용할 수 있기 때문이다. 아까의 예제를 다시 돌아가 보자.

 

 당신은 자신이 만든 모든 동물 클래스들이 공통적으로 move(), eat() 등의 메서드가 공통적으로 사용함을 인지했다. 그래서 동물이라는 클래스를 만들고, 모든 동물들이 동물 클래스를 상속하게 만들었다. 이제 동물을 만들 때마다, 동물 클래스에 있는 메서드를 복사하는 귀찮은 짓을 하지 않아도 된다. 옆자리 동료가 60번째 동물의 move()와 eat()을 복사하고 있을 때, 당신은 그걸 보면서 커피 한잔 뽑아 옥상으로 담배 피우러 올라갈 수 있다.

더 이상 동물을 추가할 때 마다 move 메서드를 추가할 필요가 없다.

 

그럼 쓰면 뭐가 좋은데? 

 상속을 사용하면, 상속하는 클래스가 만들어 놓은 기능들을 굳이 다시 사용할 필요가 없다. 똑같은 메서드를 복사할 필요가 없으므로, 중복이 줄어든다. 또한 만들어 놓은 기능을 계속 사용하기 때문에 재사용성이 좋아진다. 그밖에도 통일성, 유지보수성, 다형성의 장점을 가지고 있다. 통일성, 유지보수성, 다형성에 대한 설명은 접어두겠다. 참고하자.

더보기

통일성 - 상속하는 클래스가 메서드가 바뀌면, 상속을 받는 모든 클래스는 해당 메서드가 모두 바뀐다. 위에 있는 클래스를 그대로 사용하기 때문에. 

 

유지보수성 - 통일성이 있기 때문에 전체가 다 바뀌어야 한다면, 상속하는 클래스 하나로 전부를 수정할 수 있다.

 

다형성 - 상황에 따라 적당한 구현으로 사용할 수 있는 성질이다. 이렇게만 알고 밑의 포스팅을 조금 더 보자.

 그럼 어떻게 쓰는데? 

 간단하다. extends 키워드와 상속할 클래스 이름만 있으면 된다.

Class [클래스명] extends [상속하는 클래스명]

상속 사용법

더 상세하게 알려줘 어떻게 쓰는 건지!

 일단, 용어부터 제대로 정리하고 가자. 이제부터 상속하는 클래스와 상속받는 클래스의 명칭부터 정의하고 가겠다. 상속에서 클래스는 두 가지가 있다. 상속을 하는 녀석과 받는 녀석 이렇게 딱 둘 뿐이다. 앞으로는 이 둘을 이렇게 부를 것이다.

 

  • 부모 클래스: 상속을 하는 클래스이다. 상위 클래스, 슈퍼클래스(super class)라고 부른다.
  • 자식 클래스: 상속을 받는 클래스이다. 하위 클래스, 파생 클래스라고 한다.  

상속에서 이 두 종류의 클래스는 다음과 같은 특징이 있다.

 

  • 부모 클래스 : 여러 개의 자식 클래스를 가질 수 있다.
  • 자식 클래스 : 단 하나의 부모 클래스만 가질 수 있다.

여기서 의문이 들 수도 있다. 

 

"아니, 상속 많이 하면 내가 메서드 안 써도 되는데 개꿀 아니야? 부모 클래스 여러 개를 왜 못쓰게 해?"

 

생각해보고, 답을 모르겠다면 더보기를 펼쳐보자.

 

더보기

자바에서는 아예 다중 상속을 지원하지 않는다. 그 예를 들어보겠다.

(다중 상속을 지원하는 C++에서도 사용할 때, 이 점은 반드시 염두에 두자.) 

 

다이아몬드 문제

 A를 상속받은 B와 C는 서로 걷기라는 같은 이름의 메서드를 만들었다. 그런데 안에 내용물이 다르다. D가 다중 상속을 받는 다면, D는 혼란스럽다. 무엇을 상속받아야 하는지 선택할 수 없기 때문이다. 이것이 다중 상속을 했을 때, 대표적으로 문제가 되는 '다이아몬드 문제'이다. 

 파생 클래스가 상위 클래스의 두메 서드 중 어느 것을 선택해야 할지 모르기 때문에 충돌이 발생한다.

이러한 모호성 때문에 자바에서는 애초에 다중 상속을 지원하지 않는다. 

 

 

"아 그런데, 꼭 다 상속해줘야 되는 거야? 난 이런 기능은 상속해서 주기 싫은데, 상속받지 못하게 할 수 있나?"

 

 상속은 접근 제한자(Acceess modifier)를 이용하여 상속의 범위를 조정할 수 있다. 접근 제한자의 기본 제약 조건을 그대로 따른다.

 

접근 제한자 키워드의 범위

이해가 잘 안 간다면, 더보기란에 예제를 추가하도록 하겠다. 참고하자. 

더보기
부모 클래스(왼쪽) 와 자식 클래스(오른쪽) 

 

 부모 클래스에는 public , protected, private의 3가지 변수와 private 매더스가 있다.

protected 변수인 gender는 하위 클래스에서 사용이 가능하지만, private으로 제한된 tobacoo와 smoke 메서드는 사용할 수 없다. 이처럼 접근 제한자를 통해 파생 클래스에서 사용을 제한할 수 있다.

 

키워드

 이외에도 몇 가지 용어들을 자주 보게 될 것이다. 정확히는 상속을 배우면서 자주보게 될 키워드들이다. 이 이야기를 하려고 접근 제한자에 대해서도 올려둔 것이다. 일단, 알아두고 예제를 보면서 이해하자.

 

  • super  - 상위 클래스의 필드나 자원을 사용하기 위한 참조 변수 키워드
  • super() - 상위 클래스의 생성자를 호출 키워드
  • this - 자기 자신의 필드나 자원을 사용하기 위한 참조 변수 키워드
  • this() - 자신의 생성자를 호출 키워드
  • final - 변수를 상수화 시키기 위한 키워드

이렇게 알아두고, 이해에 욕심이 난다면 더보기를 참조하자.

더보기

"아니, 괄호 있는 거랑 없는 거랑 뭐가 다른 거야? 헷갈려!!"

super, super(), this, this() 이 4가지를 헷갈리지 않게 이해하기 위해서 예제와 함께 설명하도록 하겠다. 저 4가지는 목적에 따라 분류될 수 있다. '무엇을 부르기 위해 사용하는 것이냐'에 따라 두 가지로 나뉠 수 있다.

 

1. 생성자를 부르기 위해 사용하는 키워드 - this(), super()

this()

스스로의 생성자를 부를 때, 사용한다. 같은 클래스에 있는 생성자 중 해당하는 생성자를 오버 로딩하여 사용한다. 

this를 사용하여, 디폴트값을 줄 수 있다.

 위 예제에서는 파라미터가 있는 생성자와 없는 생성자가 존재한다. 파라미터가 없는 생성자가 호출되어도, this()를 이용하여, 파라미터가 있는 생성자로 호출할 수 있다.

super()

 상위 클래스의 생성자는 하위 클래스에서 생성할 수도 직접 호출 할 수도 없다. 그렇기 때문에 super() 키워드를 통해 접근해야 한다. super()는 상위 클래스 생성자에 접근하기 위해 사용한다.

 

super() 없이는 상위 클래스 생성자를 사용할 수 없다.

상위 클래스가 디폴트 생성자가 없는 경우에는 반드시 super()를 통해 파라미터를 전달해줘야 한다.

 

super()를 이용하여 생성자 초기화

 

2. 생성자 이외의 것을 부르기 위해 사용하는 키워드

this

자기 필드 안의 자원을 호출하는 참조 변수이다. 변수, 메서드 모두 호출 가능하다.

 

super

상위 필드 안의 자원을 호출하는 참조변수이다. 변수, 메서드 모두 호출 가능하다. 접근 제한자의 영향을 받는다.

 

 

 

"그럼 final은 왜 만든 거야? const 키워드 있잖아!!"

final과 const 둘 다 값을 고정시키는 의미를 지니고 있지만, 사용법이 약간 다르다.

 

const - 선언 시 초기화하여 값이 상 수화된다. 즉, 컴파일 시 이미 고정된 값이다.

final - final의 정확한 정의는 '상수'가 아니라 '한번만 초기화 가능하다'이다.

때문에 런타임 중에 값을 고정시킬 수 있고, 인스턴스마다 다른 값으로도 고정시키는 것이 가능하다.

클래스에 들어갈 땐, 더 이상 상속을 할 수 없고, 메서드에 들어갈 땐, 오버 라이딩할 수 없다.

이 부분에 대해서는 Uno님의 블로그에서 정리를 참 잘해주셨다. 따로 포스팅 전까지는 해당 블로그의 포스팅이 더 이해를 돕는 게 빠르다고 생각된다.

 

 

왜 자바에서 final 멤버 변수는 관례적으로 static을 붙일까?

자바 final, static 키워드와 코딩 best practice 되짚어보기

djkeh.github.io

 

 

오버라이드(Override)와 오버 로딩(Overloading)

 학생 때, 난 이 둘이 항상 헷갈렸다. 뜻을 모르고 그냥 외웠기 때문에 더 했갈렸다고 생각한다. 지금부터 둘의 차이를 확실하게 이해하고 기억하고 가자 

 

 Override

 오버라이드는 '우선하다.'라는 사전적 의미를 지니고 있다. 이 녀석은 상속관계에서 나타나는 특징이다. 상속 관계에 있는 클래스에서 부모 클래스의 메서드를 재정의하는 것을 '오버 라이딩'이라고 한다. 자식 클래스에서 재정의 후 메서드를 호출하면, 자식 클래스의 오버 라이딩된 메서드가 '우선' 되어 호출된다. 

 

오버라이딩된 메소드

 

정리 : 상속관계에서 부모 클래스의 메서드를 재정의 하는 것. 

 

 

 Overloading

 오버 로딩은 '과적(많이 적재함)하다.' 라는 사전적 의미를 가지고 있다. 사실, 이 녀석은 상속과 1도 상관이없다. 그런데 왜 상속얘기 할 때마다 나오냐고 ? 이름 비슷하고, 둘 다 메서드와 상관관계가 있어서, 역활과 명칭이 헷갈리기 때문이지 않을까 하고 생각한다. 오버로딩은 한 클래스 내에서 이름은 같으나 여러 기능을 가진 것을 의미한다.  같은 이름을 가졌으나, 파라미터에 따라 다르게 동작하는 메서드들이 정확히 오버 로딩이다. 같은 이름에 기능을 '과적'시킨 것으로 보는 것이다.

생성자 오버로딩

정리 : 상속과 관계없이, 같은 이름을 가졌으나, 인수(파라미터)를 달리하여 기능을 다양하게 만드는 것.

 

 

이렇게 기능을 추가/상속을 받으며, 재정의 하면서 다양한 형태로 객체를 사용/ 관리하는 것을 다형성이라고 한다.

아까 언급한 다형성이 이러한 특성을 의미한다.

 

 그 외 상속과 관련된 특징

 IS-A

 상속은 확장성으로 인해 포함 관계를 가진다. 자식 클래스는 부모의 모든 자원을 상속받은 것 + 자신의 기능을 추가한 것이므로, 자식 클래스는 부모 클래스를 포함한다고 할 수 있다.

 

쉽게 예를 들자면, 동물 클래스를 상속받은 고양이는 동물의 특징을 모두 포함하고 있다.  '동물은 고양이다'라고 말할 수는 없다. 반대로 고양이의 특징을 모든 동물이 가지고 있지 않기 때문이다.  '고양이는 동물이다'라고 말할 수 있다.  이러한 

 

 

업 캐스팅과 다운 캐스팅

 

업 캐스팅(UPCASTING)

자식 클래스가 부모 클래스로 타입캐스팅되는 것을 업캐스팅이라고 한다. 이렇게만 말하면 100% 나중에 헷갈리니 아래와 같이 형태를 인지하자.

업캐스팅 예시

 

 

[Super Class] [Variable Name] = [Child Class] 

 

 위와 같이 자식 클래스 인스턴스가 슈퍼 클래스 자료형으로 캐스팅 되는 형태이다. 이것이 되는 이유는 '고양이는 동물이다'와 같은 이치이다. IS-A 관계이기 때문에 일부 자식 클래스의 자원과 기능은 사용할 수 없지만, 그 외엔 된다. 

 

 예를 들기 위해, 돈이라는 객체를 생성을 했다. 돈은 나라마다 단위가 다르므로, 이 돈 클래스를 상속받는 달러 클래스와 원클래스를 생성했다.

세 클래스 관계

 

 

 

 

Q1. "그럼 오버 라이딩된 메서드는 누구를 따라가나요? "

 당연히 오버라이드 된 자식 클래스의 메서드를 따라간다. 재정의된 메서드가 '우선'이기 때문에.

업캐스팅되어도, 메소드는 오버라이드 된 메소드가 우선이다.

Q2. "근데 이렇게 쓸 일이 있나요?"

 나는 이 질문이 사실 제일 중요하다고 생각한다. 목적 없이 외웠기에 잘 쓰지도 못했고, 이해하기도 어려웠다. 저 예제를 확장해보자. 나(J-Sik)와 민수라는 사람이 있다고 하자. 민수가 돈이 급해서, 일단 달러를 인출해서 민수에게 전달했다. 민수가 멍청해서 달러를 모른다면 "이 종이 쪼가리 말고, 신사임당을 달란 말이야!" 하면서 달러를 찢어버릴 순 있겠지만, 다행히 민수는 그렇게 멍청하진 않다. 그럼 민수는 일반적으로 이 돈을 받을 것이다. 화폐가 달라도 돈이니까.

 

public class Person {
    String name;
    int value ;

    Person(){
        this(1000,"J-sik");
    }

    Person(int value,String name){
        this.value = value;
        this.name = name;
    }

    public Dollar drawMoney(int value){
        System.out.println(name+ "은/는 "+"달러를 뽑았다 --" + value);
        this.value -= value;
        Dollar dollar = new Dollar(value, 12312312);
        return dollar;
    }

    public void getMoney(Money money){
        System.out.println(name+ "은/는 "+"돈을 얻었다 -- " + money.value);
        this.value+= money.value;


    }

    public void showStatus(){
        System.out.println(name+ "의 "+"현재 가진 가치 - " + this.value);
    }


}



public class Main {

    public static void main(String[] args) {

        Person j_sik = new Person();

        Person friend = new Person(0,"Minsu");
        //민수의 재정 상태
        friend.showStatus();
        //j_sik이 달러를 인출한다
        Dollar dollar = j_sik.drawMoney(1000);

        //민수가 돈을 받았다.
        friend.getMoney(dollar);
        //돈을 받은 뒤 재정 상태
        friend.showStatus();

    }

}

달러도 받아서 가치가 올라간 민수

 업 캐스팅이 없다면, 이게 한국돈인지, 미국 돈인지, 조건문으로 수많은 나라를 확인하며, 맞는 메서드를 호출해야겠지만,  업 캐스팅을 이용한다면, 무슨 돈이든 상관이 없다. 돈이기만 하면 받을 수 있으니까. 이러한 다형성의 특징을 업 캐스팅도 가진다.(이 부분은 잘 이해하고 넘어간다면, 인터페이스의 맥락도 엄청 쉬울 것이다.) 

 

 

다운 캐스팅(DOWNCASTING)

 업 캐스팅과 반대다. 부모 클래스가 자식 클래스로 타입캐스팅 되는 것이다. 다운캐스팅을 봤을 때, 약간 이상함을 느꼈다면 당연한 것이다. '고양이는 동물이다.'라는 말을 반대로 '동물은 고양이다' 라고 말하는 것처럼 들릴 것이기 때문에.

'동물 중 일부가 고양이' 일 수도 있는 것처럼, 부모 클래스 인스턴스가 자식 클래스로 타입캐스팅 되는것이 안되는 것은 아니다. 다만, 자식 클래스 객체가 자신의 고유 기능(자식 클래스에서 할당된 자원과 메서드)는 없을 뿐더러 사용을 할 수도 없다. 그래서 컴파일 오류는 안나지만, 자식 클래스 기능을 호출하면 런타임 중 ClassCastException에러를 발생시킨다.

 

 

그럼 다운 캐스팅을 쓰는 목적이 무엇일까?

 자식 클래스의 기능이 필요하다면, 자식클래스로 객체를 만들어 쓰면 된다. 그런데 왜 부모 클래스를 쓰고 다운 캐스팅을 하려는 걸까? 아까의 예제를 확장해보자. 민수가 받은 돈이 원인지, 달러인지 생각해 보고 싶어졌다. 그래서 사람 클래스에 받은 돈이 달러인지 원인지 표시하기로 했다.

 

public class Person {

	//나머지 생략
    
    
    public void getMoney(Money money){
        System.out.println(name+ "은/는 "+"돈을 얻었다 -- " + money.value);
	//추가된 코드
        if(money instanceof Dollar){
            System.out.println("그 돈은 달러다");
        }else{
            System.out.println("그 돈은 원이다.");
        }
        this.value+= money.value;

    }
	
    
    //나머지 생략

}

위와 같이 돈으로 업 캐스팅된 상태에서 다시 자식 클래스에 자원이 필요할 때 쓰는 것이다. 

즉, 다운 캐스팅은 업 캐스팅된 인스턴스를 다시 원상태로 돌리는 것이다. 

 

 

상속에 관련된 최대한도로 꾸겨 넣었다. 혹시라도 부족한 게 있거나, 재미가 없거나, 콘텐츠가 빠졌으면, 댓글에 남겨주자. 읽고, 또 읽으며 읽기만 할 것이다. 수정 따위 안 할 거다. 다시는 길게 안 만들 거다.(농담 ㅎㅎ) 

 


포스팅에 문제가 있거나, 설명이 잘못된 부분 지적 환영합니다.

더 나은 퀄리티의 콘텐츠를 제공할 수 있도록 노력하겠습니다.

읽어주셔서 감사합니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함