[JAVA] 다형성(Polymorphism), 오버로딩 & 오버라이딩

henu·2024년 7월 28일

다형성


다형성(Polymorphism)은 하나의 타입에서 여러 가지 타입으로 확장할 수 있는 성질을 말한다.
다형성은 상속, 추상화와 더불어 객체지향 프로그램이에서 중요한 특징 중 하나로, 다형성을 활용하면 기능을 확장하거나, 객체를 변경해야할 때 타입 변경 없이 객체 생성만으로 타입 변경이 일어나게 할 수 있다.

오버로딩


오버로딩(OverLoading)은 자바의 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메소드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메소드를 정의할 수 있다는 것을 말한다. 오버로딩의 특성 덕분에 함수 이름을 동일하게 사용하면서도 다양한 경우에 대응할 수 있다.
오버로딩은 컴파일 시점에 함수의 인자를 바탕으로 적절한 함수를 찾아주기 때문에, 이를 정적 다형성이라고도 한다.

오버로딩의 조건


  • 메서드 이름이 동일해야 한다. 메서드의 이름이 동일하면, 코드의 일관성이 유지되고 개발자가 메서드의 기능을 쉽게 이해할 수 있다.
  • 매개변수의 타입 또는 개수가 달라야 한다. 같은 이름의 메서드가 다양한 매개변수를 받아 처리할 수 있게 하여, 메서드의 사용 범위를 확장할 수 있다.
  • 반환 타입은 영향을 주지 않는다. 즉, 반환 타입만 다르게 지정한 메서드를 오버로딩할 수 없다.
  • 접근 제어자도 자유롭게 지정해줄 수 있다. 각 메서드의 접근 제어자를 public, default, protected, private으로 다르게 지정해줘도 상관없다. 하지만, 접근 제어자만 다르게 한다고 오버로딩이 가능하지는 않다.

오버로딩을 사용하는 이유와 장점


  • 다양한 데이터 타입을 처리할 수 있다.
    - 오버로딩을 사용하면 동일한 이름의 메서드가 다양한 데이터 타입의 매개변수를 처리할 수 있기 때문에, 메서드의 사용 범위를 확장하고, 유연한 프로그래밍이 가능해진다.

    • 예를 들어, 텍스트 출력 시 사용하는 println의 인자 값으로 int, double, boolean, String 등의 다양한 타입의 매개변수들을 집어 넣어도 println 메서드는 각 탕비에 맞게 데이터를 출력해준다.
  • 코드의 중복을 줄일 수 있다.
    - 동일한 기능을 수행하는 메서드를 여러 개 정의하는 대신, 매개변수가 다른 여러 가지 상황에 대응할 수 있는 하나의 메서드를 정의함으로써 코드의 중복을 줄일 수 있다.

    • println 메서드를 각각 정의하게 되면, printlnInt, printlnDouble 등 많은 메서드가 만들어져 코드의 중복이 생기고, 메서드들의 이름을 각각 정의해주기 때문에 복잡성이 가중된다.
  • 코드의 가독성을 향상시킨다.
    - 메서드의 이름이 일관되고 명확하면, 개발자가 코드를 더 쉽게 이해할 수 있다. 또한, 오버로딩을 사용하면 메서드의 이름을 기억하기 쉽고, 코드를 더 간결하게 작성할 수 있다.

오버로딩의 예시



public class Example {
    public int sum(int a, int b) {
        return a + b;
    }

    public double sum(double a, double b) {
        return a + b;
    }

    public int sum(int a, int b, int c) {
        return a + b + c;
    }

    public String sum(String a, String b) {
        return a + b;
    }

    public static void main(String[] args) {
        Example example = new Example();
        System.out.println(example.sum(3, 5)); // 8
        System.out.println(example.sum(3.5, 5.5)); // 9.0
        System.out.println(example.sum(3, 5, 7)); // 15
        System.out.println(example.sum("Hello, ", "world!")); // "Hello, world!"
    }
}

오버라이딩


오버라이딩(Overriding)은 상속 관계에서 자식 클래스가 부모 클래스의 메서드를 재정의하는 것을 말한다. 상속받은 메서드를 그대로 사용할 수도 있지만, 자식 클래스에서 상황에 맞게 변경해야하는 경우 오버라이딩할 필요가 생긴다.

@Override 어노테이션은 오버라이딩을 검증하는 기능을 한다. 코드를 검사했을 때 오버라이딩이 실제로 실행되지 않을 경우 컴파일 에러가 발생한다.

오버라이딩은 런타임에 객체의 타입에 따라 적절한 메서드를 동적으로 호출하기 때문에, 이를 동적 다형성이라고도 한다.

오버라이딩의 조건


  • 상속 관계
    - 오버라이딩은 부모 클래스와 자식 클래스 사이에서 발생한다.

  • 메서드 시그니처
    - 오버라이딩할 메서드는 부모 클래스의 메서드와 동일한 이름, 매개변수 리스트, 반환 타입을 가져야 한다.

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

    • 부모 클래스의 메서드보다 더 많이 선언할 수 없다.
    • static 메서드는 인스턴스의 메서드로 또는 그 반대로 바꿀 수 없다. 부모클래스의 static 메서드를 자식에서 같은 이름으로 정의할 수 있지만 이것은 다시 정의하는 것이 아니라 같은 이름의 static 메서드를 새롭게 정의하는 것이다.

오버라이딩을 사용하는 이유와 장점


  • 다형성 구현
    - 오버라이딩을 통해 부모 클래스의 메서드를 자식 클래스에서 재정의할 수 있기 때문에 다형성을 구현할 수 있다. 부모 클래스 타입으로 선언된 객체에 실제 자식 클래스의 인스턴스를 할당하여, 실행 시에 동적으로 적절한 메서드가 호출된다.

  • 확장성과 유연성
    - 오버라이딩을 통해 자식 클래스는 부모 클래스의 기능을 확장하거나 변경할 수 있다. 부모 클래스의 메서드를 수정하지 않고 자식 클래스에서만 변경된 동작을 정의할 수 있기 때문에 코드의 유지보수성이 향상된다.

  • 코드의 가독성과 간결성
    - 오버라이딩은 메서드의 동작을 명확하게 재정의할 수 있기 때문에 코드의 가독성과 이해도를 높일 수 있다.

    • 상속 관계에서 동일한 이름의 메서드를 사용하므로 코드의 일관성과 간결성을 유지할 수 있다.
  • 다양한 기능 구현
    - 오버라이딩을 통해 다양한 기능을 구현할 수 있다.

    • 부모 클래스에서 정의한 기본 동작을 자식 클래스에서 수정하거나 추가 기능을 구현하여 다양한 요구사항을 충족시킬 수 있다.

오버라이딩의 예시



public class Example {
    static class Animal {
        public void makeSound() {
            System.out.println("The animal makes a sound");
        }
    }

    static class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("The dog barks");
        }
    }

    static class Cat extends Animal {
        @Override
        public void makeSound() {
            System.out.println("The cat meows");
        }
    }

    static class Cow extends Animal {
        @Override
        public void makeSound() {
            System.out.println("The cow moos");
        }
    }

    public static void main(String[] args) {
        Animal myAnimal = new Animal();
        Animal myDog = new Dog();
        Animal myCat = new Cat();
        Animal myCow = new Cow();

        myAnimal.makeSound(); // "The animal makes a sound"
        myDog.makeSound(); // "The dog barks"
        myCat.makeSound(); // "The cat meows"
        myCow.makeSound(); // "The cow moos"
    }
}
  • 오버라이딩
    - Dog, Cat, Cow 클래스에서는 Animal 클래스의 makeSound 메서드를 재정의(오버라이딩)하여 동물의 소리를 다르게 출력한다. 이렇게 오버라이딩된 메서드는 부모 클래스의 메서드를 자식 클래스에서 재정의하여 사용하는 것을 의미한다.

  • 다형성
    - Animal 클래스로 선언된 객체인 myAnimal과 Dog, Cat, Cow 클래스로 선언된 객체인 myDog, myCat, myCow는 모두 Animal 타입으로 선언되었다. 그러나 실제로는 각각의 객체는 다른 클래스의 인스턴스를 가지고 있기 때문에, myDog, myCat, myCow 객체는 오버라이딩된 makeSound 메서드를 호출하므로 각각의 동물 소리를 출력한다.

  • 동적 바인딩
    - 메서드 오버라이딩은 실행 시에 동적으로 적절한 메서드가 호출된다. 예를들어, myDog.makeSound()를 호출하면 Dog 클래스에서 오버라이딩된 makeSound 메서드가 호출되어 "The dog barks"가 출력된다. 이는 프로그램 실행 중에 객체의 실제 타입을 확인하여 적절한 메서드를 호출하는 동적 바인딩의 특성을 나타낸다.

profile
주니어 백엔드 개발자입니다

0개의 댓글