Backend/Java

JAVA 오버로딩, 오버라이딩을 통해 다형성 구현하기

컴슈터 2024. 3. 20. 11:42

다형성(polymorphism)이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것이다. 자바에서는 다형성을 위해 부모클래스 타입의 참조변수로 자식클래스 타입의 인스턴스를 참조할 수 있도록 한다. 오버로딩과 오버라이딩을 통해 다형성을 구현할 수 있다.

오버로딩과 오버라이딩의 비교

오버로딩과 오버라이딩은 그 단어의 유사함으로 인해 혼동하기 쉽다. 하지만 그 개념은 확실히 다르며, 그 차이점을 아는 것이 중요하다. 오버로딩(overloading)은 새로운 메서드를 정의하는 것이고, 오버라이딩(overriding)은 상속받은 기존의 메서드를 재정의하는 것이다.

  오버로딩 오버라이딩
 메서드명  동일   동일 
 매개변수 및 타입  다름  동일
 리턴 타입  관계 없음  동일

다형성을 구현하는 방법

오버로딩 : 메서드명이 같지만 파라미터가 다른 메서드

  • 메서드명이 같아야 한다.
  • 매개변수의 개수 또는 타입이 달라야 한다.
  • 매개변수는 같고 리턴 타입만 다르면 오버로딩이 성립되지 않는다.

메서드 오버로딩을 사용함으로써 메서드에 사용되는 이름을 절약할 수 있다. 또한, 메서드를 호출할 때 전달해야 할 매개변수의 타입이나 개수에 대해 크게 신경을 쓰지 않고 호출할 수 있게 된다. 대표적인 예로는 println() 메서드를 들 수 있다.

class OverloadingTest {

    void test() {
        System.out.println("No parameters");
    }

    void test(int param) {
        System.out.println(param);
    }

    void test(String param) {
        System.out.println(param);
    }

    void test(int param1, int param2) {
        System.out.println(param1 + ", " + param2);
    }
}

public class Sample {
    public static void main(String[] args) {

        OverloadingTest obj = new OverloadingTest();

        obj.test(); // 출력 결과 : No parameters
        obj.test(1); // 출력 결과 : 1
        obj.test("one"); // 출력 결과 : one
        obj.test(10, 20); // 출력 결과 : 10, 20
    }
}

오버라이딩 : 부모클래스에서 정의한 메서드를 자식클래스에서 재정의

  • 부모클래스의 메서드와 메서드 구성 요소 모두가 동일해야 한다.
  • 메서드명, 매개변수, 리턴 타입이 모두 같아야 한다.

자바에서 자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메서드를 상속받는다. 이렇게 상속받은 메서드는 그대로 사용해도 되고, 필요한 동작을 위해 재정의하여 사용할 수도 있다. 즉, 메서드 오버라이딩이란 상속받은 부모 클래스의 메서드를 재정의하여 사용하는 것을 의미한다.

class Employee {
    public String name;
    public int age;

    public void print() {
        System.out.println("사원의 이름은 " + this.name + "이고 나이는 " + this.age + "입니다.");
    }
}

class Manager extends Employee {
    String jobOfManage;

    public void print() {
        System.out.println("관리자 " + this.name + "은 " + this.jobOfManage + " 담당입니다.");
    }
}

public class Sample {
    public static void main(String[] args) {

        Manager obj = new Manager();
        obj.name = "홍길동";
        obj.age = 40;
        obj.jobOfManage = "PM";  
        obj.print(); // 출력 결과 : 관리자 홍길동은 PM 담당입니다.
    }
}

좀 더 알아보기 : 참조변수의 다형성

참조변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수보다 같거나 적어야 참조할 수 있다. 클래스는 상속을 통해 확장될 수는 있어도 축소될 수는 없기 때문에, 자식클래스에서 사용할 수 있는 멤버의 개수는 언제나 부모클래스와 같거나 많게 된다.

class Parent { ... }
class Child extends Parent { ... }

Parent pa = new Parent(); // 가능
Child ch = new Child();  // 가능
Parent pc = new Child(); // 가능
Child cp = new Parent();  // 오류 발생

참조변수의 타입 변환

class Parent { ... }
class Child extends Parent { ... }
class Brother extends Parent { ... }

Parent p1 = null;
Parent p2 = new Parent();
Child ch = new Child();
Brother br = null;

p1 = ch;          // 가능
br = (Brother)p2; // 가능
br = (Brother)ch; // 오류 발생

instanceof 연산자

다형성으로 인해 런타임에 참조변수가 실제로 참조하고 있는 인스턴스의 타입을 확인할 필요성이 생긴다. 자바에서는 instanceof 연산자를 제공하여, 참조변수가 참조하고 있는 인스턴스의 실제 타입을 확인할 수 있도록 해준다. 즉, 해당 객체가 어떤 클래스나 인터페이스로부터 생성되었는지를 판별해 주는 역할을 한다.

class A {}

class B extends A {}

public static void main(String[] args) {
    A a = new A();
    B b = new B();

    System.out.println(a instanceof A); // true
    System.out.println(b instanceof A); // true
    System.out.println(a instanceof B); // false
    System.out.println(b instanceof B); // true
}

왼쪽에 전달된 참조변수가 실제로 참조하고 있는 인스턴스의 타입이 오른쪽에 전달된 클래스 타입이면 true를 반환하고, 아니면 false를 반환한다. 만약에 참조변수가 null을 가리키고 있으면 false를 반환한다.

class Parent { ... }
class Child extends Parent { ... }
class Brother extends Parent { ... }

public class Polymorphism {
    public static void main(String[] args) {
        Parent p = new Parent();
        System.out.println(p instanceof Object); // true
        System.out.println(p instanceof Parent); // true
        System.out.println(p instanceof Child);  // false

        Parent c = new Child();
        System.out.println(c instanceof Object); // true
        System.out.println(c instanceof Parent); // true
        System.out.println(c instanceof Child);  // true    
    }
}