한 클래스 내에 사용하려는 이름과 동일한 메소드가 기존에 있더라도, 매개변수의 개수 또는 타입이 다르면 동일한 메서드명을 사용해서 정의할 수 있다. 이것이 바로 오버로딩이다.
그런데 이 때 메서드명, 매개변수 타입, 매개변수 개수까지 같으면서 리턴 타입만 다른 것은 오버로딩 할 수 없다.
아래의 코드를 통해서 예를 들어보자.
class OverloadingTest {
public static void main(String[] args) {
OverloadingMethods om = new OverloadingMethods();
om.print();
System.out.println(om.print(3));
om.print("Hello!");
System.out.println(om.print(4, 5));
}
}
class OverloadingMethods {
public void print() {
System.out.println("오버로딩1");
}
String print(Integer a) {
System.out.println("오버로딩2");
return a.toString();
}
void print(String a) {
System.out.println("오버로딩3");
System.out.println(a);
}
String print(Integer a, Integer b) {
System.out.println("오버로딩4");
return a.toString() + b.toString();
}
}
오버로딩1
오버로딩2
3
오버로딩3
Hello!
오버로딩4
45
위 코드는 아무런 문제없이 잘 실행되고 있다. print라는 같은 이름을 가진 네개의 메소드가 매개변수의 개수와 타입를 다르게 지정하여 지정하는 것이 가능하다는 것을 보여주고 있다.
단, 여기서 한번 더 강조할 점은 '리턴 값'만 다르게 지정하는 것은 오버로딩할 수 없다는 것.
또한 접근 제어자도 자유롭게 지정해 줄 수 있다. 각 메소드의 접근 제어자를 public
, default
, protected
, private
으로 다르게 지정해줘도 상관없다는 것이다. 접근 제어자만 다르게한다고 오버로딩이 가능하지 않다는 것도 알아두자.
결국 오버로딩은 매개변수의 차이로만 구현할 수 있다는 것이다. 매개변수가 다르다면 리턴 값은 다르게 지정할 수 있다는 것이 핵심이다.
대표적인 예가 print 메서드이다. print 메서드는 int든 boolean이든 String이든 다양한 매개변수 타입을 지원한다.
이렇게 '출력하다.'라는 같은 기능을 가진 메소드들를 println이라는 하나의 이름으로 정의가 가능한 것이다.
위에서 예로 들었던 print 메서드를 매개변수의 종류에 따라서 다르게 지정한다고 생각해보자. printInt, printDouble, printBoolean 등 수많은 메소드들의 이름을 정해줘야 할 것이다. 이는 프로그래머의 입장에서는 메서드네이밍 고민을 가중시킨다. 그리고 이런 이름들을 절약하여 다른 곳에 사용할 수 있는 가능성도 생긴다.
다형성(하나의 메서드나 클래스가 다양한 방법으로 동작하는 것)의 대표적인 예시로는 오버로딩과 오버라이딩이 있다.
둘 다 다형성을 구현한다는 공통점을 가지고 있지만, 오버라이딩은 부모클래스로부터 상속받은 자식 클래스가 재정의(override)하는 행위이기 때문에,
1) 메서드명, 매개변수, 리턴값이 모두 같아야 한다.
2) 자식 클래스에서 오버라이딩하는 메서드의 접근 제어자는 부모 클래스보다 더 좁게 설정할 수 없다.
또한
3) 오버로딩은 컴파일 중에 발생하는 '정적' 다형성이고, 오버라이딩은 런타임 중에 발생하는 '동적' 다형성이다.
(단, @Override
어노테이션을 통해 컴파일 시 오버라이딩이 실제 시행 여부는 검증할 수 있다.)
어노테이션? 컴파일러에게 특정한 정보를 제공해주는 역할
아래 코드를 살펴보자
public class OverridingTest {
public static void main(String[] args) {
Person person = new Person();
Child child = new Child();
Senior senior = new Senior();
person.laugh();
child.laugh();
senior.laugh();
}
}
class Person {
void laugh() {
System.out.println("ㅎㅎ");
}
}
class Child extends Person {
@Override // 컴파일 중에 검증
protected void laugh() {
System.out.println("ㅋㅋ");
}
}
class Senior extends Person {
@Override
public void laugh() {
System.out.println("^^");
}
}
ㅎㅎ
ㅋㅋ
^^
기준 | Overriding | Overloading |
---|---|---|
접근 제어자 | 부모 클래스의 메소드의 접근 제어자보다 더 넓은 범위의 접근 제어자를 자식 클래스의 메소드에서 설정할 수 있다. | 모든 접근 제어자를 사용할 수 있다.(즉 달라도 상관없다) |
리턴타입 | 동일해해야 한다. | 달라도 된다.(같아도 호출 시 에러 X) |
메서드명 | 동일해야 한다. | 동일해야 한다. |
매개변수 | 동일해야 한다. | 달라야만 한다. |
다형성 실현 시점 | 런타임(단, 어노테이션을 통해 컴파일 타임에도 검증 가능) | 컴파일 타임 |
위의 내용을 바탕으로 둘을 비교 정리해보면 이렇다. 각 기법이 어떤 상황에서 어떻게 사용되고, 왜 사용되는지 알아두면 더 나은 프로그램을 설계할 수 있을 것이다.