[Java] 08. 클래스의 상속과 this, super 키워드 사용법


Study/Java  2020. 5. 6. 15:00

안녕하세요. 명월입니다.


이 글은 Java에서의 클래스의 상속과 this, super 키워드 사용법에 대한 글입니다.


이전에 클래스를 생성하는 방법에 대한 글을 작성했었습니다.

링크 - [Java] 07. 클래스를 생성하는 방법 (생성자를 사용하는 방법)


클래스는 하나의 목적을 두고 구성한다고 설명을 했습니다. 그러나 프로그램을 만들 때, 여러가지 클래스를 만들게 되는데 작성하게 되면 비슷비슷한 클래스가 여럿 생성되는 것을 보실 수 있습니다.

예를 들면, 이전 포스트에서 가계부에 관한 클래스를 작성했는 데 그걸 통해 좀 더 자세히 설명하겠습니다.

import java.util.List;
import java.util.ArrayList;
// 클래스  
public class Privatefinance {
  // 맴버 변수
  // 입금에 관계된 변수
  private List<Integer> input;
  // 출금에 관계된 변수
  private List<Integer> output;
  // 생성자
  public Privatefinance() {
    // 리스트를 할당한다.
    this.input = new ArrayList<>();
    this.output = new ArrayList<>();
  }
  // 입금을 한다.
  public void input(int money) {
    // 맴버 변수에 금액을 추가한다.
    this.input.add(money);
  }
  // 출금을 한다.
  public void output(int money) {
    // 맴버 변수에 금액을 추가한다.
    this.output.add(money);
  }
  // input 리스트의 데이터를 취득한다.
  protected int getInput(int i) {
    // input 리스트의 데이터를 반환
    return input.get(i);
  }
  // output 리스트의 데이터를 취득한다.
  protected int getOutput(int i) {
    // output 리스트의 데이터를 반환
    return output.get(i);
  }
  // 계산한다.
  public int calculator() {
    // 결과를 위한 변수
    int sum = 0;
    // 리스트에 저장된 모든 입금 금액을 더한다.
    for (int i = 0; i < input.size(); i++) {
      sum += input.get(i);
    }
    // 리스트에 저장된 모든 출금 금액을 뺀다.
    for (int i = 0; i < output.size(); i++) {
      sum -= output.get(i);
    }
    // 결과를 반환
    return sum;
  }
}

Privatefinance의 클래스는 입금, 출금에 관한 가계부에 관한 클래스입니다. 입금, 출금 함수를 이용해서 데이터를 모으고 최종적으로 calculator함수를 통해서 총액을 계산합니다.

그러나 이 클래스는 놔두고 매우 비슷한 통장 내역에 관한 클래스를 만들고 싶습니다.

클래스의 내용은 기본적으로 입금, 출금은 같지만 내역이 있으면 더 좋겠습니다. 그러나 이전의 Privatefinance의 클래스는 서비중의 클래스이고 수정을 하면 안된다는 조건이 있습니다.

서비스 중의 클래스를 수정을 해야하고 그것에 맞는 테스트를 다시 실시해야 합니다. 위 예제는 워낙 간단한 소스이니 조금 수정한다고 문제가 되지 않겠지만, 꽤 매우 복잡한 실무 프로그램이라고 하면 아마 선뜻 수정하기 어려울 것입니다.


그러면 가장 쉽게 클래스를 만들 수 있는 방법이 Copy + Paste를 통해서 클래스를 작성하는 것입니다. 그대로 Copy + Paste를 해서 클래스명을 바꿔서 생성하면 됩니다. 그렇게 사양에 따라 한 두개가 아닌 30~40개의 클래스를 만들었습니다.

그런데 만약 자바 사양에서 갑자기 List 클래스를 사용하지 말라고 제약이 왔습니다. 사실 이런 가능성은 제로입니다만 예를 들어 생각해 봅시다.

그럴 경우 위 맴버 변수인 input과 ouput의 자료형을 모두 수정해야 할 것입니다. 아마 Copy + Paste해서 만든 클래스가 30~40개이니 30~40개의 모든 클래스를 수정 해야 겠네요..그럴 경우 저의 경험상, 반드시 빠트리는 실수가 발생할 것입니다. 나는 매우 꼼꼼한 사람이다해도 사람이 하는 일은 실수가 발생합니다. 그럼 프로그램에 버그가 발생하는 것입니다.

실제 실무에서도 그렇게 만드는 사람이 없다고 이야기하지 않습니다. 꽤 많습니다.


그러나 위의 문제를 클래스의 상속을 이용하면 아주 깔끔하게 해결됩니다.

import java.util.ArrayList;
import java.util.List;
// Account 클래스 작성, Privatefinance 클래스를 상속
public class Account extends Privatefinance {
  // 내부 inline 클래스, 즉, 클래스 내부에 있는 클래스로 외부에서는 접근 금지
  private class Context {
    String context;
    int type;
  }
  // 통장 내역을 저장할 변수
  // 변수의 할당은 생성자에서 해야 하지만, 직접 맴버 변수로 해도 상관없다.
  private List<Context> context = new ArrayList<>();
  // 위 Context 클래스 생성 함수
  private Context newContext(String context, int type) {
    // Context 클래스 생성
    Context c = new Context();
    // 내역
    c.context = context;
    // 타입
    c.type = type;
    // 생성된 클래스를 리턴
    return c;
  }
  // 입금에 관한 함수 (이전 금액만 넣는 intput 함수에서 내역도 추가한다.)
  public void input(String context, int money) {
    // Context 생성
    Context c = newContext(context, 1);
    // 내역 추가
    this.context.add(c);
    // Privatefinance 클래스의 input 함수에 금액을 추가한다.
    super.input(money);
  }
  // 출금에 관한 함수 (이전 금액만 넣는 output 함수에서 내역도 추가한다.)
  public void output(String context, int money) {
    // Context 생성
    Context c = newContext(context, 2);
    // 내역 추가
    this.context.add(c);
    // Privatefinance 클래스의 output 함수에 금액을 추가한다.
    super.output(money);
  }
  // 출력 함수
  public void print() {
    // 해더 출력
    System.out.println("No\tContext\t\t입금\t\t출금");
    // 모든 내역의 순서는 context 리스트에 add 순서대로 있다.
    // ※ for문의 변수 초기치는 하나의 변수가 아닌 복수의 변수도 가능
    for (int inputIndex = 0, outputIndex = 0, i = 0; i < context.size(); i++) {
      // context 리스트에서 Context 클래스를 취득
      Context c = context.get(i);
      int money = 0;
      if (c.type == 1) {
        // 입금 리스트에서 금액을 취득
        money = getInput(inputIndex);
        // 입금 리스트의 인덱스 증가
        inputIndex++;
        // 콘솔 출력
        System.out.println((i + 1) + "\t" + c.context + "\t\t" + money + "\t\t");
      } else {
        // 출금 리스트에서 금액을 취득
        money = getOutput(outputIndex);
        // 출금 리스트의 인덱스 증가
        outputIndex++;
        // 콘솔 출력
        System.out.println((i + 1) + "\t" + c.context + "\t\t" + "\t\t" + money);
      }
    }
  }
  // 실행 함수
  public static void main(String... args) {
    // Account 클래스 할당
    Account account = new Account();
    // 통장 입금
    account.input("월급", 100000);
    // 통장 출금
    account.output("핸드폰 요금", 30000);
    // 통장 출금
    account.output("공과금", 50000);
    // 통장 내역 출력
    account.print();
    // 개행 추가
    System.out.println();
    // 콘솔 출력
    System.out.println("총 잔액 : " + account.calculator());
  }
}

위의 클래스는 Account는 Privatefinance의 클래스를 상속 받았습니다.

클래스를 상속 받을 때는 extends의 키워드를 사용합니다. Java에서의 상속은 두개의 클래스를 상속받는 다중 상속은 금지되어 있고 오직 하나의 클래스만 상속을 받을 수 있습니다.

상속의 의미는 Account클래스에서 Privatefinance클래스의 모든 것을 사용할 수 있다는 것입니다.

main 함수를 보면 Account 클래스에 calculator 함수가 없습니다. 그러나 Privatefinance 클래스에 calculator 함수가 있기 때문에 상속 받은 Account 클래스는 calculator를 사용할 수 있습니다.


그럼 클래스 내부에서는 input의 함수를 작성했습니다. 일단 Privatefinance 클래스의 input함수와 파라미터의 정의가 다르기 때문에 재정의(override)는 아닙니다.

재정의(override)에 관해서는 설명이 길어지기 때문에 따로 추상 클래스에 대한 설명을 작성할 때 설명하겠습니다.

일단 input 함수를 보면 안에 super.input(money)를 작성한 것을 볼 수 있습니다.


여기서 this와 super 차이를 알 수 있는데, this는 현재의 클래스, 즉 Account의 필드 영역을 참조하는 것이고, super의 경우는 상위 클래스, 즉 Privatefinance의 필드를 이야기하는 것입니다.

즉, super.input은 Privatefinance 클래스의 input함수를 호출하는 것입니다.

// 상위 클래스 (SuperProgram)
class SuperProgram {
  // 함수
  public void print() {
    // 콘솔 출력
    System.out.println("SuperProgram print");
  }
}
// 클래스 , SuperProgram를 상속 받는다.
public class Program extends SuperProgram{
  // 함수
  public void print() {
    // 콘솔 출력
    System.out.println("Program print");
  }
  // 함수
  public void run() {
    // 상위 클래스의 print 호출
    super.print();
    // 내부 클래스의 print 호출
    this.print();
  }

  // main 함수
  public static void main(String... args) {
    // Program 클래스 생성
    Program p = new Program();
    // run 함수 호출
    p.run();
  }
}

이렇게 Program 클래스에서 run 함수를 호출하면 this와 super 차이가 명확하게 보이네요.


그러면 상속의 장점은 무엇이 있을까요?

딱 봐도 소스의 가독성이 좋습니다. 소스의 가독성은 소스가 얼마나 쉽게 읽히느냐인데, 소스의 스탭이 적으니 가독성이 좋습니다.

관리의 용이성도 좋습니다. 만약 Copy + Paste로 Account의 클래스를 작성했다. 그런데 Privatefinance의 클래스에 버그가 있었다. 그래서 수정을 한다고 하면 Copy + Paste한 소스를 모두 찾아서 수정해야 합니다. (일단 Copy + Paste를 하는 것 자체가 최악의 개발입니다.)

마지막으로 클래스와 상속은 객체 지향 언어(OOP)의 꽃이라고 할 수 있습니다. 그만큼 클래스와 상속으로 많은 개발 패턴(디자인 패턴)이 생겨나고 개발 기법이 생겨나 프로그램이 정말 쉽게 작성할 수 있지 않을까 생각합니다.


여기까지 Java에서의 클래스의 상속과 this, super 키워드 사용법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.