인스턴스 메서드
슈퍼클래스의 인스턴스 메서드와 서명(이름, 매개변수의 수와 타입)과 반환 타입이 동일한 서브클래스의 인스턴스 메서드는 슈퍼클래스의 메서드를 재정의합니다.
서브클래스가 메서드를 재정의하는 기능을 사용하면 클래스가 동작이 “충분히 유사한” 슈퍼클래스로부터 상속받은 다음 필요에 따라 동작을 수정할 수 있습니다. 재정의 메서드는 재정의하는 메서드와 이름, 매개변수 수 및 타입, 반환 타입이 동일합니다. 재정의 메서드는 재정의된 메서드가 반환한 타입의 하위 타입을 반환할 수도 있습니다. 이 하위 유형을 공변량 반환 타입이라고 합니다.
메서드를 재정의할 때는 컴파일러에 슈퍼클래스의 메서드를 재정의할 것을 지시하는 @Override 어노테이션을 사용할 수 있습니다. 어떤 이유로 컴파일러가 슈퍼클래스 중 하나에 메서드가 존재하지 않는다고 감지하면 오류가 발생합니다. @Override에 대한 자세한 내용은 어노테이션 섹션을 참조하세요.
정적 메서드
서브클래스가 슈퍼클래스의 정적 메서드와 동일한 서명을 가진 정적 메서드를 정의하는 경우, 서브클래스의 메서드는 슈퍼클래스의 메서드를 숨깁니다.
정적 메서드를 숨기는 것과 인스턴스 메서드를 재정의하는 것의 차이점은 중요한 의미를 갖습니다:
-
재정의된 인스턴스 메서드의 버전이 호출되는 것은 서브클래스에 있는 버전입니다.
-
호출되는 숨겨진 정적 메서드의 버전은 슈퍼클래스에서 호출되는지 아니면 서브클래스에서 호출되는지에 따라 달라집니다.
-
두 개의 클래스가 포함된 예제를 살펴봅시다. 첫 번째 클래스는 인스턴스 메서드 하나와 정적 메서드 하나를 포함하는
Animal입니다:
public class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
public void testInstanceMethod() {
System.out.println("The instance method in Animal");
}
}두 번째 클래스인 Animal의 서브클래스는 Cat입니다:
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
public void testInstanceMethod() {
System.out.println("The instance method in Cat");
}
public static void main(String[] args) {
Cat myCat = new Cat();
Animal myAnimal = myCat;
Animal.testClassMethod();
myAnimal.testInstanceMethod();
}
}Cat 클래스는 Animal의 인스턴스 메서드를 재정의하고 Animal의 정적 메서드를 숨깁니다. 이 클래스의 메인 메서드는 Cat의 인스턴스를 생성하고 클래스에서는 testClassMethod를, 인스턴스에서는 testInstanceMethod를 호출합니다.
이 프로그램의 출력은 다음과 같습니다:
The static method in Animal
The instance method in Cat약속한 대로, 호출되는 숨겨진 정적 메서드의 버전은 슈퍼클래스에 있는 버전이고, 호출되는 재정의된 인스턴스 메서드의 버전은 서브클래스에 있는 버전입니다.
인터페이스 메서드
인터페이스의 기본 메서드와 추상 메서드는 인스턴스 메서드처럼 상속됩니다. 그러나 클래스 또는 인터페이스의 수퍼타입이 동일한 서명을 가진 여러 기본 메서드를 제공하는 경우 Java 컴파일러는 이름 충돌을 해결하기 위해 상속 규칙을 따릅니다. 이러한 규칙은 다음 두 가지 원칙에 따라 결정됩니다.
- 인스턴스 메서드는 인터페이스 기본 메서드보다 선호됩니다.
다음 클래스와 인터페이스를 고려하세요:
public class Horse {
public String identifyMyself() {
return "I am a horse.";
}
}
public interface Flyer {
default public String identifyMyself() {
return "I am able to fly.";
}
}
public interface Mythical {
default public String identifyMyself() {
return "I am a mythical creature.";
}
}
public class Pegasus extends Horse implements Flyer, Mythical {
public static void main(String... args) {
Pegasus myApp = new Pegasus();
System.out.println(myApp.identifyMyself());
}
}Pegasus.identifyMyself() 메서드는 I am a horse 문자열을 반환합니다.
- 다른 후보에 의해 이미 재정의된 메서드는 무시됩니다. 이러한 상황은 슈퍼타입이 공통 조상을 공유할 때 발생할 수 있습니다.
다음 인터페이스와 클래스를 고려해 보세요:
public interface Animal {
default public String identifyMyself() {
return "I am an animal.";
}
}
public interface EggLayer extends Animal {
default public String identifyMyself() {
return "I am able to lay eggs.";
}
}
public interface FireBreather extends Animal { }
public class Dragon implements EggLayer, FireBreather {
public static void main (String... args) {
Dragon myApp = new Dragon();
System.out.println(myApp.identifyMyself());
}
}Dragon.identifyMyself() 메서드는 I am able to lay eggs라는 문자열을 반환합니다.
두 개 이상의 독립적으로 정의된 기본 메서드가 충돌하거나 기본 메서드가 추상 메서드와 충돌하는 경우 Java 컴파일러는 컴파일러 오류를 생성합니다. 수퍼타입 메서드를 명시적으로 재정의해야 합니다.
이제 날 수 있는 컴퓨터 제어 자동차에 대한 예를 생각해 보겠습니다. 동일한 메서드(startEngine())에 대한 기본 구현을 제공하는 두 개의 인터페이스(OperateCar 및 FlyCar)가 있습니다:
public interface OperateCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}
public interface FlyCar {
// ...
default public int startEngine(EncryptedKey key) {
// Implementation
}
}OperateCar와 FlyCar를 모두 구현하는 클래스는 startEngine() 메서드를 재정의해야 합니다. super 키워드를 사용하여 기본 구현 중 하나를 호출할 수 있습니다.
public class FlyingCar implements OperateCar, FlyCar {
// ...
public int startEngine(EncryptedKey key) {
FlyCar.super.startEngine(key);
OperateCar.super.startEngine(key);
}
}super 앞의 이름(이 예에서는 FlyCar 또는 OperateCar)은 호출된 메서드의 기본값을 정의하거나 상속하는 직접 슈퍼인터페이스를 참조해야 합니다. 이러한 형태의 메서드 호출은 동일한 서명을 가진 기본 메서드를 포함하는 여러 구현된 인터페이스를 구별하는 데만 국한되지 않습니다. super 키워드를 사용하여 클래스와 인터페이스 모두에서 기본 메서드를 호출할 수 있습니다.
클래스에서 상속된 인스턴스 메서드는 추상 인터페이스 메서드를 재정의할 수 있습니다. 다음 인터페이스와 클래스를 고려해 보세요:
public interface Mammal {
String identifyMyself();
}
public class Horse {
public String identifyMyself() {
return "I am a horse.";
}
}
public class Mustang extends Horse implements Mammal {
public static void main(String... args) {
Mustang myApp = new Mustang();
System.out.println(myApp.identifyMyself());
}
}Mustang.identifyMyself() 메서드는 문자열 I am a horse를 반환합니다. Mustang 클래스는 Horse 클래스에서 identifyMyself() 메서드를 상속하고, 이 메서드는 Mammal 인터페이스에서 같은 이름의 추상 메서드를 재정의합니다.
참고: 인터페이스의 정적 메서드는 상속되지 않습니다.
수정자
재정의된 메서드의 접근 지정자는 재정의된 메서드보다 더 많은 접근을 허용할 수 있지만 더 적은 접근은 허용하지 않을 수 있습니다. 예를 들어, 수퍼클래스의 protected 인스턴스 메서드는 서브클래스에서 public으로 만들 수 있지만 private으로 만들 수는 없습니다.
슈퍼클래스의 인스턴스 메서드를 서브클래스의 정적 메서드로 변경하거나 그 반대로 변경하려고 하면 컴파일 타임 오류가 발생합니다.
요약
다음 표는 슈퍼클래스의 메서드와 동일한 서명을 가진 메서드를 정의할 때 어떤 일이 발생하는지 요약한 것입니다.
| Superclass Instance Method | Superclass Static Method | |
|---|---|---|
| Subclass Instance Method | Overrides | Generates a compile-time error |
| Subclass Static Method | Generates a compile-time error | Hides |
Note: 서브클래스에서는 슈퍼클래스로부터 상속된 메서드를 오버로드할 수 있습니다. 이러한 오버로드된 메서드는 슈퍼클래스 인스턴스 메서드를 숨기거나 재정의하지 않습니다. —서브클래스에 고유한 새로운 메서드입니다.