1. 기본 클래스 메서드를 호출하기 위한 super 사용
서브클래스를 만들다 보면, 특히 메서드나 필드를 오버라이드했거나 “섀도잉(숨김)”한 경우 기본 클래스의 필드나 메서드에 접근할 필요가 생길 때가 있습니다. 이를 위해 Java에는 특별한 키워드인 super가 있습니다.
비유하자면, super는 서브클래스에서 부모에 정의된 것을 명시적으로 참조하고 싶을 때 누르는 “엄마, 도와줘!” 버튼과 같습니다.
예를 들어 Animal 클래스에 eat() 메서드가 있고, 이 메서드는 단순히 "동물이 먹는다"를 출력한다고 해봅시다. 그런데 Cat 클래스에서는 고양이가 먼저 자기 행동(예: 야옹)을 하고 나서, 그래도 표준의 “동물이 먹는다”도 실행되길 원할 수 있습니다. 바로 이때 super.eat()가 유용합니다.
서브클래스에서 메서드를 오버라이드하되, 그 안에서 여전히 기본 클래스의 구현을 호출하고 싶다면 super.imyaMetoda()를 사용하세요.
예시: 동작 확장하기
class Animal {
void eat() {
System.out.println("동물이 먹는다");
}
}
class Cat extends Animal {
@Override
void eat() {
System.out.println("고양이가 음식을 냄새 맡는다...");
super.eat(); // Animal의 eat() 메서드를 호출
System.out.println("고양이가 만족스럽게 골골거린다");
}
}
어떻게 동작하나요?
- Cat 타입 객체에서 eat()를 호출하면, 먼저 Cat.eat()의 코드, 즉 자신의 메서드가 실행됩니다.
- 그 메서드 안에서 super.eat(), 즉 부모 클래스 Animal의 구현을 명시적으로 호출합니다.
- 이렇게 하면 “부모의” 동작을 잊지 않으면서 추가 동작을 덧붙일 수 있습니다.
실습: 애플리케이션에서 사용하기
우리의 학습 애플리케이션에 기본 클래스 Animal과 서브클래스 Dog, Cat이 있다고 합시다. 동물을 먹일 때 공통 동작(예: 포만감 증가)과 각 동물별 특화 동작이 모두 수행되도록 하고 싶습니다.
class Animal {
int satiety = 0;
void eat() {
satiety += 10;
System.out.println("동물이 먹는다. 포만감: " + satiety);
}
}
class Dog extends Animal {
@Override
void eat() {
System.out.println("개가 밥 먹기 전에 꼬리를 흔든다");
super.eat();
}
}
이제 dog.eat()를 호출하면 두 메시지가 모두 출력되고, satiety도 올바르게 증가합니다.
2. 기본 클래스 필드에 접근하기 위해 super 사용
서브클래스에서 부모 클래스와 같은 이름의 필드를 선언했다면, 그 필드는 부모의 필드를 “가립니다(섀도잉)”. 가끔은 기본 클래스의 원래 필드에 접근해야 할 때가 있는데 — 이럴 때 super.imyaPolya가 필요합니다.
예시: 필드 섀도잉
class Animal {
String name = "동물";
}
class Cat extends Animal {
String name = "고양이";
void printNames() {
System.out.println("Cat의 이름: " + name);
System.out.println("Animal의 이름: " + super.name);
}
}
new Cat().printNames(); 호출은 다음을 출력합니다:
Cat의 이름: 고양이
Animal의 이름: 동물
실무에서는 특별한 필요가 없다면 필드를 “가리는” 것을 권장하지 않지만, 이런 가능성이 있다는 것은 알아둘 가치가 있습니다.
3. super(...)로 기본 클래스의 생성자 호출
상속 계층에서 객체는 어떻게 생성될까요?
서브클래스의 객체를 만들면, 먼저 기본 클래스의 생성자가 호출되고 그다음에 서브클래스의 생성자가 호출됩니다. 이렇게 해야 모든 필드가 올바르게 초기화되는데, 서브클래스는 부모로부터 상태의 일부를 “상속”받기 때문입니다.
기본 클래스 생성자 명시적 호출
기본 클래스에 매개변수가 없는 생성자가 있다면 간단합니다. Java가 서브클래스 생성자를 실행하기 전에 자동으로 그것을 호출합니다. 하지만 부모에 매개변수 없는 생성자가 없다면, 반드시 super(...)로 필요한 생성자를 명시적으로 호출해야 합니다.
예시:
class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("동물이 생성됨: " + name);
}
}
class Cat extends Animal {
Cat(String name) {
super(name); // 필수! 매개변수 없는 Animal() 생성자가 없음
System.out.println("고양이가 생성됨: " + name);
}
}
new Cat("무르카") 호출은 다음을 출력합니다:
동물이 생성됨: 무르카
고양이가 생성됨: 무르카
중요: super(...)를 통한 부모 생성자 호출은 서브클래스 생성자의 첫 번째 줄이어야 합니다. 이 호출보다 앞에 다른 코드를 작성하면 컴파일러가 불만을 표시하며 이를 상기시켜 줄 것입니다.
명시적으로 호출하지 않으면?
부모에 매개변수가 있는 생성자만 있고, 이를 super(...)로 명시적으로 호출하지 않으면, 컴파일러는 오류를 발생시킵니다: "constructor Animal in class Animal cannot be applied to given types".
4. 유용한 뉘앙스
super는 언제 사용할까요?
동작을 대체하지 않고 확장하고 싶을 때.
가끔은 메서드의 동작을 완전히 바꾸기보다, 부모의 로직 앞이나 뒤에 무언가를 덧붙이고 싶을 수 있습니다. 이런 경우 오버라이드된 메서드의 본문에서 super.imyaMetoda()를 사용하세요.
상속된 필드 초기화를 위해.
부모에 반드시 초기화해야 하는 필드(예: 동물의 이름)가 있다면, super(...)로 부모 생성자를 필요한 매개변수와 함께 호출해야 합니다.
가려진 필드/메서드에 접근하려고 할 때.
무슨 이유로든 부모의 필드나 메서드를 “가렸는데”, 그래도 그것에 접근해야 한다면 super.imyaPolya 또는 super.imyaMetoda()를 사용하세요.
super 사용의 제한 및 특징
- super(...)를 통한 부모 생성자 호출은 생성자 안에서만 그리고 첫 번째 줄에서만 할 수 있습니다.
- 서브클래스의 생성자 외부에서 부모 생성자를 호출할 수는 없습니다.
- super(...)를 명시적으로 호출하지 않으면, Java는 부모의 매개변수 없는 생성자를(존재한다면) 호출하려고 시도합니다.
- super는 정적 메서드에서 사용할 수 없습니다 — 인스턴스 메서드와 생성자에서만 사용할 수 있습니다.
- 부모의 메서드나 필드가 private라면, super로도 접근할 수 없습니다.
5. 연습 예제
예제 1. super로 메서드 확장
class Animal {
void makeSound() {
System.out.println("동물이 소리를 낸다");
}
}
class Dog extends Animal {
@Override
void makeSound() {
super.makeSound(); // 먼저 표준 동작 수행
System.out.println("개가 짖는다: 멍멍!");
}
}
예제 2. 기본 클래스 생성자 호출
class Vehicle {
String brand;
Vehicle(String brand) {
this.brand = brand;
System.out.println("차량: " + brand);
}
}
class Car extends Vehicle {
int year;
Car(String brand, int year) {
super(brand); // 부모 생성자 호출
this.year = year;
System.out.println("자동차 " + brand + ", 연식: " + year);
}
}
Car car = new Car("Toyota", 2023);
// 출력:
// 차량: Toyota
// 자동차 Toyota, 연식: 2023
예제 3. 전형적인 실수: super(...) 호출을 잊음
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
Cat() {
// super(); // 오류! 매개변수 없는 Animal() 생성자가 없음
// super(name)을 명시적으로 호출해야 한다
super("이름 없는 고양이");
}
}
6. super를 사용할 때의 흔한 실수
오류 1: super(...)를 생성자의 첫 줄에서 호출하지 않음.
Java는 super(...)를 통한 부모 생성자 호출이 서브클래스 생성자의 첫 번째 줄이어야 한다고 엄격히 요구합니다. 이 호출 전에 무엇인가를 하려고 하면(예: 메시지 출력), 컴파일러가 오류를 보고합니다.
오류 2: 부모에 알맞은 생성자가 없음.
기본 클래스에 매개변수 없는 생성자가 없는데, super(...)로 다른 생성자를 호출하지 않았다면, 컴파일러는 기본 호출을 생성할 수 없으므로 오류를 보고합니다.
오류 3: super로 부모의 private 멤버에 접근하려 시도함.
super는 부모의 private 필드나 메서드에 마법처럼 접근 권한을 주지 않습니다. private로 선언된 것은 서브클래스에서 접근할 수 없습니다.
오류 4: 이해 없이 필드/메서드를 섀도잉함.
서브클래스에서 부모와 같은 이름의 필드나 메서드를 선언해 놓고 이를 잊어버리면, 예상치 못한 결과가 발생할 수 있습니다. 이런 경우 부모 멤버에 접근하려면 항상 super를 통해야 한다는 점을 기억하세요.
오류 5: 정적 메서드에서 super 사용.
정적 메서드에서는 super를 사용할 수 없습니다. 정적 메서드는 특정 객체에 속하지 않기 때문입니다.
GO TO FULL VERSION