다형성(Polymorphism)은 하나의 타입에서 여러 가지 타입으로 확장할 수 있는 성질을 말한다.
다형성은 상속, 추상화와 더불어 객체지향 프로그램이에서 중요한 특징 중 하나로, 다형성을 활용하면 기능을 확장하거나, 객체를 변경해야할 때 타입 변경 없이 객체 생성만으로 타입 변경이 일어나게 할 수 있다.
오버로딩(OverLoading)은 자바의 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메소드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은 이름을 사용해서 메소드를 정의할 수 있다는 것을 말한다. 오버로딩의 특성 덕분에 함수 이름을 동일하게 사용하면서도 다양한 경우에 대응할 수 있다.
오버로딩은 컴파일 시점에 함수의 인자를 바탕으로 적절한 함수를 찾아주기 때문에, 이를 정적 다형성이라고도 한다.
다양한 데이터 타입을 처리할 수 있다.
- 오버로딩을 사용하면 동일한 이름의 메서드가 다양한 데이터 타입의 매개변수를 처리할 수 있기 때문에, 메서드의 사용 범위를 확장하고, 유연한 프로그래밍이 가능해진다.
코드의 중복을 줄일 수 있다.
- 동일한 기능을 수행하는 메서드를 여러 개 정의하는 대신, 매개변수가 다른 여러 가지 상황에 대응할 수 있는 하나의 메서드를 정의함으로써 코드의 중복을 줄일 수 있다.
코드의 가독성을 향상시킨다.
- 메서드의 이름이 일관되고 명확하면, 개발자가 코드를 더 쉽게 이해할 수 있다. 또한, 오버로딩을 사용하면 메서드의 이름을 기억하기 쉽고, 코드를 더 간결하게 작성할 수 있다.
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 어노테이션은 오버라이딩을 검증하는 기능을 한다. 코드를 검사했을 때 오버라이딩이 실제로 실행되지 않을 경우 컴파일 에러가 발생한다.
오버라이딩은 런타임에 객체의 타입에 따라 적절한 메서드를 동적으로 호출하기 때문에, 이를 동적 다형성이라고도 한다.
상속 관계
- 오버라이딩은 부모 클래스와 자식 클래스 사이에서 발생한다.
메서드 시그니처
- 오버라이딩할 메서드는 부모 클래스의 메서드와 동일한 이름, 매개변수 리스트, 반환 타입을 가져야 한다.
접근제어자
- 접근제어자를 설정할 때, 자식 클래스에서 오버라이딩하는 메서드의 접근제어자는 부모클래스보다 더 좁게 설정할 수 없다.
다형성 구현
- 오버라이딩을 통해 부모 클래스의 메서드를 자식 클래스에서 재정의할 수 있기 때문에 다형성을 구현할 수 있다. 부모 클래스 타입으로 선언된 객체에 실제 자식 클래스의 인스턴스를 할당하여, 실행 시에 동적으로 적절한 메서드가 호출된다.
확장성과 유연성
- 오버라이딩을 통해 자식 클래스는 부모 클래스의 기능을 확장하거나 변경할 수 있다. 부모 클래스의 메서드를 수정하지 않고 자식 클래스에서만 변경된 동작을 정의할 수 있기 때문에 코드의 유지보수성이 향상된다.
코드의 가독성과 간결성
- 오버라이딩은 메서드의 동작을 명확하게 재정의할 수 있기 때문에 코드의 가독성과 이해도를 높일 수 있다.
다양한 기능 구현
- 오버라이딩을 통해 다양한 기능을 구현할 수 있다.
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"가 출력된다. 이는 프로그램 실행 중에 객체의 실제 타입을 확인하여 적절한 메서드를 호출하는 동적 바인딩의 특성을 나타낸다.