다형성 과 추상화

박대운·2022년 11월 9일
0

Java

목록 보기
3/9

다형성

자바 프로그래밍에서 다형성은 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미합니다.

좀 더 구체적으로 이야기하면, 상위 클래스 타입의 참조변수를 통해서 하위 클래스의 객체를 참조할 수 있도록 허용한 것이라 할 수 있습니다.

예시)

//참조변수의 다형성 예시

class Friend {
    public void friendInfo() {
        System.out.println("나는 당신의 친구입니다.");
    }
}

class BoyFriend extends Friend {
   
    public void friendInfo() {
        System.out.println("나는 당신의 남자친구입니다.");
    }
}

class GirlFriend extends Friend {
    
    public void friendInfo() {
        System.out.println("나는 당신의 여자친구입니다.");
    }
}

public class FriendTest {

    public static void main(String[] args) {
        Friend friend = new Friend(); // 객체 타입과 참조변수 타입의 일치
        BoyFriend boyfriend = new BoyFriend();
        Friend girlfriend = new GirlFriend(); // 객체 타입과 참조변수 타입의 불일치

        friend.friendInfo();
        boyfriend.friendInfo();
        girlfriend.friendInfo();
    }
}

// 출력값
나는 당신의 친구입니다.
나는 당신의 남자친구입니다.
나는 당신의 여자친구입니다.

상속받은 클래스에서 객체를 생성하는데 참조변수 GirlFriend를 사용하지않고, 상위클래스 Friend를 사용할 수 있다. GirlFriend의 멤버가 Friend의 멤버보다 많기 때문입니다.

참조변수의 타입 변환

참조 변수의 타입 변환은 다르게 설명하면 사용할 수 있는 멤버의 개수를 조절하는 것을 의미합니다.

타입 변환을 위해서는 다음의 세 가지 조건을 충족해야 합니다.

1. 서로 상속관계에 있는 상위 클래스 - 하위 클래스 사이에만 타입 변환이 가능합니다.
2. 하위 클래스 타입에서 상위 클래스 타입으로의 타입 변환(업캐스팅)은 형변환 연산자(괄호)를 생략할 수 있습니다.
3. 반대로 상위 클래스에서 하위 클래스 타입으로 변환(다운캐스팅)은 형변환 연산자(괄호)를 반드시 명시해야합니다.

예시)

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 연산자

instanceof 연산자는 앞서 배웠던 참조변수의 타입 변환, 즉 캐스팅이 가능한 지 여부를 boolean 타입으로 확인할 수 있는 자바의 문법요소입니다.

참조_변수 instanceof 타입

만약 참조_변수 instanceof 타입을 입력했을 때 리턴 값이 true가 나오면 참조 변수가 검사한 타입으로 타입 변환이 가능하며, 반대로 false가 나오는 경우에는 타입 변환이 불가능합니다.

만약에 참조 변수가 null인 경우에는 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{};

Cat 객체를 예로 들어보면, 생성된 객체는 Animal 타입으로 선언되어있지만 다형적 표현 방법에 따라 Object와 Animal 타입으로도 선언될 수 있다는 점을 확인하실 수 있습니다.

이렇듯 소스 코드가 길어지는 등 일일이 생성 객체의 타입을 확인하기가 어려운 상황에서 instanceof 연산자는 형변환 여부를 확인하여 에러를 최소화하는 매우 유용한 수단이 될 수 있습니다.

추상화

자바에서의 추상화는 객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 의미합니다.

상속이 하위 클래스를 정의하는데 상위 클래스를 사용하는 것이라고 한다면 추상화는 반대로 기존 클래스들의 공통적인 요소들을 뽑아서 상위 클래스를 만들어 내는 것이라고 할 수 있습니다.

자바에서는 주로 추상 클래스와 인터페이스라는 문법 요소를 사용해서 추상화를 구현합니다.

abstract 제어자

abstract는 주로 클래스와 메서드를 형용하는 키워드로 사용되는데, 메서드 앞에 붙은 경우를 ‘추상 메서드(abstract method)’, 클래스 앞에 붙은 경우를 ‘추상 클래스(abstract class)’라 각각 부릅니다.
어떤 클래스에 추상 메서드가 포함되어있는 경우 해당 클래스는 자동으로 추상 클래스가 됩니다.

예시)

abstract class AbstractExample { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
	abstract void start(); // 메서드 바디가 없는 추상메서드
}

추상 메서드는 충분히 구체화되지 않은 ‘미완성 메서드’이며, 미완성 메서드를 포함하는 클래스는 ‘미완성 클래스'를 의미하는 추상 클래스가 됩니다.

추상클래스

추상 클래스는 상속 관계에 있어 새로운 클래스를 작성하는데 매우 유용합니다.

abstract class Animal {
	public String kind;
	public abstract void sound();
}

class Dog extends Animal { // Animal 클래스로부터 상속
	public Dog() {
		this.kind = "포유류";
	}

	public void sound() { // 메서드 오버라이딩 -> 구현부 완성
		System.out.println("멍멍");
	}
}

class Cat extends Animal { // Animal 클래스로부터 상속
	public Cat() {
		this.kind = "포유류";
	}

	public void sound() { // 메서드 오버라이딩 -> 구현부 완성
		System.out.println("야옹");
	}
}

class DogExample {       
    public static void main(String[] args) throws Exception {
       Animal dog = new Dog();
       dog.sound();

       Cat cat = new Cat();
       cat.sound();
    }
 }

// 출력값
멍멍
야옹

Dog 와 Cat 클래스가 Animal을 상속 받았고 그 아래 sound() 메서드를 오버라이딩해서 구현을 해주었다. 이렇듯 추상클래스에서는 정의만 해주고 상속받은 클래스에서 구현을 해주는 것이 추상클래스이다. 서로 다른 변경 상황이 생길 때 유연하게 유지보수 및 대처를 할 수 있다.

추상 클래스는 자바 객체지향 프로그래밍의 마지막 기둥인 추상화를 구현하는데 핵심적인 역할을 수행합니다.
추상화를 한마디로 정리하면 “객체의 공통적인 속성과 기능을 추출하여 정의하는 것”이라 정리할 수 있습니다.

만약 여러 사람이 함께 개발하는 경우, 공통된 속성과 기능임에도 불구하고 각각 다른 변수와 메서드로 정의되는 경우 발생할 수 있는 오류를 미연에 방지할 수 있습니다.

결론적으로 구체화에 반대되는 개념으로 추상화를 생각해보면, 상속계층도의 상층부에 위치할 수록 추상화의 정도가 높고 그 아래로 내려갈수록 구체화된다고 정리해 볼 수 있습니다.

다른 말로, 상층부에 가까울수록 더 공통적인 속성과 기능들이 정의되어 있다고 생각할 수 있습니다.

final 키워드

영어로 ‘최종의', ‘마지막의'라는 뜻을 가지고 있는 final 키워드는 필드, 지역 변수, 클래스 앞에 위치할 수 있으며 그 위치에 따라 그 의미가 조금씩 달라지게 됩니다.


출처. codestates

위 내용은 위치에 따라 의미가 다르지만 공통적으로 변경이 불가능하고 확장할 수 없다는 점이 유사합니다.

인터페이스

영어에서 인터페이스는 “-간/사이"를 뜻하는 inter와 “얼굴/면"을 의미하는 face의 결합으로 구성된 단어로, 두 개의 다른 대상 사이를 연결한다는 의미를 가지고 있습니다.

기본적으로 인터페이스도 추상 클래스처럼 자바에서 추상화를 구현하는 데 활용된다는 점에서 동일하지만, 추상클래스에 비해 더 높은 추상성을 가진다는 점에서 큰 차이가 있습니다.

추상 클래스를 설계가 모두 끝나지 않은 “미완성 설계도"에 비유할 수 있다면, 인터페이스는 그보다 더 높은 추상성을 가지는 가장 기초적인 “밑그림"에 빗대어 표현할 수 있습니다.

인터페이스는 기본적으로 추상 메서드와 상수만을 멤버로 가질 수 있다는 점에서 추상 클래스에 비해 추상화 정도가 더 높다고 할 수 있습니다.

인터페이스의 기본 구조

인터페이스는 일반 클래스와 다르게, 내부의 모든 필드가 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 생략 
}

인터페이스는 interface 키워드를 사용하여 만들어지고 구현부가 완성되지 않은 추상 메서드와 상수만으로 구성되어있습니다.

인터페이스 안에서 상수를 정의하는 경우에는 반드시 public static final로, 메서드를 정의하는 경우에는 public abstract로 정의되어야 하지만 위에서 보시는 것처럼 일부분 또는 전부 생략이 가능합니다.

인터페이스의 구현

추상클래스와 마찬가지로 인터페이스도 그 자체로 인스턴스를 생성할 수 없고, 메서드 바디를 정의하는 클래스를 따로 작성해야합니다.

추상클래스는 extends를 쓰지만 인터페이스는 “구현하다"라는 의미를 가진 implements 키워드를 사용한다는 점에서 차이가 있다고 할 수 있습니다.

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

어떤 클래스가 특정 인터페이스를 구현한다는 것은 그 클래스에게 인터페이스의 추상 메서드를 반드시 구현하도록 강제하는 것을 의미합니다.

인터페이스의 다중 구현

상속에서 하위클래스는 하나의 상위클래스만 상속 받을 수 있었습니다.

하지만 인터페이스는 다중적 구현이 가능합니다.

다시 말해, 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있습니다. 다만 인터페이스는 인터페이스로부터만 상속이 가능하고, 클래스와 달리 Object 클래스와 같은 최고 조상이 존재하지 않습니다.

예시)

abstract class Animal { // 추상 클래스
	public abstract void cry();
} 
interface Pet { // 인터페이스
	public abstract void play();
}

class Dog extends Animal implements Pet { // Animal 클래스 상속 & Pet 인터페이스 구현
    public void cry(){
        System.out.println("멍멍!");
    }

    public void play(){
        System.out.println("원반 던지기");
    }
}

class Cat extends Animal implements 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();
    }
}

// 출력값
멍멍!
원반 던지기
야옹~!
쥐 잡기
profile
성장하는사람이 되자

0개의 댓글