코드스테이츠 백엔드 부트캠프 12일차 [객체지향 프로그래밍 심화 -2]

wish17·2022년 12월 29일
0
post-thumbnail

Section1 - [Java] 객체지향 프로그래밍 심화 -2

다형성(Polymorphism)

  • 한 타입의 참조변수를 통해 여러 타입의 객체를 참조할 수 있도록 만든 것을 의미
  • 상위 클래스 타입의 참조변수를 통해서 하위 클래스의 객체를 참조할 수 있도록 허용한 것
//참조변수의 다형성 예시

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();
    }
}

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

main메소드에서 GirlFriend의 경우 상위 클래스를 참조변수 타입으로 지정해 객체를 만들었다. 때문에 자연스럽게 참조변수가 사용할 수 있는 멤버의 개수는 상위 클래스의 멤버의 수가 된다.
(사용 가능한 멤버가 줄어드는데 이게 왜 장점일까? 이 질문에 대한 답이 뒤에서 나온다.)

상위 클래스의 타입으로 하위 클래스 타입의 객체를 참조하는 것은 가능하지만, 그 반대는 성립되지 않는다.


참조변수의 타입 변환

사용할 수 있는 멤버의 개수를 조절하는 것을 의미

타입 변환을 위한 세 가지 조건

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

참조변수의 타입 변환, 즉 캐스팅이 가능한 지 여부를 boolean 타입으로 확인할 수 있는 자바의 문법요소

참조_변수 instanceof 타입

캐스팅 가능 여부를 판단하기 위해서 ‘객체를 어떤 생성자로 만들었는가’와 ‘클래스 사이에 상속관계가 존재하는가’를 판단해야 한다.
이걸 대신 해주는 연산자다.

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{};

다형성의 활용 예제

main class

public class Main {
    public static void main(String[] args) {
        Customer customer = new Customer(50000);
        customer.buyCoffee(new Americano());
        customer.buyCoffee(new Caffelatte());

        System.out.println(customer.getMoney());

    }
}

Coffee class

package fortest;

public class Coffee {
    int price;

    public Coffee(int price) {
        this.price = price;
    }

}

class Americano extends Coffee {
    public Americano() {
        super(4000); // 상위 클래스 Coffee의 생성자를 호출
    }

    public String toString() {return "아메리카노";}; //Object클래스 toString()메서드 오버라이딩
}
class Caffelatte extends Coffee {
    public Caffelatte() {
        super(5000);
    }

    public String toString() {return "카페라떼";};
}

class Customer {


    private int money;

    public Customer(int money) { //cusomer 여러명일 경우를 위해 money 할당할 수 있게 만듬
        this.money = money;
    }

    void buyCoffee(Coffee coffee) {
        if (this.money < coffee.price) { // 물건 가격보다 돈이 없는 경우
            System.out.println("잔액이 부족합니다.");
            return;
        }
        this.money = this.money - coffee.price; // 가진 돈 - 커피 가격
        System.out.println(coffee + "를 구입했습니다.");
    }

    public int getMoney() {
        return money;
    }
}

buyCoffee(Coffee coffee) 메서드를 이용해 다양한 종류의 커피를 Coffee클래스에 상속시켜 간편하게 사용할 수 있는 것을 볼 수 있다.

추가로 앞에서 학습한 캡슐화와 생성자를 복습하기 위해 Customer의 종류가 많아질 경우(가진 돈이 달라질 경우)에 따라 생성자를 추가할 수 있게 기능을 추가해 보았다.


추상화(Abstraction)

객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 의미
기존 클래스들의 공통적인 요소들을 뽑아서 상위 클래스를 만들어 내는 것

abstract 제어자

클래스와 메서드를 형용하는 키워드로 사용해 추상 메서드, 추상 클래스로 만드는 제어자

  • 어떤 클래스에 추상 메서드가 포함되어있는 경우 해당 클래스는 자동으로 추상 클래스가 된다.
abstract class AbstractExample { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
	abstract void start(); // 메서드 바디가 없는 추상메서드
}
  • 위 star 메서드와 같이 추상 메서드는 메서드의 시그니처만 있고 바디가 없다.

  • 추상 클래스는 미완성 설계도이기 때문에 메서드 바디가 완성이 되기 전까지 이를 기반으로 객체 생성이 불가능하다.


추상 클래스

메서드 시그니처만 존재하고 바디가 선언되어있지 않은 추상 메서드를 포함하는 ‘미완성 설계도’

  • 미완성된 구조를 가지고 있기에 이를 기반으로 객체를 생성하는 것이 불가능

오버라이딩을 통해 추상 클래스로부터 상속받은 추상 메서드의 내용을 구현하여 메서드를 완성시킬 수 있고, 이렇게 완성된 클래스를 기반으로 해당 객체를 생성할 수 있다.

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();
    }
 }

// 출력값
멍멍
야옹
  • 추상 클래스를 사용하면 상속을 받는 하위 클래스에서 오버라이딩을 통해 각각 상황에 맞는 메서드 구현이 가능하다는 장점이 있다.

  • 추상 클래스는 자바 객체지향 프로그래밍의 마지막 기둥인 추상화를 구현하는데 핵심적인 역할을 수행한다.
    (추상화 = “객체의 공통적인 속성과 기능을 추출하여 정의하는 것”)

  • 상속계층도의 상층부에 위치할 수록 추상화의 정도가 높고 그 아래로 내려갈수록 구체화된다.
    (상층부에 가까울수록 더 공통적인 속성과 기능들이 정의되어 있다)

final 키워드

final 키워드는 필드, 지역 변수, 클래스 앞에 위치할 수 있으며 그 위치에 따라 그 의미가 조금씩 다르다.

조금의 차이점이 있지만 결국 공통적으로 변경이 불가능하고 확장할 수 없다는 점에서 유사하다.

final class FinalEx { // 확장/상속 불가능한 클래스
	final int x = 1; // 변경되지 않는 상수

	final int getNum() { // 오버라이딩 불가한 메서드
		final int localVar = x; // 상수
		return x;
	}
}

인터페이스

서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어주는 부분 or
그런 접속 장치
ex) GUI - windows나 Mac OS 같은거

  • 추상클래스에 비해 더 높은 추상성을 가진다.
    (추상 클래스가 미완성 설계도라면 인터페이스는 "밑그림"이다.)

  • 추상 메서드와 상수만을 멤버로 가질 수 있다.


인터페이스의 기본 구조

인터페이스를 작성하는 것은 기본적으로 클래스를 작성하는 것과 유사하지만 아래와 같은 차이가 있다.

  • class 키워드 대신 interface 키워드를 사용한다.
  • 내부의 모든 필드가 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 생략 
}

인터페이스의 구현

인스턴스를 생성할 수 없고, 메서드 바디를 정의하는 클래스를 따로 작성해야한다.

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

extend로 상속하는 것과 비슷하지만

특정 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 추상메서드를 구현해야 한다.

인터페이스의 다중 구현

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

class ExampleClass implements ExampleInterface1, ExampleInterface2, ExampleInterface3 { 
				... 생략 ...
}

특정 클래스는 다른 클래스로부터의 상속을 받으면서 동시에 인터페이스를 구현할 수 있다.

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();
    }
}

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

인터페이스의 장점

  • 역할과 구현을 분리시켜 사용자로 복잡한 기능의 구현이나 교체/변경을 신경쓰지 않고도 코드 변경의 번거로움을 최소화하고 손쉽게 해당 기능을 사용할 수 있도록 함.

  • 선언과 구현을 분리시켜 개발시간을 단축할 수 있고, 독립적인 프로그래밍을 통해 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화할 수 있다.


오늘의 정리

다향성

자바 객체지향 프로그래밍에서 다형성이 가지는 의미와 장점

  • 의미
    • 상위 클래스 타입의 참조변수를 통해서 하위 클래스의 객체를 참조할 수 있더록 허용하는 것
  • 장점
    • 중복되는 코드를 줄이고 보다 편리하게 코드를 작성하는 것이 가능해 진다.
      (한 타입의 참조변수를 통해 여러 타입의 객체를 참조함으로써 매번 다른 타입의 참조변수를 매개변수로 전달해주어야하는 번거로움을 줄이거나 여러 종류의 객체를 배열로 다루는 등 코드를 짜기 편리해짐.)

참조변수의 타입 변환의 목적

  • 사용할 수 있는 멤버의 개수를 조절하는 것

업캐스팅과 다운캐스팅의 차이

  • 하위->상위 클래스 타입 변환(업케스팅)은 형변환 연산자(괄호) 생략 가능. but 반대는 생략 안됨
  • 다운 캐스팅은 업 캐스팅이 되어 있는 참조변수에 한해서만 가능
  • 업케스팅은 상속관계면 가능

instanceof 연산자를 언제 어떻게 활용할 수 있나?

  • 캐스팅 가능 여부를 판단할 때 활용할 수 있다.
    (클래스가 너무 많아지면 매번 객체를 어떤 생성자로 만들었는지, 상속관계가 존재하는지 일일히 다 따지기 힘들다. 이때 유용하게 사용 가능하다.)

추상화

추상화의 핵심 개념과 목적

기존 클래스들의 공통적인 요소들을 뽑아서 상위 클래스를 만들어 내 코드의 중복을 줄이고, 효과적으로 클래스 간의 관계를 설정할 수 있어며, 유지/보수가 용이하도록 한다.

abstract 제어자가 내포하고 있는 의미와 사용

  • 내포하는 의미 = ‘미완성'

  • abstract는 주로 클래스와 메서드를 형용하는 키워드로 사용되는데, 메서드 앞에 붙은 경우를 ‘추상 메서드(abstract method)’, 클래스 앞에 붙은 경우를 ‘추상 클래스(abstract class)’가 된다.

추상 클래스의 핵심 개념, 용도

  • 개념
    • 미완성된 구조를 가지고 있는 ‘미완성 설계도’
      (따라서 객체를 생성하는 것이 불가능)
  • 용도 = 상속 관계에 있어 새로운 클래스를 작성하는데 매우 유용하다.
    • 하위 클래스에서 오버라이딩을 통해 각각 상황에 맞는 메서드 구현이 가능
    • 추상화를 구현하는데 핵심적인 역할을 수행

final 키워드

  • 클래스,메서드,변수 맨 앞에 붙여 사용
  • 변경이 불가능하고 확장할 수 없게 만듦

인터페이스의 핵심 내용과 그 활용

  • 내용
    • 추상 메서드와 상수만으로 구성
    • 내부의 모든 필드가 public static final로 정의된다.
    • static과 default 메서드 이외의 모든 메서드가 public abstract로 정의된다.

추상 클래스와 인터페이스의 차이

  • 구성

    • 추상 클래스 = 추상 메서드를 하나 이상 포함한다는 점 외에는 기본적으로 일반 클래스와 동일하다.

    • 인터페이스 = 추상 메서드와 상수만을 멤버로 가질 수 있다.

  • 상속과 구현의 메서드 강제성

    • 추상 클래스 = 상속받은 하위 클래스가 선택적으로 추상 메서드 사용

    • 인터페이스 = 구현한 클래스가 인터페이스의 추상 메서드를 반드시 구현하도록 강제한다.

  • 다중 구현

    • 추상클래스 = 클래스 간의 상속에서 다중 상속은 허용되지 않음, Object 클래스와 같은 최고 조상 존재o

    • 인터페이스는 다중적 구현이 가능 ( but 인터페이스는 인터페이스로부터만 상속이 가능), Object 클래스와 같은 최고 조상 존재x, 특정 클래스는 다른 클래스로부터의 상속을 받으면서 동시에 인터페이스를 구현할 수 있음.

추상클래스의 상속은 기능을 이용하거나 확장하기 위해서 사용하고
인터페이스의 구현은 구현한 객체들에 대해서 동일한 동작을 약속하기 위해 사용한다고 생각하면 될 것 같다.

둘이 비슷하니 다중구현의 필요성, 기능의 통일성을 기준으로 둘 중 뭘 사용할지 정하면 될 것 같다.


0개의 댓글