
자바에서 객체지향을 처음 공부할 때 가장 헷갈렸던 부분이 extends, implements, this, super였다.
문법은 외우기 쉬운데, 왜 이런 문법이 필요한지, 언제 써야 하는지가 명확하지 않으면 코드가 계속 꼬인다.
이 글은 문법 나열이 아니라, “내가 왜 여기서 헷갈렸는지 → 그래서 무엇을 기준으로 이해해야 하는지”를 중심으로 정리한다.
extends는 부모 클래스의 상태와 동작을 물려받는 것이다.
class Animal {
void eat() {}
}
class Dog extends Animal {
void bark() {}
}
여기서 핵심은 문법이 아니라 관계다.
👉 extends는 IS-A 관계를 표현할 때만 써야 한다.
implements는 “이 클래스가 이 역할을 수행할 수 있다”는 약속이다.
interface Flyable {
void fly();
}
class Bird implements Flyable {
@Override
public void fly() {}
}
여기서 Bird는:
👉 implements는 CAN-DO 관계다.
인터페이스 메서드는 작성할 때 public을 생략해도 된다.
interface Flyable {
void fly();
}
하지만 이건 실제로는 다음과 같다.
public abstract void fly();
그래서 구현 클래스에서는 반드시 public을 붙여야 한다.
class Bird implements Flyable {
public void fly() {} // O
}
class Bird implements Flyable {
void fly() {} // X
}
public너무 정상적인 상황이다.
interface Shape {
int area(int width, int height);
}
이 한 줄이 의미하는 계약은 명확하다.
“이 역할을 구현하는 클래스는 int 두 개를 받아 int 하나를 반환하는 area를 제공해야 한다”
public)파라미터 이름은 계약이 아니다.
Shape s = new Rectangle();
s.area(10, 5);
이 문장은 단순한 문법이 아니다.
👉 “구현이 아니라 역할에 의존하겠다”는 선언이다.
Shape s = new Triangle();
Shape s = new Rectangle();
이게 바로 다형성이고,
if-else를 없애는 객체지향의 핵심이다.
super는 부모 클래스가 아니라 부모 객체다.
super.필드super.메서드()super() → 부모 생성자 호출class Parent {
int x = 10;
}
class Child extends Parent {
int x = 20;
void print() {
System.out.println(super.x); // 10
}
}
자식 객체를 만들 때 부모가 먼저 완성돼야 한다.
class Child extends Parent {
Child() {
super(); // 생략돼 있어도 자동 호출
}
}
super()는 생성자의 첫 줄this();
this("kim");
this("kim", 20);
this()는 생성자 오버로딩을 위한 도구다.
class Person {
Person() {
this("guest", 0);
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
전달한 인자의 타입과 시그니처가 정확히 일치하는 생성자를
컴파일 타임에 선택한다
this(10); // int 생성자
this("kim"); // String 생성자
모호하면 컴파일 에러가 난다.
실제 흐름은 항상 이렇다.
this() → 다른 생성자 → super() → 부모 생성자
extends → 정체성 상속 (IS-A)implements → 역할 구현 (CAN-DO)super → 부모 객체 접근this() → 생성자 중복 제거용 도구