Java 정리 (3)

Hazel·2025년 2월 5일

다형성

  • 실제 하나의 객체를 다양한 타입의 참조로 사용할 수 있음
  • 다양한 유형의 여러 객체를 하나의 공통된 타입으로 참조 가능
  • 상속 관계에 있을 때 조상 클래스 타입으로 자손 클래스의 객체를 참조 가능
  • 다양한 타입으로 참조하더라도 참조만 바뀔 뿐 실제 객체 자체(객체의 행위)는 변함이 없음
    • 다양한 타입으로 참조하더라도 → 스택 영역에 생성
    • 객체는 힙에 생성

업캐스팅(Upcasting)

  • 자식 객체를 부모 타입으로 변환하는 것
  • 자동 변환(묵시적 변환) 되며, 명시적으로 형변환할 필요 없음
  • 부모 클래스의 멤버(필드, 메서드)만 사용 가능
  • 하지만 오버라이딩된 메서드는 자식 클래스의 것이 실행됨 → 동적 바인딩
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() 실행 (동적 바인딩)
    }
}
----------------------[출력]------------------------
멍멍!
  • 업캐스팅 후 a.makeSound();가 실행되었을 때, 부모가 아닌 자식의 메서드가 실행
    → 이것이 바로 "동적 바인딩(Dynamic Binding)"

다운캐스팅(Downcasting)

  • 업캐스팅된 객체를 다시 원래의 자식 타입으로 변환하는 것
  • 명시적 형변환 필요 ((자식클래스))
  • 업캐스팅된 객체만 다운캐스팅 가능
  • 다운캐스팅하면 자식 클래스의 고유 메서드도 사용 가능
public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();  	// ✅ 업캐스팅
        // a.fetchBall();  		// ❌ 오류! Animal에는 fetchBall()이 없음

        Dog d = (Dog) a;  		// ✅ 다운캐스팅 (명시적 변환)
        d.makeSound();  		// ✅ Dog의 메서드 실행 (멍멍!)
    }
}
----------------------[출력]------------------------
멍멍!

동적 바인딩(Dynamic Binding)

  • 업캐스팅된 상태에서도, 오버라이딩된 메서드는 자식 클래스의 것이 실행됨
  • 컴파일 시점이 아닌, 실행 시점에서 어떤 메서드를 호출할지 결정됨
  • 업캐스팅 후 부모 타입으로 메서드를 호출해도 자식 클래스의 오버라이딩된 메서드가 실행됨
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()

스택(Stack)과 힙(Heap)의 개념

📌 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 객체]
  • Dog 객체는 new를 통해 힙(Heap) 메모리에 저장
  • Animal a 참조 변수는 스택(Stack) 메모리에 저장
  • 참조 변수 a는 Dog 객체를 가리키지만, Animal 타입으로 다뤄짐
  • a.makeSound(); 호출 시, 오버라이딩된 Dog의 메서드가 실행됨 (멍멍! 출력)

📌 다운캐스팅 후

스택(Stack) 영역          힙(Heap) 영역
────────────────────────────
Animal a  ────────▶   [Dog 객체]
Dog d     ────────▶   [Dog 객체]  (같은 객체를 참조)
  • Dog d = (Dog) a; → a가 참조하던 같은 Dog 객체를 d도 참조
    d.fetchBall(); 실행 가능 (강아지가 공을 가져옵니다. 출력)

추상 클래스

  • 추상 클래스는 객체를 직접 생성할 수 없는 클래스
  • 추상 메서드(Abstract Method)를 포함할 수 있음
  • 일반 필드(변수)와 메서드도 포함 가능
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();  						// ✅ "바둑이가 먹이를 먹습니다."
    }
}

추상 클래스 특징

  • 추상 메서드(abstract)는 반드시 자식 클래스에서 구현해야 함
  • 일반 필드, 일반 메서드도 가질 수 있음 (구현된 메서드 포함 가능)
  • 객체 생성 불가능 → 상속을 통해 사용해야 함
  • extends 키워드로 상속 → 단일 상속만 가능

인터페이스(Interface)

  • 인터페이스는 클래스가 아닌 "규격(설계도)"
  • 자바 8부터 default와 static 메서드 추가 가능 (구현 O)
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 (구현 X)
  • 모든 필드는 static final (상수)
  • implements 키워드로 구현
    다중 구현(다중 상속) 가능
  • 클래스는 인터페이스의 추상 메서드를 모두 구현(재정의)해야 객체 생성 가능
    → 모든 메서드를 구현하지 않으면 추상 클래스로 변경해야 함

추상 클래스 vs. 인터페이스

비교 항목추상 클래스 (Abstract Class)인터페이스 (Interface)
목적공통된 기능을 제공 (코드 재사용)규격(설계도) 제공 (기능 구현 강제)
객체 생성❌ 직접 생성 불가❌ 직접 생성 불가
상속 방식extends 사용 (단일 상속)implements 사용 (다중 구현 가능)
메서드✅ 추상 메서드 + ✅ 일반 메서드✅ 추상 메서드만 (Java 8 이후 default, static 추가 가능)
필드✅ 인스턴스 변수 선언 가능❌ 모든 필드는 public static final (상수)
생성자✅ 생성자 가짐 (super() 가능)❌ 생성자 없음
사용 예시동물(Animal) 클래스를 상속받아 개(Dog), 고양이(Cat) 구현TV 리모컨, 버튼 클릭 이벤트 등 표준화된 규칙 제공

추상 클래스 & 인터페이스 공통점 QQQQQQQQ

  • 특정 기능의 구현을 강제하고 싶을 때 사용 ....? 인터페이스는 자유를 주는거 아닌가?
  • 다형성 가능
  • 보다 추상화된 설계도에 의존하는 코드를 작성하고 싶을 때
  • 타입으로 사용 가능

정적(static) 메서드 & private 메서드

  • Java 8부터 인터페이스에 static 메서드와 default 메서드가 추가
  • Java 9부터 private 메서드도 추가되어 인터페이스 내부에서 코드 재사용 가능

정적(static) 메서드

  • 인터페이스 내부에 static 메서드를 선언할 수 있음
  • 클래스의 static 메서드와 사용 방식이 동일
  • 인터페이스 이름을 통해 접근 가능 (인터페이스명.메서드명())
  • 객체 생성 없이 사용 가능
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);
    }
}

private 메서드

  • Java 9부터 인터페이스에 private 메서드 추가 가능
  • 인터페이스 내부에서만 사용 가능 (외부에서 호출 불가능)
  • 반복되는 로직을 default 또는 static 메서드에서 분리하여 코드 재사용성 증가
  • static 키워드 사용 가능 (private static 메서드 선언 가능)
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("오류 발생");
    }
}
profile
이것저것 학습 기록장

0개의 댓글