본문 바로가기

Java SE Tutorials/Overriding

Java Overriding, Overload & Hiding

Java 프로그래밍, 오버라이딩 ( Overriding ), 오버로드(Overload), Hiding


오버라이딩(Overriding)의 개념

상속에서 부모 클래스에 있는 인스턴스 메소드의 시그니쳐(Signature) 및 리턴타입이 동일한 메소드를 하위 클래스에서 다시 정의하면 오버라이딩(Overriding)이라는 문법이 적용되어 부모의 메소드를 자식 클래스에서 상속 받아서 그 기능을 변경하는 작업이 된다. 즉, 오버라이딩(Overriding)은 부모의 인스턴스 메소드를 자식 클래스에서 상속된 그대로 사용하는 것이 아니라 자식 클래스에서 그 기능을 좀더 보완하거나 수정하기 위해 필요한 작업이다. 시그니쳐란 메소드의 이름과 파라미터를 합쳐 부르는 말이다

오버라이딩은 인스턴스 메소드에만 적용되며 static 메소드를 자식 클래스에서 다시 정의하면 해당 메소드가 부모의 메소드를 가리게(Hiding) 된다. 오버라이딩할 때는 대상이 되는 부모 메소드의 리턴타입, 이름, 파라미터가 동일하게 자식 클래스에서 정의되어야 하며, 리턴타입은 부모의 메소드가 리턴하는 타입과 동일하거나 서브타입(subtype)이면 된다.

여기서 서브타입(subtype)이란, 예를 들어, 부모 메소드가 리턴하는 타입이 Object 라면 자식 클래스에서 오버라이드하는 메소드는 그 리턴타입이 Object 의 하위 클래스 이름을 사용할 수 있다는 의미이다.


@Override Annotation

오버라이딩하기 위해 제정의하는 메소드에 사용할 수 있으며 문법적으로 요구되는 것은 아니다. @Override 애노테이션을 사용하면 컴파일 타임에 컴파일러가 오버라이딩 문법으로 @Override 애노테이션이 붙은 메소드를 검증해주기 때문에 오류 발견이 쉽게 되다. 오버라이딩할 때는 특별한 이유가 없다면 @Override 애노테이션를 사용하는 것이 좋다.


Hiding Methods ( static 메소드를 자식 클래스에서 다시 정의할 경우 )

부모 클래스에 있는 static 메소드를 자식 클래스에서 다시 정의하면 부모 클래스에 있는 메모드가 가려지게 된다(Hiding). 가려진 상태는 존재가 지워지거나 삭제된 것이 아니라 해당 메소드의 호출 환경(부모의 참조, 자식의 참조)에 따라서 2개의 메소드를 모두 사용할 수 있다는 의미이다. 이와는 달리 오버라이딩은 부모의 참조나 자식의 참조에 상관 없이 오버라이딩한 메소드가 호출된다.


오버라이딩과 접근 제한자 (Access Modifier)

부모 클래스에 있는 인스턴스 메소드를 자식 클래스에서 오버라이딩할 때 해당 메소드의 접근 제한자는 더 제한적이면 안된다. 예를 들어, 부모 클래스에 있는 인스턴스 메소드의 접근 제한자가 package-private(default) 라면 자식 클래스에 정의된 메소드는 public, protected, default(키워드 없음) 중에 하나이어야 한다.


상속된 메소드도 오버로드(Overload)할 수 있다

오버로드(Overload)는 한 클래스에 안에서 이름이 동일한 메소드를 다수개 정의하는 것이다. 메소드를 오버로드할 때는 메소드 이름이 동일한 또 하나의 메소드를 정의하면서 파라미터는 달라야 한다는 제약이 따른다. 즉 자바에서는 메소드를 식별할 때 내부적으로는 메소드 이름 뿐만 아니라 그 파라미터까지 동일한지 검사하여 파라미터까지 일치해야 완전히 동일한 메소드로 인식한다는 의미이다. 파라미터를 다르게 한다는 것은 파라미터의 갯수나 자료형이 다르거나 파라미터 배치 순서가 다르면 된다. 단지 변수명이 다르다고 해서 파라미터가 다르다고 인정하지는 않는다

오버로드(Overload)는 자바 입장에서는 완전히 다른 메소드로 취급되므로 오버라이딩이나 static 메소드의 가려지는 현상과는 전혀 무관하다. 호출할 때 파라미터에 따라서 적절한 메소드가 구별되어 호출된다


Method Overriding / Method Hiding 예제

public class OverridingHiding {

	public static void main(String[] args) {
		// 각 클래스에 동일한 static 메소드가 있더라도 구분되어 호출된다
		Car.printCompany();    // General Auto Co.LTD
		Truck.printCompany();  // General Auto Truck Division
		
		Car car = new Car("소나라", "General Auto", 3000);
		Truck truck = new Truck("Volvo", "General Auto",5000, 10);
		
		// 각 클래스의 인스턴스 참조를 이용하여 메소드를 호출한다(각 클래스의 메소드가 호출됨)
		car.printInfo();       // Car 클래스의 printInfo() 호출됨
		truck.printInfo();     // Truck의 printInfo() 호출됨
		
		// 각 클래스의 참조를 통해 static 메소드를 호출하면 각 클래스 안에 선언된 printCompany()가 호출됨
		car.printCompany();    // General Auto Co.LTD
		truck.printCompany();  // General Auto Truck Division
		
		// 참조를 부모형으로 캐스팅하여 메소드를 호출하더라도 자식이 오버라이딩한 메소드가 호출됨 
		Car c = (Car)truck;
		c.printInfo();         // Car의 참조를 통해 호출하지만 Truck의 printInfo()가 호출됨
	}
}

class Car {
	String carName;
	String company;
	int price;
	
	Car(String carName, String company, int price) {
		this.carName = carName;
		this.company = company;
		this.price = price;
	}
	
	public static void printCompany() {
		System.out.println("General Auto Co.LTD");

	}
	
	public void printInfo() {
		System.out.printf("%n차종:%s \t제조사:%s \t가격:%d \t", carName,company,price);
	}
}

class Truck extends Car {
	int maxLoad;
	
	Truck(String carName, String company, int price, int maxLoad) {
		super(carName, company, price);
		this.maxLoad = maxLoad;
	}
	// 부모 클래스의 printCompany() 메소드를 가린다(Hiding)
	public static void printCompany() {
		System.out.println("General Auto Truck Division");

	}
	
	@Override  // 부모의 메소드를 오버라이딩
	public void printInfo() {
		super.printInfo();
		System.out.printf("적재중량:%d %n", maxLoad);
	}
}


Overriding & Overload 예제

class Vector2D
{
	double x, y;
	
	// 생성자 오버로드 (Overload)
	public Vector2D() {}
	public Vector2D(double x, double y) {
		this.x = x;
		this.y = y;
	}
	
	// 원점으로부터의 거리를 구하는 메소드
	public double getLength() {
		return Math.sqrt(x*x + y*y);
	}
}

class Vector3D extends Vector2D
{
	double x, y, z;
	
	// 생성자 오버로드
	public Vector3D() {}
	public Vector3D(double x, double y) {
		super(x, y);
	}
	public Vector3D(double x, double y, double z) {
		this(x,y);
		this.z = z;
	}
	
	@Override  // 부모 클래스 인스턴스 메소드를 오버라이딩함
	public double getLength() {
		return Math.sqrt(x*x + y*y + z*z);
	}
	
	// 벡터의 스칼라 곱
	public Vector3D multiply(double num) {
		return new Vector3D(x*num, y*num, z*num);
	}
	
	// 벡터의 내적, 오버로드(이름은 동일하게, 파라미터는 다르게)
	public double multiply(Vector3D v) {
		return x*v.x + y*v.y + z*v.z;
	}
}