4 분 소요

자바 복습 및 정리 5P

다형성

객체 지향 프로그래밍의 핵심 개념 중 하나로,

같은 타입이나 인터페이스를 구현하는 다양한 클래스의 객체가 다양한 방식으로 동작할 수 있는 능력을 의미함.

코드의 재사용성과 유연성을 높일 수 있다.

컴파일타임 다형성 (Compile-time Polymorphism) - 메서드 오버로딩

메서드 오버로딩은 같은 이름의 메서드가 서로 다른 매개변수를 가지는 것을 말한다.

컴파일러는 메서드 호출 시 전달되는 인자의 개수나 타입을 기반으로 어떤 메서드가 호출되어야 하는지 결정한다.

런타임 다형성 (Runtime Polymorphism) - 메서드 오버라이딩

메서드 오버라이딩은 부모 클래스에서 정의된 메서드를 자식 클래스에서 동일한 이름과 시그니처로 재정의하는 것을 말한다.

이 때, 실제 객체의 타입에 따라 어떤 메서드가 호출되어야 하는지 런타임 중에 결정된다.

class Animal {
    void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Bark!");
    }
}

class Cat extends Animal {
    void makeSound() {
        System.out.println("Meow!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal cat = new Cat();
        
        dog.makeSound(); // "Bark!"
        cat.makeSound(); // "Meow!"
    }
}

위 코드에서 Animal 클래스의 makeSound 메서드는 자식 클래스인 Dog, Cat 에서 각각 오버라이딩 되었다.

dog, cat은 모두 Aimal 타입으로 선언되었지만,

실제 객체는 각각 Dog, Cat이다.

이로 인해 런타임에 올바른 오버라이딩된 메서드가 호출되어 다양한 동작을 수행할 수 있는 것이 바로 다형성의 예시이다.

참조 변수의 타입 변환

부모 클래스와 자식 클래스 사이에서 이루어지며, 업캐스팅과 다운캐스팅 형태로 나뉜다.

업캐스팅 (Upcasting)

부모 클래스 타입의 참조 변수로 자식 클래스의 객체를 참조하는 것을 말한다.

즉, 부모 클래스로부터 파생된 자식 클래스의 객체를 부모 클래스의 타입으로 다루는 것이다.

ParentClass parent = new ChildClass(); // 업캐스팅

다운캐스팅 (Downcasting)

업캐스팅된 객체를 다시 자식 클래스 타입으로 변환하는것을 의미한다.

다운캐스팅은 명시적으로 타입 캐스팅 연산자를 사용해야 한다.

ParentClass parent = new ChildClass();
ChildClass child = (ChildClass) parent; // 다운캐스팅

다운캐스팅은 주의가 필요하다.

실제로 업캐스팅된 객체가 해당 자식 클래스의 인스턴스인지 확인해야 하며, 그렇지 않으면 런타임 예외가 발생할 수 있다.

instanceof 연산자를 사용하여 확인할 수 있다. (instanceof 연산자 설명은 밑에서 확인할 수 있다.)

if (parent instanceof ChildClass) {
    ChildClass child = (ChildClass) parent; // 다운캐스팅
}
public class VehicleTest {
    public static void main(String[] args) {
        Car car = new Car();
        Vehicle vehicle = (Vehicle) car; // 상위 클래스 Vehicle 타입으로 변환(생략 가능)
        Car car2 = (Car) vehicle; // 하위 클래스 Car타입으로 변환(생략 불가능)
        MotorBike motorBike = (MotorBike) car; // 상속 관계가 아니므로 타입 변환 불가 -> 에러발생
    }
}

class Vehicle {
    String model;
    String color;
    int wheels;

    void startEngine() {
        System.out.println("시동 걸기");
    }

    void accelerate() {
        System.out.println("속도 올리기");
    }

    void brake() {
        System.out.println("브레이크!");
    }
}

class Car extends Vehicle {
    void giveRide() {
        System.out.println("다른 사람 태우기");
    }
}

class MotorBike extends Vehicle {
    void performance() {
        System.out.println("묘기 부리기");
    }
}

instanceof 연산자

객체의 타입을 확인하는 데 사용되는 연산자이다.

이 연산자는 주어진 객체가 특정 클래스나 인터페이스의 인스턴스인지 여부를 판단할 수 있다.

object instanceof Type

object는 확인하려는 객체를 나타내며, Type은 특정 클래스나 인터페이스 타입을 나타낸다.

결과 값은 true, false로 반환된다.

public class InstanceOfExample {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal instanceof Object); //true
        System.out.println(animal instanceof Animal); //true
        System.out.println(animal instanceof Bat); //false

        Animal cat = new Cat();
        System.out.println(cat instanceof Object); //true
        System.out.println(cat instanceof Animal); //true
        System.out.println(cat instanceof Cat); //true
        System.out.println(cat instanceof Bat); //false
    }
}

class Animal {};
class Bat extends Animal{};
class Cat extends Animal{};

인터페이스

추상화된 형식을 정의하는 역할을 하는 중요한 개념.

클래스와는 달리 메서드의 구현을 가지지 않으며, 메서드의 시그니처(이름, 매개변수, 반환 타입)만을 정의한다.

추상 클래스는 설계가 모두 끝나지 않은 미완성 설계도에 비유할 수 있다면,

인터페이스는 그보다 더 높은 추상성을 가지는 가장 기초적인 밑그림에 빗대어 표현할 수 있다.

일반 클래스와 다르게, 내부의 모든 필드가 public static final로 정의 되고,

static과 default 메서드 이외의 모든 메서드가 public abstract로 정의된다.

public interface InterfaceEx {
    public static final int rock =  1; // 인터페이스 인스턴스 변수 정의
    final int scissors = 2; // public static 생략
    static int paper = 3; // public & final 생략

    public abstract String getPlayingNum();
    void call() //public abstract 생략 
}

인터페이스의 특징은 다음과 같다.

추상화된 형식

클래스와 비슷하게 메서드와 상수를 가질 수 있지만, 메서드는 구현을 가지지 않는다.

즉, 인터페이스는 어떤 동작이 수행되어야 하는지만 정의하고, 실제 구현은 인터페이스를 수현하는 클래스에서 이루어 진다.

다중 상속

클래스는 하나의 클래스만 상속할 수 있지만, 인터페이스는 여러 인터페이스를 동시에 구현할 수 있다.

구현 강제

클래스가 특정 인터페이스를 구현한다면, 해당 인터페이스에 정의된 모든 메서드를 반드시 구현해야 한다.

이로써 일관된 동작을 보장할 수 있다.

인스턴스 생성 불가

인터페이스는 객체를 직접 생성할 수 없다.

즉, new InterfaceName(); 와 같은 방식으로는 객체를 직접 생성할 수 없다.

대신 인터페이스를 구현한 클래스의 객체를 생성하여 사용한다.

인터페이스 구현

class 클래스명 implements 인터페이스명 {
  ... // 인터페이스에 정의된 모든 추상메서드 구현
}

extends 키워드를 사용하는 클래스의 상속과 기본적으로 동일하지만,

구현하다라는 의미를 가진 implements 키워드를 사용한다.

interface Shape {
    void draw(); // 추상 메서드 선언
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a square");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle();
        Shape square = new Square();
        
        circle.draw(); // "Drawing a circle"
        square.draw(); // "Drawing a square"
    }
}
interface Animal { // 인터페이스 선언. public abstract 생략 가능.
	public abstract void cry();
} 

interface Pet {
	void play();
}

class Dog implements Animal, Pet { // Animal과 Pet 인터페이스 다중 구현
    public void cry(){ // 메서드 오버라이딩
        System.out.println("멍멍!"); 
    }

    public void play(){ // 메서드 오버라이딩
        System.out.println("원반 던지기");
    }
}

class Cat implements Animal, Pet { // Animal과 Pet 인터페이스 다중 구현
    public void cry(){
        System.out.println("야옹~!");
    }

    public void play(){
        System.out.println("쥐 잡기");
    }
}

public class MultiInheritance {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.cry();
        dog.play();
        cat.cry();
        cat.play();
    }
}

// 출력값
// 멍멍!
// 원반 던지기
// 야옹~!
// 쥐 잡기

댓글남기기