class Animal {
void makeSound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("멍멍!");
}
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // ✅ 업캐스팅 (자동 변환)
a.makeSound(); // ✅ Dog의 makeSound() 실행 (동적 바인딩)
}
}
----------------------[출력]------------------------
멍멍!
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // ✅ 업캐스팅
// a.fetchBall(); // ❌ 오류! Animal에는 fetchBall()이 없음
Dog d = (Dog) a; // ✅ 다운캐스팅 (명시적 변환)
d.makeSound(); // ✅ Dog의 메서드 실행 (멍멍!)
}
}
----------------------[출력]------------------------
멍멍!
class Parent {
void show() {
System.out.println("부모 클래스");
}
}
class Child extends Parent {
@Override
void show() {
System.out.println("자식 클래스");
}
}
public class Main {
public static void main(String[] args) {
Parent p = new Child(); // ✅ 업캐스팅
p.show(); // ✅ 자식 클래스의 show() 실행 (동적 바인딩)
}
}
----------------------[출력]------------------------
자식 클래스
업캐스팅 & 다운캐스팅 & 동적 바인딩
개념 설명 변환 필요 여부 실행 결과 업캐스팅 자식 객체를 부모 타입으로 변환 자동 변환 (묵시적 변환) 부모 기능만 사용 가능 (단, 오버라이딩된 메서드는 자식 것 실행) 다운캐스팅 부모 타입을 다시 자식 타입으로 변환 명시적 변환 필요 ( (자식 클래스))자식 클래스의 고유 기능 사용 가능 동적 바인딩 업캐스팅된 상태에서도 오버라이딩된 메서드는 자식 클래스의 것이 실행됨 없음 (자동) 부모 타입이지만 자식 클래스의 메서드 실행
정적 바인딩 vs 동적 바인딩 비교
구분 정적 바인딩(Static Binding) 동적 바인딩(Dynamic Binding) 결정 시점 컴파일 시점(Compile Time) 실행 시점(Runtime) 적용 대상 메서드 오버로딩, static, private 메서드 메서드 오버라이딩(상속 관계) 속도 빠름 (컴파일 시 결정) 느림 (런타임에 결정) 업캐스팅 영향 부모의 메서드가 실행됨 자식 클래스의 오버라이딩된 메서드가 실행됨 예제 코드 static void show(),void show(int a),void show(double a)@Override void show()
📌 Java의 메모리 구조
| 영역 | 설명 |
|---|---|
| 스택(Stack) | 메서드 호출 시 생성되는 지역 변수(참조 변수 포함)가 저장됨 |
| 힙(Heap) | new 키워드로 생성된 객체(인스턴스)가 저장됨 |
| 메서드 영역(Method Area) | 클래스의 메서드, static 변수 등이 저장됨 |
📌 참조 변수는 스택(Stack)에 저장되지만, 객체 자체는 힙(Heap)에 저장됨.
📌 업캐스팅/다운캐스팅을 해도 힙의 객체는 변하지 않음!
class Animal {
void makeSound() {
System.out.println("동물이 소리를 냅니다.");
}
}
class Dog extends Animal {
void makeSound() {
System.out.println("멍멍!");
}
void fetchBall() {
System.out.println("강아지가 공을 가져옵니다.");
}
}
public class Main {
public static void main(String[] args) {
// ✅ 업캐스팅 (부모 타입으로 자식 객체 참조)
Animal a = new Dog(); // Dog 객체 생성 (힙 영역에 저장), 참조 변수 a (스택 영역에 저장)
a.makeSound(); // ✅ Dog의 오버라이딩된 메서드 호출 (멍멍!)
// a.fetchBall(); // ❌ 오류! (Animal 타입에는 fetchBall() 없음)
// ✅ 다운캐스팅을 통해 다시 Dog 타입으로 변환
Dog d = (Dog) a;
d.fetchBall(); // ✅ 이제 fetchBall() 사용 가능
}
}
📌 업캐스팅 시
스택(Stack) 영역 힙(Heap) 영역
────────────────────────────
Animal a ────────▶ [Dog 객체]
📌 다운캐스팅 후
스택(Stack) 영역 힙(Heap) 영역
────────────────────────────
Animal a ────────▶ [Dog 객체]
Dog d ────────▶ [Dog 객체] (같은 객체를 참조)
abstract class Animal { // ✅ 추상 클래스
String name;
Animal(String name) { // 일반 생성자
this.name = name;
}
abstract void makeSound(); // ✅ 추상 메서드 (구현 X)
void eat() { // 일반 메서드 (구현 O)
System.out.println(name + "가 먹이를 먹습니다.");
}
}
class Dog extends Animal {
Dog(String name) {
super(name);
}
@Override
void makeSound() { // ✅ 추상 메서드 구현
System.out.println(name + "가 멍멍 짖습니다.");
}
}
public class Main {
public static void main(String[] args) {
// Animal a = new Animal(); // ❌ 오류! (추상 클래스는 객체 생성 불가)
Dog d = new Dog("바둑이");
d.makeSound(); // ✅ "바둑이가 멍멍 짖습니다."
d.eat(); // ✅ "바둑이가 먹이를 먹습니다."
}
}
interface Animal { // ✅ 인터페이스
void makeSound(); // ✅ 추상 메서드 (abstract 생략 가능)
}
------------------------------------------------
class Dog implements Animal { // ✅ 인터페이스 구현
@Override
public void makeSound() {
System.out.println("멍멍!");
}
}
------------------------------------------------
public class Main {
public static void main(String[] args) {
Dog d = new Dog();
d.makeSound(); // ✅ "멍멍!"
}
}
| 비교 항목 | 추상 클래스 (Abstract Class) | 인터페이스 (Interface) |
|---|---|---|
| 목적 | 공통된 기능을 제공 (코드 재사용) | 규격(설계도) 제공 (기능 구현 강제) |
| 객체 생성 | ❌ 직접 생성 불가 | ❌ 직접 생성 불가 |
| 상속 방식 | extends 사용 (단일 상속) | implements 사용 (다중 구현 가능) |
| 메서드 | ✅ 추상 메서드 + ✅ 일반 메서드 | ✅ 추상 메서드만 (Java 8 이후 default, static 추가 가능) |
| 필드 | ✅ 인스턴스 변수 선언 가능 | ❌ 모든 필드는 public static final (상수) |
| 생성자 | ✅ 생성자 가짐 (super() 가능) | ❌ 생성자 없음 |
| 사용 예시 | 동물(Animal) 클래스를 상속받아 개(Dog), 고양이(Cat) 구현 | TV 리모컨, 버튼 클릭 이벤트 등 표준화된 규칙 제공 |
추상 클래스 & 인터페이스 공통점 QQQQQQQQ
interface Utility {
static int add(int a, int b) { // ✅ 정적 메서드
return a + b;
}
}
public class Main {
public static void main(String[] args) {
int result = Utility.add(10, 20); // ✅ 인터페이스명.메서드명() 으로 접근
System.out.println("결과: " + result);
}
}
interface Logger {
default void logInfo(String message) {
log("[INFO] " + message); // ✅ private 메서드 호출
}
default void logError(String message) {
log("[ERROR] " + message); // ✅ private 메서드 호출
}
private void log(String message) { // ✅ private 메서드
System.out.println(message);
}
}
public class Main {
public static void main(String[] args) {
Logger logger = new Logger() {}; // 익명 클래스 사용 (인터페이스 직접 구현)
logger.logInfo("시스템 시작");
logger.logError("오류 발생");
}
}