티스토리 뷰

JAVA

[JAVA] 오버로딩과 오버라이딩의 차이점

까리한 새우 2024. 3. 16. 14:22

자바의 특징이자 객체 지향(OOP) 4대 특성인 다형성을 지원하는 방법으로 메서드 오버로딩과 오버라이딩이 있다.

 

오버로딩(Overloading) 

같은 이름의 메서드를 여러개 가지면서 매개변수의 유형과 개수가 다르도록 하는 기술

 

오버라이딩(Overriding)

상위 클래스가 가지고 있는 메서드를 하위 클래스가 재정의하는 기술

 

서로의 개념은 다르지만, 이름이 비슷하여 자주 헷갈리곤 합니다. 

그래서 이번 글에서는 오버로딩과 오버라이딩의 차이점을 하나하나 살펴보고 예제로도 알아보겠습니다.

(실제로 면접에서 자주 나오는 질문이라고 합니다만, 저는 면접을 본적이 없어서 잘 모르겠네요..)


 

오버로딩(Overloading)

오버로딩이란 같은 이름의 메서드를 여러개 정의하고, 매개변수의 유형과 개수를 다르게 하여 메서드를 정의하는 것을 말합니다.

오버로딩은 메서드에서만 가능한 것은 아니고, 메서드와 생성자 오버로딩이 가능합니다.

여기서 잠깐 알고가야할 점은 메서드 오버로딩할 때 리턴 값만 다른것은 오버로딩이 불가합니다. 

우선 메서드 오버로딩을 코드를 통해서 이해를 해보자.

 

메서드 오버로딩

public class OverloadingTest {
    public static void main(String[] args) {
        //OverlodingMethod 객체 생성
        OverloadingMethod om = new OverloadingMethod();
        
        //매개변수가 없는 print 메서드 실행
        om.print();
       
        //매개변수가 int인 print 메서드 실행
        om.print(10);
        
        //매개변수가 String인 print 메서드 실행
        om.print("hello");
        
        //매개변수가 boolean인 print 메서드 실행
        om.print(true);
        
        //매개변수가 2개의 int인 print 메서드 실행
        om.print(10, 20);
    }
}

class OverloadingMethod {
    public void print() {
        System.out.println("매개변수X 메소드");
    }

    public void print(int a) {
        System.out.println("매개변수 int 메소드, 값="+a);
    }

    public void print(String s) {
        System.out.println("매개변수 String 메소드, 값="+s);
    }

    public void print(boolean bool) {
        System.out.println("매개변수 boolean 메소드, 값="+bool);
    }

    public void print(int a, int b) {
        System.out.println("매개변수 int 2개 메소드, 값="+a+","+b);
    }
}

 

메서드를 호출 시에 매개변수에 따라 메서드가 매칭이되어 맞는 메서드를 실행시켜 줍니다.

저희는 OverloadingTest에서 print 메서드만 사용했을 뿐인데, 알아서 매칭되서 출력해주는 것을 확인할 수 있습니다.

 

< 결과 >

메서드 오버로딩

 

주의할 점은 처음에 말했듯이 매개변수의 유형과 개수가 달라야 오버로딩이지 아래 코드와 같이 리턴 타입만 다르다고 오버로딩이 되질 않습니다. 아래같이 코드를 작성할 경우 컴파일 오류가 발생합니다. 애초에 문법적으로 틀리다는 뜻이죠.

 

< 코드 >

public void print(int a) {
        System.out.println("매개변수 int 메소드");
    }

public int print(int b) {
    System.out.println("매개변수 int 메소드");
    return b;
}

 

 

생성자 오버로딩

생성자 오버로딩 또한 메서드 오버로딩과 동일하다.

아래 코드를 통해 확인을 해보자.

 

< 코드 >

class OverlodingConstructor {

    public OverlodingConstructor() {
    }

    public OverlodingConstructor(int a) {
    }

    public OverlodingConstructor(String s){
    }
    
}

 

한번 더 강조할 점은 리턴 값만 다르게 지정하는 것은 오버로딩 할 수 없다는 것이다. (생성자 오버로딩은 반환타입 없음)

 

접근 제어자도 자유롭게 지정해 줄 수 있다. 각 메소드의 접근 제어자를 public, default, protected, private 으로 다르게 지정해주어도 상관 없다는 것이다. 하지만 이 또한 접근 제어자만 다르게한다고 오버로딩이 가능하지는 않다.

 

결국엔 오버로딩은 매개변수의 차이로만 구현할 수 있다는 것이다. 매개변수가 다르다면 리턴 값은 다르게 지정할 수 있다.

 

 

오버로딩을 사용하는 이유는?

 

1.  같은 기능을 하는 메서드를 하나의 이름으로 사용 가능하다.

JAVA 에서 우리가 흔히 콘솔창에 텍스트를 출력할 때 사용하는 println 메서드를 대표적인 예로 들어볼 수 있다. 

우리는 println 의 인자 값으로 int, double, boolean, String 등의 아주 다양한 타입의 매개변수들을 넣어도 우리는 그 메서드들이 어떻게 실행되는지 모르지만 콘솔창에서는 결과를 잘 출력해주는 것을 볼 수 있다.

 

실제로 System.out.println() 에서 사용하는 println 메서드가 정의된 것을 확인해보면 아래 사진처럼 이미 구현되어 있는 것을 확인할 수 있다. (java.io.PrintStream에서 확인 가능하다.)

다양한 타입으로 구현되어 있는 println 메서드

이렇게 '출력하다' 와 같은 기능을 가진 메소드들을 println 이라는 하나의 이름으로 정의가 가능한 것이다.

 

 

2. 메서드의 이름을 절약할 수 있다.

위의 첫 번째 사용이유와 비슷하다고 볼 수 있다.

메서드 이름을 통일함으로써 메서드의 이름을 절약할 수 있는 것인데, 예를 들어 println 메서드에서 오버로딩이 안된다고 가정해보자. 그러면 메서드에서 받는 매개변수의 타입에 따라 메서드 이름이 달라져야 할 것이다. (메서드 이름 중복 X)

printlnInt, printlnString, printlnBoolean 등등 수많은 메서드들의 이름을 정해줘야 하는 번거로움이 생긴다.

 


 

오버라이딩(Overriding)

오버라이딩은 부모클래스로부터 상속받은 메소드를 자식 클래스에서 재정의하는 것을 말한다.

상속받은 메서드를 그대로 사용할수도 있지만, 자식 클래스에서 상황에 맞게 변경해야할 경우 오버라이딩할 필요가 생긴다.

 

오버라이딩은 오버로딩과는 다르게 부모 클래스의 메서드를 재정의하는 것이므로, 자식 클래스에서는 오버라이딩하고자 하는 메서드의 이름, 매개변수, 리턴 값이 모두 같아야 한다.

아래 코드를 통해 자세히 알아보자.

 

< 코드 >

public class OverridingTest {

    public static void main(String[] args) {
        Animal animal = new Animal();
        Cat cat = new Cat();
        Dog dog = new Dog();
        Cow cow = new Cow();

        animal.cry();
        cat.cry();
        dog.cry();
        cow.cry();
    }

}

class Animal {
    public void cry() {
        System.out.println("울음소리");
    }
}

class Cat extends Animal {
    public void cry() {
        System.out.println("cat: 야옹~");
    }
}

class Dog extends Animal {
    public void cry() {
        super.cry();
        System.out.println("dag: 멍멍~");
    }
}

class Cow extends Animal {
    // 아무 값도 입력하지 않음
}

 

< 결과 >

 

위의 코드에서 몇 가지 예시를 확인 할 수 있습니다.

우선 부모 클래스인 Animal 에서 cry 메서드를 호출 했을 때, "울음소리" 가 출력되는 것을 확인할 수 있죠? 당연한겁니다.

 

그리고 자식클래스 3개를 만들어주었는데 우선 Cat 에서 cry 메서드를 호출 했을 때는, "야옹~" 이 출력되는 것을 볼 수 있습니다. 이게 바로 오버라이딩입니다.

부모에도 cry 메서드가 있지만 자식 클래스에서 재정의함으로써 부모 메서드가 아닌 자식 클래스의 메서드가 호출된다.

 

Dog 자식 클래스에서는 super.cry() 라고 호출해주었는데, super 예약어는 부모클래스를 호출한다 라는 뜻입니다.

즉, super.cry() 라고 작성하면 부모의 cry 메서드를 호출해라 이런 뜻입니다. 

그러니 결과를 보시면 부모클래스의 "울음소리"와 자식클래스의 "멍멍~" 이 함께 출력되는 것을 볼 수 있습니다.

 

Cow 자식 클래스에서처럼 메서드 오버라이딩을 하지 않는다면, 출력결과에서 볼 수 있듯이 부모 클래스의 메서드가 호출됩니다.

 

오버라이딩시 주의할 점

1. 자식 클래스에서 오버라이딩하는 메서드의 접근 제어자는 부모 클래스보다 더 좁게 설정할 수 없습니다.

만약 부모 클래스에서 메서드가 default 접근제어자가 설정되어 있다면, 자식 클래스에서 오버라이딩시 default 보다 같거나 더 넒은 범위의 접근 제어자만 설정할 수 있습니다.

즉 default, protected, public 의 접근제어자를 사용할 수 있고 private 접근제어자는 사용할 수 없습니다.

 

2. 예외(Exception)는 부모 클래스의 메서드보다 많이 선언할 수 없습니다.

부모 클래스에서 어떤 예외를 throws 한다고 할때, 자식 클래스에서는 그 예외보다 더 넓은(큰) 범위의 예외를 throws 할 수 없습니다.

 

3. static 메서드를 인스턴스의 메서드로 또는 그 반대로 바꿀 수 없습니다.

무슨 얘기냐면, 상위 클래스의 static 메서드 오버라이딩이 불가능하다는 것입니다.

static 메서드는 클래스에 속하는 메서드이기 때문에 상속되지 않고 따라서 오버라이드도 되지 않습니다.

(static 메서드는 런타임 시에 생성되는 것이 아니라 컴파일 시 생성되어 메모리에 적재되는 방식이기 때문에 런타임 시에 해당 메서드를 구현한 실체 객체를 찾아가서 호출하게 됩니다. 즉, static 메서드에 대해서는 다형성이 적용되지 않습니다.)

 

다음과 같이 static 메서드를 오버라이드 하려면 에러가 발생한다.

 

여기서 이런 생각을 할 수도 있겠다

"어라? 오버라이딩할때 static 을 안붙여줘서 에러나는거 아니에요?!!??!"

static 을 붙이고 오버라이딩 한다면 실행은 되나 부모 클래스 메서드를 오버라이딩 한 것이 아닌 새로운 메서드를 정의한 것과 동일한 것으로 생각하면 된다.

 

그 외에도 final이 지정된 메서드 역시 오버라이드가 불가능하며, private 접근 제어자를 가진 메서드는 상속 자체가 불가능하기 때문에 오버라이드가 성립되지 않는다.

(final 같은 경우는 하위 클래스가 해당 메서드를 재정의 할 수 없도록 하기 위해서 사용된다.)

 


 

오버로딩 vs 오버라이딩

 

간단하게 정리하자면

오버로딩(Overloading) : 기존에 없는 메서드를 새로 추가

오버라이딩(Overriding) : 상속 받은 메서드를 재정의

출처 :&nbsp;https://hyoje420.tistory.com/14