1.메서드 오버라이딩(Method Overriding)이란
메서드 오버라이딩이란 자바와 같은 객체 지향 프로그래밍에서 하위 클래스가 자신의 상위 클래스들, 또는 그 중 하나에 의해 이미 제공된 메서드를 특정한 형태로 재정의하는 것을 의미한다.
이는 동일한 시그니쳐(메서드 이름, 매개 변수의 개수와 데이터타입)와 리턴 타입이 같아야만 가능하다. 상위 클래스의 객체로 메서드를 호출한다면 상위 클래스 버전의 메서드가 실행되고, 하위 클래스의 객체로 메서드를 호출한다면 하위 클래스 버전의 메서드가 실행될 것이다.
//탈 것을 정의한 클래스
public class Vehicle {
//탈 것의 run 메서드를 정의
public void run(){
System.out.println("Run with Vehicle");
}
}
//오토바이 클래스는 Vehicle 클래스를 상속받고 있다.
public class Bike extends Vehicle{
//Vehicle의 run메서드를 재정의
@Override
public void run(){
System.out.println("Run with Bike");
}
}
//자전거 클래스는 Vehicle 클래스를 상속받고 있다.
public class Bicycle extends Vehicle{
//Vehicle의 run메서드를 재정의
@Override
public void run(){
System.out.println("Run with Bicycle");
}
}
위처럼 상위 클래스와 같은 시그니쳐와 리턴타입으로 메서드를 정의하면 오버라이딩이 된 것이다. @Override는 어노테이션으로 이 메서드는 상위 클래스의 메서드를 오버라이딩하였다는 것을 명시적으로 적어준 것이다. 시그니쳐와 리턴타입이 잘못되었을 경우 컴파일러가 알려줄 것이다.
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
Bike bike2 = new Bike();
Bicycle bicycle = new Bicycle();
vehicle2.run(); // Run with Vehicle 출력
bike.run(); // Run with Bike 출력
bicycle.run(); // Run with Bicycle 출력
}
이처럼 오버라이딩을 하면 Vehicle클래스의 메서드이지만 Vehicle의 하위 클래스인 Bike와 Bicycle 클래스에서 같은 메서드를 다른 형태로 재정의할 수 있다.
2. 다이나믹 메서드 디스패치(Dynamic Method Dispatch)또는 런타임 다형성(Run-time Polymorphism)
하위 클래스의 객체를 참조한 인스턴스가 상위 클래스 타입의 변수를 참조하고 있을 때 오버라이딩된 메서드의 호출은 런타임시에 결정된다. 메서드 호출 시 실행할 메서드는 인스턴스 타입에 따라 결정되기 때문이다. 오버라이딩된 메서드에 대한 호출이 런타임시에 확인하는 것을 다이나믹 메서드 디스패치 또는 런타임 다형성이라 한다.
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
Vehicle bike = new Bike(); //Vehicle클래스를 참조하는 Bike인스턴스 생성
Vehicle bicycle = new Bicycle(); //Vehicle클래스를 참조하는 Bicycle인스턴스 생성
ArrayList<Vehicle> list = new ArrayList<>();
list.add(vehicle);
list.add(bike);
list.add(bicycle);
// 츨력
// Run with Vehicle
// Run with Bike
// Run with Bicycle
for (Vehicle v : list) {
v.run();
}
}
상위 클래스 Vehicle을 참조하여 각기 다른 클래스의 인스턴스를 생성하였다. 실행시 실제 생성된 객체의 메서드를 실행한다.
3. 상위 클래스의 private메서드는 오버라이딩할 수 없다
public class Vehicle {
private void run(){
System.out.println("Run with Vehicle");
}
}
public class Bike extends Vehicle{
public void run(){
System.out.println("Run with Bike");
}
}
public class Main {
public static void main(String[] args) {
Vehicle bike = new Bike();
bike.run(); //컴파일 오류
Bike bike2 = new Bike();
bike2.run(); // Run with Bike 출력
}
}
Vechicle 클래스를 참조한 bike변수의 run 메서드는 오버라이딩되어 Run with Bike를 출력할 것 같지만, private 메서드는 자동으로 해당 클래스 외에서는 실행할 수 없게 되어있다. Bike 객체를 참조한 인스턴스는 Vehicle의 private메서드를 실행할 수 없기 때문에 컴파일 오류가 발생한다.
아래 Bike 클래스를 참조한 bike2는 run메서드를 실행할 경우 Run with Bike가 출력된다 하지만 이것은 실제 오버라이드된 것이 아니라 Vehicle 클래스의 run메서드와는 아예 관련이 없는 새로운 메서드가 생성된 것이다.
4. private 메서드를 오버라이딩하는 것을 허용한다면?
private메서드는 캡슐화 원칙에 따라 외부 클래스에서 해당 메서드를 알 수 없다. 오버라이딩이 허용된다면 보안 위험이 발생할 수 있다.
예를 들어 boolean hasCredentials()라는 상위클래스의 private 메서드가 있다고 가정한다. 이는 개인정보가 맞는지 확인하는 메서드이다. 이를 아래와 같이 오버라이딩을 한다.
boolean hasCredentials() (
return true;
)
어떠한 개인정보든 무조건 맞다는 결과를 보여줄 것이다.
5. final이 붙은 메서드는 오버라이딩할 수 없다.
fianl 예약어는 변수에 선언할 경우 변수의 값을 변경할 수 없게 하고, 클래스에 선언할 경우 상속을 막는 역할을 한다. 상위 클래스의 메서드에 붙일 경우 이 메서드는 변경될 수 없는 마지막이라는 의미이기 때문에 오버라이딩을 할 수 없다.
6. static 메서드는 오버라이딩할 수 없는 이유
오버라이딩은 다형성을 지원하는데 이는 런타임시 인스턴스가 참조하는 객체에 따라 실행되는 메서드의 버전이 달라진다. 하지만 static 메서드는 컴파일시 참조하는 타입에 따라 이미 어떠한 메서드가 실행될지 결정되기 때문에 다형성을 지원할 수 없다. 또한 static 메서드는 메모리 영역에 static영역에 속해있고 객체는 heap영역에 속해있다.
7. 하위 클래스에서 메서드의 접근 제어자(Access Modifier)가 더 제한적일 수 없는 이유
상위 클래스에서 public으로 선언 된 메서드를 오버라이딩할 경우 하위 클래스에서도 public이여야한다. 또한 상위 클래스에서 protected일 경우 하위 클래스에서 protected나 public이어야 한다. 이처럼 더 제한적일 수는 없다.
public class Vehicle {
public void run(){
System.out.println("Run with Vehicle");
}
}
public class Bike extends Vehicle{
private void run(){
System.out.println("Run with Bike");
}
}
public class Main {
public static void main(String[] args) {
Bike bike1 = new Bike();
Vehicle bike2 = bike1;
bike1.run(); // private이기 때문에 외부에서 실행될 수 없음
bike2.run(); // public이기 때문에 실행될 수 있음
}
}
실제로 컴파일될 수 없는 코드이지만 된다고 가정을 해보면 Bike 클래스를 참조하며 Bike 클래스 인스턴스를 생성한 경우 private메서드이니 외부에서 실행될 수 없다. 같은 인스턴스를 Vechicle 참조 변수에 대입하면 public 메서드이기 때문에 실행될 수 있다.
이는 객체지향의 기본적인 원칙이다. 하위 클래스는 상위 클래스를 참조할 자격을 갖춘 인스턴스이므로 적어도 같은 상위 클래스의 메서드를 제공해야한다. 그렇지 않으면 하위 클래스에서 더 제한적으로 변한 메서드를 사용하기 위해서는 상위 클래스의 인스턴스로 사용할 수 없게 된다. 다형성 또한 잃어버리게 된다.
참고
ko.wikipedia.org/wiki/%EB%A9%94%EC%86%8C%EB%93%9C_%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9
stackoverflow.com/questions/2000137/overriding-private-methods-in-java
'프로그래밍 언어 > Java' 카테고리의 다른 글
프로젝트 시작할 때 자바 버전 고르기 Java 8 vs Java 11 (0) | 2022.01.12 |
---|---|
[Effective Java] Item 1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2021.07.24 |
[JAVA 기초] Wrapper Class란 무엇인가 (0) | 2021.01.11 |
[JAVA 참고] JCP, JSR, TCK란 무엇인가 (0) | 2021.01.09 |
[JAVA 기초] JDK, JRE, JVM이란 무엇인가 (0) | 2021.01.09 |