jb.log

..loading

JAVA - 17. 메소드 오버라이딩

August 19, 2019

메소드 오버라이딩(Method Overriding)이란?

상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 행위를 가리켜 '메소드 오버라이딩' 이라 하는데, 여기서 말하는 오버라이딩은 '무효화 시키다'의 뜻으로 해석이 된다.

다음 예제를 통해 메소드 오버라이딩의 결과를 확인해보자.

class Cake{
    public void yummy() {
        System.out.println("Yummy Cake");
    }
}

class CheeseCake extends Cake{
    public void yummy() {// Cake의 Yummy 메소드를 오버라이딩 함
        System.out.println("Yummy Cheese Cake");
    }
}

public class YummyCakeOverriding {
    public static void main(String[] args) {
        Cake c1 = new CheeseCake();
        CheeseCake c2 = new CheeseCake();
        
        c1.yummy(); //오버라이딩 한 CheeseCake의 Yummy 메소드가 호출됨
        c2.yummy(); //오버라이딩 한 CheeseCake의 Yummy 메소드가 호출됨
    }
}

실행 결과 : Yummy Cheese Cake가 두번 출력 되는 것을 알 수 있다.

위의 CheeseCake 클래스는 Cake를 상속하면서, Cake에 정의된 yummy메소드와 다음 세 가지가 같은 메소드를 정의하였다.

  • 메소드의 이름
  • 메소드의 반환형
  • 메소드의 매개변수 선언

위의 세가지가 같아야 메소드 오버라이딩이 성립한다.

즉 Cake의 yummy 메소드를 CheeseCake의 Yummy 메소드가 오버라이딩 하였다. 그리고 오버라이딩을 하면, 참조변수의 형에 상관없이 오버라이딩 한 메소드가 오버라이딩된 메소드를 대신하게 된다.

위의 예제의 main 메소드에서 다음과 같이 Cake 형 참조변수로 CheeseCake 인스턴스를 참조하였다.

Cake c1 = new CheeseCake();//업캐스팅

그리고 다음과 같이 yummy 메소드를 호출하였다.

c1.yummy();

앞서 설명한 바에 의하면 c1은 Cake형 참조변수이니, 위 문장의 경우 Cake의 yummy 메소드가 호출되어야 한다. CheeseCake 인스턴스를 참조하고 있는 상황이라도 말이다. 그러나 Cake의 yummy 메소드는 오버라이딩 되었다(무효화 되었다). 따라서 이 경우에는 CheeseCake의 yummy 메소드가 대신 호출이 된다.

메소드 오버라이딩의 일반화

앞서 설명한 메소드 오버라이딩을 문법적으로 정리하기 위해서 클래스를 다음과 같이 정의하였다.

class Cake{
  public void yummy(){...}
}
class CheeseCake extends Cake{
  public void yummy(){...}
}
class StrawberryCheeseCake extends CheeseCake{
  public void yummy(){...}
}

위와 같이 클래스를 정의한 경우 CheeseCake의 참조변수와 인스턴스의 생성문을 다음과 같이 구성할 수 있다.

Cake c1 = new StrawberryCheeseCake();
CheeseCake c2 = new StrawberryCheeseCake();
StrawberryCheeseCake c3 = new StrawberryCheeseCake();

그리고 다음 세 문장이 실행되었을 때 호출되는 메소드는 StrawberryCheeseCake의 yummy 메소드이다.

c1.yummy(); //StrawberryCheeseCake의 yummy 메소드 호출
c2.yummy(); //StrawberryCheeseCake의 yummy 메소드 호출
c3.yummy(); //StrawberryCheeseCake의 yummy 메소드 호출

오버라이딩된 메소드를 호출하는 방법

위의 예제들에서도 알 수 있듯이 Cake, CheeseCake에 정의된 yummy 메소드들을 위의 방법처럼 호출하는 것은 불가능하다.

하지만 클래스 외부가 아닌 내부에서 Cake의 yummy 메소드를 호출하는 방법은 있다. 다음 예제를 살펴보자.

package ch11_상속;
//오버라이딩 된 메소드를 호출하는 방법 예제

class Cake{
    public void yummy() {
        System.out.println("Yummy Cake");
    }
}

class CheeseCake extends Cake{
    public void yummy() {
        super.yummy();
        System.out.println("Yummy CheeseCake");
    }
}

public class YummyCakeSuper {
    public static void main(String[] args) {
        CheeseCake cake = new CheeseCake();
        cake.yummy();
    }
}

지금까지는 상위 클래스의 생성자를 호출할 목적으로 키워드 super를 사용하였다. 그런데 위의 예제에서 보이듯이 상위 클래스에 정의된, 오버라이딩 된 메소드의 호출을 목적으로도 super가 사용될 수 있다.

Object 클래스

클래스를 정의할 때 어떤 클래스도 상속하지 않으면 해당 클래스는 java.lang 패키지에 묶여 있는 Object 클래스를 상속하게 된다.

class MyClass {...}

class MyClass extends Object {...}

두 클래스의 정의는 동일하다.

물론 위의 설명에도 언급했듯이 상속하는 클래스가 있는 경우에는 Object 클래스를 상속하지 않는다.

class MyClass extends OtherClass {...} 

그러나 이 경우에도 OtherClass 또는 OtherClass가 상속하는 클래스가 Object 클래스를 상속한다. 결국 자바의 모든 클래스는 Object 클래스를 직접 혹은 간접적으로 상속하게 되어있다. 그렇다면 자바의 모든 클래스는 Object 클래스를 상속하도록 한 이유는 무엇일까?

이는 자바의 모든 인스턴스에 공통된 기준 및 규약을 적용하기 위함이다. 한 예로 자바의 모든 인스턴스는 다음 메소드의 인자로 전달될 수 있다.

public void println(Object x) // System.out.println 메소드

위 메소드의 매개변수 형이 Object이다. 따라서 자바의 모든 인스턴스는 위 메소드의 인자가 될 수 있다. 그리고 위의 메소드는 인자로 전달된 인스턴스의 다음 메소드를 호출한다. 이 메소드는 Object 클래스에 정의되어 있는 메소드이므로 모든 인스턴스를 대상으로 호출이 가능하다.

이 블로그엔 포스팅하지 않았지만 글쓴이의 github 에 String 클래스 예제에 대해 다루었다.(목차에서 String 클래스 부분의 글들을 찾아보면 된다)

이 예제에서 클래스를 정의하면서 toString 메소드를 정의한 바 있다. 그런데 사실 이것은 Object 클래스의 toString 메소드를 오버라이딩 한 것 이다. 이와 관련해서 다음 예제를 살펴보자.

class Bread{
    
    //오브젝트 클래스의 toString 메소드를 오버라이딩
    public String toString() {
        System.out.println(super.toString());
        return "My Bread";
    }
}

class CreamBread extends Bread{
    
    //Bread 클래스의 toString 메소드를 오버라이딩
    public String toString() {
        
        return "my CreamBread";
    }
}

public class OverridingToString {
    public static void main(String[] args) {
        Bread b1 = new Bread();
        Bread b2 = new CreamBread();
        
        //b1이 참조하는 인스턴스의 toString 메소드 호출로 이어짐
        System.out.println(b1);
        
        //b2가 참조하는 인스턴스의 toString 메소드 호출로 이어짐
        System.out.println(b2);
    }
}

클래스와 메소드의 final 선언

클래스를 정의하는데 있어서 해당 클래스를 다른 클래스가 상속하는 것을 원치 않는다면, 다음과 같이 final 선언을 추가하면 된다.

public final class MyClass{...} //MyClass 는 다른 클래스가 상속 할 수 없음

대표적인 final 클래스로 String 클래스가 있다. 따라서 우리는 String 클래스를 상속할 수 없다. 또한 다음과 같이 메소드의 정의에 final 선언을 추가하여 해당 메소드의 오버라이딩을 허용하지 않을 수 도 있다.

public final void func(){...}

@Override

자바 5에서 '어노테이션(Annotations)'이라는 것이 소개되었다. 그리고 이와 관련하여 이후에 별도로 설명을 하겠다. 그러나 상속, 정확히는 메소드 오버라이딩과 관련 있는 내용이 있어 이에 대한 부분만 먼저 소개하고자 한다. 다음 예제를 보자. 이 예제는 컴파일도 되고 실행도 잘 된다. 그러나 프로그래머의 실수가 일부 포함되어 있다. 그 실수가 무엇인지찾아보자.

class ParentAdder{
    public int add(int a, int b) {
        
        return a + b;
    }
}

class ChildAdder extends ParentAdder{
    
    // 상위 클래스의 add를 오버라이딩 하려고 합니다.
    public double add(double a, double b) {
        return a + b;
    }
}

public class OverrideMistake {
    public static void main(String[] args) {
        ParentAdder adder = new ChildAdder();
        System.out.println(adder.add(3, 4));
    }
}

클래스 ChildAdder 는 ParentAdder를 상속한다. 그리고 ParentAdder의 add를 오버라이딩 할 의도였음을 주석을 통해 알 수 있다. 그러나 부모 메소드와 매개변수 타입과 반환형이 달랐기 때문이다. 이러한 유형의 실수는 매우 흔하다. 그럼에도 불구하고 발견이 쉽지 않기 때문에 치명적인 실수가 될 수 있다. 제일 좋은 것은 컴파일 과정에서 실수가 확인되는 것이다. 그러나 이 경우 문법적으로는 오류가 없기 때문에 컴파일도 되고 실행도 된다.

이러한 상황을 방지하기 위해서 '어노테이션' 이라는 것을 사용할 수 있다. 어노테이션은 일종의 메모이다. 그것도 '자바 컴파일러에게 메시지를 전달하는 목적의 메모'이다. ChildAdder 클래스를 설계하는 과정에서 add 메소드가 ParentAdder의 add 메소드를 오버라이딩 할 의도였다면 다음과 같이 메모를 달아준다.

class ChildAdder extends ParentAdder{
    
    // 상위 클래스의 add를 오버라이딩 하려고 합니다.
    @Override
    public double add(double a, double b) {
        return a + b;
    }
}

위와같이 어노테이션을 정의하면 컴파일러는 오버라이딩이 제대로 되었는지 확인을 하고, 프로그래머의 의도대로 오버라이딩이 되지 않았다면 컴파일 단계에서 에러를 전달해준다.

메소드를 오버라이딩 해야 한다면, 이렇듯 어노테이션을 사용하여 컴파일 과정에서 확인되지 않는 오류의 발생을 차단하는 것이 좋다.

Other Posts

October 1, 2019
jsp 프로젝트 만들기 - mvc1, mvc2
이전에 만든 `board-detail.jsp`은 DB와 잘 연결되어 화면에 데이터를 잘 출력하는 것을 볼 수 있다. 하지만 jsp 파일 내의 코드를 보면 자바코드와 html코드가 뒤엉켜 있는 것을 볼 수 있다. 이것을 스파게티 코드라 한다.
September 29, 2019
jsp 프로젝트 만들기 - 시작
본격적으로 jsp를 이용한 servlet 프로젝트를 만들어보겠다. jsp와 servlet의 활용 목적이 주된 내용이기 html/css 는 가급적 손대지 않고 비즈니스 로직에 집중하도록 하겠다.
September 25, 2019
jsp 프로그래밍
jsp란 `Java Server Pages` 의 약자이며 HTML 코드에 JAVA 코드를 넣어 동적웹페이지를 생성하는 웹어플리케이션 도구이다.
September 24, 2019
Servlet 상태관리
서블릿은 요청이 오면 응답을 주고 메모리에서 사라지기 때문에 서블릿들 간의 연결이 불가능하다. 만약 기존의 데이터를 저장할 일이 생겼다고 하면 서블릿 스스로 저장할 수 있는 것은 아니다. 이것은 ServletContext로 해결할 수 있다.
September 22, 2019
한글 인코딩
servlet 클래스에서 한글을 출력하면 한글이 깨지는 것을 볼 수 있다. 해당 문제점은 다음과 같이 해결할 수 있다.
September 20, 2019
Servlet 다루기
기존의 html 문서만으로는 동적인 내용을 전달할 수 없다. 때문에 WAS(web application server) 에서 동작하는 프로그래밍 언어를 사용하면 가능하다.