[Java 개념노트 30] 메서드 오버라이딩 이해하기
안녕하세요. Java 개념노트 시리즈 서른 번째 글입니다.
지난 글에서는 부모 클래스의 필드와 메서드를 자식 클래스가 물려받는 상속에 대해 정리했습니다. 이번 글에서는 상속과 함께 자주 사용되는 메서드 오버라이딩에 대해 알아보겠습니다.
상속을 받으면 부모 클래스의 메서드를 그대로 사용할 수 있습니다. 하지만 자식 클래스마다 동작을 다르게 만들고 싶을 때도 있습니다. 이럴 때 사용하는 것이 바로 메서드 오버라이딩입니다.
1. 메서드 오버라이딩이란?
메서드 오버라이딩은 부모 클래스에서 물려받은 메서드를 자식 클래스에서 다시 정의하는 것입니다.
부모 클래스의 메서드를 그대로 사용할 수도 있지만, 자식 클래스의 상황에 맞게 다르게 동작하도록 바꾸고 싶을 때 오버라이딩을 사용합니다.
쉽게 말하면
오버라이딩은 부모에게 물려받은 메서드를 자식 클래스에서 다시 작성해서 자기 방식대로 동작하게 만드는 문법입니다.
2. 오버라이딩이 필요한 이유
예를 들어 모든 동물은 소리를 낸다고 생각해보겠습니다. 부모 클래스인 Animal에 sound() 메서드를 만들 수 있습니다.
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
하지만 강아지는 “멍멍”, 고양이는 “야옹”처럼 서로 다른 소리를 냅니다. 부모의 메서드를 그대로 사용하면 모든 동물이 같은 문장을 출력하게 됩니다.
이럴 때 자식 클래스에서 sound() 메서드를 다시 정의하면 각각의 동물에 맞는 동작을 만들 수 있습니다.
3. 오버라이딩 기본 예제
강아지 클래스에서 부모의 sound() 메서드를 오버라이딩해보겠습니다.
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound();
}
}
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍");
}
}
실행 결과는 다음과 같습니다.
멍멍
Dog 클래스는 Animal 클래스를 상속받았습니다. 그리고 부모의 sound() 메서드를 자식 클래스에서 다시 정의했습니다. 그래서 dog.sound()를 호출하면 부모의 메서드가 아니라 자식의 메서드가 실행됩니다.
4. @Override 어노테이션
오버라이딩할 때는 메서드 위에 @Override를 붙이는 것이 좋습니다.
@Override
void sound() {
System.out.println("멍멍");
}
@Override는 “이 메서드는 부모 메서드를 오버라이딩한 것입니다”라고 자바에게 알려주는 표시입니다.
만약 부모 클래스에 같은 메서드가 없거나, 메서드 이름이나 매개변수를 잘못 작성하면 컴파일 오류를 알려줍니다. 그래서 실수를 줄일 수 있습니다.
핵심 포인트
오버라이딩할 때는 @Override를 붙이는 습관을 들이는 것이 좋습니다. 오타나 잘못된 오버라이딩을 빨리 찾을 수 있습니다.
5. 오버라이딩 조건
오버라이딩은 아무렇게나 다시 작성할 수 있는 것이 아닙니다. 부모 메서드와 자식 메서드의 형태가 맞아야 합니다.
| 조건 | 설명 |
| 메서드 이름이 같아야 함 | 부모 메서드와 자식 메서드의 이름이 같아야 합니다. |
| 매개변수가 같아야 함 | 매개변수의 개수, 자료형, 순서가 같아야 합니다. |
| 반환타입이 같거나 호환되어야 함 | 기본적으로 부모 메서드와 같은 반환타입을 사용합니다. |
| 접근 범위를 더 좁게 만들 수 없음 | 부모보다 더 제한적인 접근 제어자로 바꿀 수 없습니다. |
| 부모 메서드가 상속 가능해야 함 | private, final 메서드는 오버라이딩할 수 없습니다. |
6. 이름과 매개변수가 같아야 한다
오버라이딩은 부모 메서드와 이름, 매개변수 형태가 같아야 합니다.
class Parent {
void print(String message) {
System.out.println(message);
}
}
class Child extends Parent {
@Override
void print(String message) {
System.out.println("자식 출력: " + message);
}
}
위 코드는 오버라이딩입니다. 부모와 자식의 메서드 이름이 print로 같고, 매개변수도 String message로 같습니다.
7. 매개변수가 다르면 오버라이딩이 아니다
메서드 이름은 같지만 매개변수가 다르면 오버라이딩이 아니라 오버로딩이 됩니다.
class Parent {
void print(String message) {
System.out.println(message);
}
}
class Child extends Parent {
void print(int number) {
System.out.println(number);
}
}
위 코드에서 Child의 print(int number)는 부모의 print(String message)를 오버라이딩한 것이 아닙니다. 매개변수 자료형이 다르기 때문에 새로운 메서드를 추가한 것입니다.
주의
오버라이딩은 부모 메서드를 다시 정의하는 것이고, 오버로딩은 같은 이름의 메서드를 매개변수 차이로 여러 개 만드는 것입니다.
8. 오버로딩과 오버라이딩 차이
오버로딩과 오버라이딩은 이름이 비슷해서 자주 헷갈립니다. 하지만 완전히 다른 개념입니다.
| 구분 | 오버로딩 | 오버라이딩 |
| 의미 | 같은 이름의 메서드를 여러 개 정의 | 부모 메서드를 자식 클래스에서 재정의 |
| 관계 | 같은 클래스 안에서도 가능 | 상속 관계에서 사용 |
| 매개변수 | 달라야 함 | 같아야 함 |
| 목적 | 다양한 입력 형태 처리 | 자식 클래스에 맞게 동작 변경 |
| 예시 | add(int, int), add(double, double) |
Animal.sound() → Dog.sound() |
9. 여러 자식 클래스에서 다르게 오버라이딩하기
하나의 부모 메서드를 여러 자식 클래스가 각자 다르게 오버라이딩할 수 있습니다.
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
dog.sound();
cat.sound();
}
}
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("야옹");
}
}
실행 결과는 다음과 같습니다.
멍멍
야옹
Dog와 Cat은 모두 Animal을 상속받았지만, sound() 메서드를 각자 다르게 오버라이딩했습니다.
10. 부모 메서드와 자식 메서드 함께 보기
오버라이딩을 이해할 때는 부모 메서드와 자식 메서드를 나란히 비교해보면 좋습니다.
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍");
}
}
메서드 이름은 sound로 같고, 매개변수도 없습니다. 따라서 Dog의 sound()는 부모의 sound()를 오버라이딩한 것입니다.
11. super로 부모 메서드 호출하기
자식 클래스에서 오버라이딩을 하더라도 부모 클래스의 메서드를 호출하고 싶을 때가 있습니다. 이때 super를 사용할 수 있습니다.
public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.sound();
}
}
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void sound() {
super.sound();
System.out.println("멍멍");
}
}
실행 결과는 다음과 같습니다.
동물이 소리를 냅니다.
멍멍
super.sound()는 부모 클래스의 sound() 메서드를 호출합니다. 그다음 자식 클래스의 추가 코드인 멍멍이 출력됩니다.
12. 접근 제어자는 더 좁게 만들 수 없다
오버라이딩할 때 자식 메서드의 접근 범위는 부모 메서드보다 좁아질 수 없습니다.
잘못된 예
class Parent {
public void print() {
System.out.println("부모");
}
}
class Child extends Parent {
@Override
private void print() {
System.out.println("자식");
}
}
부모 메서드는 public인데 자식 메서드를 private으로 바꾸면 접근 범위가 더 좁아집니다. 이런 경우 오류가 발생합니다.
올바른 예
class Parent {
public void print() {
System.out.println("부모");
}
}
class Child extends Parent {
@Override
public void print() {
System.out.println("자식");
}
}
오버라이딩할 때는 부모 메서드와 같거나 더 넓은 접근 범위를 사용해야 합니다.
13. private 메서드는 오버라이딩할 수 없다
부모 클래스의 private 메서드는 자식 클래스에서 직접 접근할 수 없습니다. 따라서 오버라이딩할 수 없습니다.
class Parent {
private void secret() {
System.out.println("부모 private 메서드");
}
}
class Child extends Parent {
void secret() {
System.out.println("자식 메서드");
}
}
위 코드에서 Child의 secret()는 부모의 private 메서드를 오버라이딩한 것이 아닙니다. 부모의 private 메서드는 자식에게 보이지 않기 때문입니다. 그냥 자식 클래스에 새 메서드를 만든 것에 가깝습니다.
14. final 메서드는 오버라이딩할 수 없다
부모 메서드에 final이 붙어 있으면 자식 클래스에서 오버라이딩할 수 없습니다.
class Parent {
final void print() {
System.out.println("변경할 수 없는 메서드");
}
}
class Child extends Parent {
@Override
void print() {
System.out.println("자식에서 변경");
}
}
위 코드는 오류가 발생합니다. final 메서드는 자식 클래스에서 다시 정의할 수 없기 때문입니다.
기억하기final 메서드는 오버라이딩을 막기 위해 사용합니다. 부모가 반드시 유지해야 하는 메서드라면 final을 붙일 수 있습니다.
15. static 메서드는 오버라이딩이 아니다
static 메서드는 객체가 아니라 클래스에 소속됩니다. 그래서 일반적인 의미의 오버라이딩 대상이 아닙니다.
class Parent {
static void print() {
System.out.println("Parent static");
}
}
class Child extends Parent {
static void print() {
System.out.println("Child static");
}
}
이 경우 자식 클래스에서 같은 이름의 static 메서드를 만들 수는 있지만, 이것은 오버라이딩이라기보다 메서드 숨김에 가깝습니다. 처음에는 “static 메서드는 오버라이딩 개념으로 다루지 않는다” 정도로 이해하면 됩니다.
16. 오버라이딩과 다형성 미리 보기
오버라이딩은 나중에 배울 다형성과 깊게 연결됩니다. 부모 타입 변수로 자식 객체를 가리킬 때, 오버라이딩된 메서드가 실행되는 특징이 있습니다.
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound();
}
}
class Animal {
void sound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍");
}
}
실행 결과는 다음과 같습니다.
멍멍
변수 타입은 Animal이지만 실제 객체는 Dog입니다. 그래서 실행 시점에는 Dog에서 오버라이딩한 sound()가 실행됩니다. 이 부분은 다형성에서 더 자세히 정리하겠습니다.
17. toString 메서드 오버라이딩 맛보기
자바의 모든 클래스는 Object 클래스를 상속받는다고 했습니다. Object 클래스에는 toString() 메서드가 있습니다. 이 메서드를 오버라이딩하면 객체를 출력할 때 보기 좋은 문자열을 만들 수 있습니다.
public class Main {
public static void main(String[] args) {
Member member = new Member("건", 20);
System.out.println(member);
}
}
class Member {
String name;
int age;
Member(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Member{name='" + name + "', age=" + age + "}";
}
}
실행 결과는 다음과 같습니다.
Member{name='건', age=20}
toString()을 오버라이딩했기 때문에 객체를 출력했을 때 원하는 형태의 문자열이 출력됩니다.
18. 오버라이딩 전체 예제
이번에는 상품 클래스를 예로 들어 오버라이딩을 사용해보겠습니다.
public class Main {
public static void main(String[] args) {
Product product = new Product("일반 상품", 10000);
DiscountProduct discountProduct = new DiscountProduct("할인 상품", 20000, 3000);
product.printInfo();
discountProduct.printInfo();
}
}
class Product {
String name;
int price;
Product(String name, int price) {
this.name = name;
this.price = price;
}
void printInfo() {
System.out.println("상품명: " + name + ", 가격: " + price);
}
}
class DiscountProduct extends Product {
int discountAmount;
DiscountProduct(String name, int price, int discountAmount) {
super(name, price);
this.discountAmount = discountAmount;
}
@Override
void printInfo() {
System.out.println("상품명: " + name);
System.out.println("정가: " + price);
System.out.println("할인 금액: " + discountAmount);
System.out.println("할인 후 가격: " + (price - discountAmount));
}
}
실행 결과는 다음과 같습니다.
상품명: 일반 상품, 가격: 10000
상품명: 할인 상품
정가: 20000
할인 금액: 3000
할인 후 가격: 17000
DiscountProduct는 부모인 Product의 printInfo()를 자신에게 맞게 다시 정의했습니다. 할인 상품은 일반 상품보다 출력해야 할 정보가 많기 때문에 오버라이딩을 사용했습니다.
19. 오버라이딩 사용 시 자주 하는 실수
메서드 오버라이딩을 처음 배울 때는 아래와 같은 실수를 자주 합니다.
| 실수 | 문제점 | 해결 방법 |
| 메서드 이름 오타 | 오버라이딩이 아니라 새 메서드가 될 수 있음 | @Override를 붙여 확인 |
| 매개변수 다르게 작성 | 오버라이딩이 아니라 오버로딩이 됨 | 부모 메서드와 매개변수를 같게 작성 |
| 접근 범위를 더 좁게 변경 | 컴파일 오류 발생 | 부모와 같거나 더 넓은 접근 범위 사용 |
| private 메서드를 오버라이딩한다고 착각 | private 메서드는 자식에게 보이지 않음 | 상속 가능한 메서드인지 확인 |
| final 메서드를 오버라이딩하려고 함 | final 메서드는 재정의 불가 | final 여부 확인 |
20. 직접 연습해보기
아래 코드를 직접 작성하고 실행해보세요.
public class Main {
public static void main(String[] args) {
Member member = new Member("일반회원");
VipMember vipMember = new VipMember("VIP회원", 10);
member.printBenefit();
vipMember.printBenefit();
}
}
class Member {
String name;
Member(String name) {
this.name = name;
}
void printBenefit() {
System.out.println(name + "의 기본 혜택입니다.");
}
}
class VipMember extends Member {
int discountRate;
VipMember(String name, int discountRate) {
super(name);
this.discountRate = discountRate;
}
@Override
void printBenefit() {
System.out.println(name + "의 VIP 혜택입니다.");
System.out.println("할인율: " + discountRate + "%");
}
}
실행 결과는 다음과 같습니다.
일반회원의 기본 혜택입니다.
VIP회원의 VIP 혜택입니다.
할인율: 10%
21. 이번 글 정리
이번 글에서는 부모 클래스의 메서드를 자식 클래스에서 다시 정의하는 메서드 오버라이딩에 대해 정리했습니다. 핵심 내용은 다음과 같습니다.
- 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 다시 정의하는 것이다.
- 오버라이딩은 상속 관계에서 사용된다.
- 자식 클래스는 부모 메서드를 자기 상황에 맞게 변경할 수 있다.
- 오버라이딩할 때는 메서드 이름과 매개변수가 같아야 한다.
@Override를 붙이면 오버라이딩 실수를 줄일 수 있다.- 매개변수가 다르면 오버라이딩이 아니라 오버로딩이 된다.
- 접근 제어자는 부모보다 더 좁게 만들 수 없다.
private메서드는 오버라이딩할 수 없다.final메서드는 오버라이딩할 수 없다.super.메서드명()으로 부모 메서드를 호출할 수 있다.- 오버라이딩은 다형성과 깊게 연결된다.
한 줄 요약
메서드 오버라이딩은 상속받은 부모 메서드를 자식 클래스에 맞게 다시 정의해서 다른 동작을 하게 만드는 객체지향 문법입니다.
다음 글 예고
다음 글에서는 [Java 개념노트 31] super 키워드 이해하기라는 주제로 부모 클래스의 필드, 메서드, 생성자를 호출하는 방법을 정리해보겠습니다.
GWDEVELBlog Java 개념노트 시리즈

'Computer Science > Java' 카테고리의 다른 글
| [Java] 개념노트 32 다형성이란 무엇인가? (0) | 2026.06.13 |
|---|---|
| [Java] 개념노트 31 super 키워드 이해하기 (0) | 2026.06.12 |
| [Java ] 개념노트29 상속이란 무엇인가? (0) | 2026.06.11 |
| [Java] 개념노트 28 패키지와 import 이해하기 (0) | 2026.06.10 |
| [Java]개념노트 27 캡슐화와 getter/setter 이해하기 (0) | 2026.06.10 |