TIL - 다형성과 추상화

DY_DEV·2023년 3월 1일
0

TIL

목록 보기
6/17

다형성 (polymorphism)

다형성이란 "여러 개"를 의미하는 poly와 어떤 ‘형태' 또는 ‘실체’를 의미하는 morphism의 결합어로 하나의 객체가 여러 가지 형태를가질 수 있는 성질을 의미

자바에서 다형성이란?

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

인스턴스 생성시 상위 타입의 클래스를 타입으로 지정한 경우(참조변수의 타입과 객체 타입이 불일치 하는 경우)

= 상위 클래스를 참조변수의 타입으로 지정했기 때문에 자연스럽게 참조변수가 사용할 수 있는 멤버의 개수는 상위 클래스의 멤버의 수가 된다.

=‘상위 클래스 타입의 참조변수로 하위 클래스의 객체를 참조하는 것’

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

참조변수의 타입 변환

참조변수의 타입 변환 : 사용할 수 있는 멤버의 개수 조절

  • 타입변환의 세 가지 조건
    1. 서로 상속관계에 있는 상위 클래스 - 하위 클래스 사이에만 타입 변환이 가능하다.
    2. 하위 클래스 타입에서 상위 클래스 타입으로의 타입 변환(업캐스팅)은 형변환 연산자(괄호)를 생략할 수 있다.
    3. 반대로 상위 클래스에서 하위 클래스 타입으로 변환(다운캐스팅)은 형변환 연산자(괄호)를 반드시 명시해야한다.
      • 또한, 다운 캐스팅은 업 캐스팅이 되어 있는 참조변수에 한해서만 가능하다. > 형변환 기초에 나온 내용들

참조변수의 타입변환은 서로 상속 관계에 있는 관계에서는 양방향으로 수행될 수 있으나, 상위 클래스로의 타입 변환이냐(괄호 생략 가능) 아니면 하위 클래스로의 타입 변환이냐(괄호 생략 불가)에 따라서 약간의 차이가 있다.
(EX)
car > vehicle (상위 클래스로 변환. 생략 가능)
vehicle > car (하위 클래스로 변환, 생략 불가능)
car > motorBike(상속관계가 아니다. 타입 변환 불가능)

instanceof 연산자

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

  • 캐스팅 가능 여부를 판단하기 위해서는 두 가지, 즉 ‘객체를 어떤 생성자로 만들었는가’와 ‘클래스 사이에 상속관계가 존재하는가’를 판단해야한다.
    - 참조_변수 instanceof 타입
  • 입력시 리턴 값이 true가 나오면 참조 변수가 검사한 타입으로 타입 변환이 가능하며, 반대로 false가 나오는 경우에는 타입 변환이 불가능
  • 참조 변수가 null인 경우에는 false를 반환
  • 소스 코드가 길어지는 등 일일이 생성 객체의 타입을 확인하기가 어려운 상황에서 instanceof 연산자는 형변환 여부를 확인하여 에러를 최소화하는 매우 유용한 수단이 될 수 있다.

매번 새로운 타입을 매개변수로 전달해주는 메서드를 추가해야하는 경우 다형성을 통해 해결할 수 있다. > 상위 클래스의 타입을 매개변수로 전달해준다.

추상화

  • 학습목표
    • 추상화의 핵심 개념과 목적을 이해하고 설명할 수 있다.
    • abstract 제어자가 내포하고 있는 의미를 이해하고, 어떻게 사용되는 지 설명할 수 있다.
    • 추상 클래스의 핵심 개념과 기본 문법을 이해할 수 있다.
    • final 키워드를 이해하고 설명할 수 있다.
    • 자바 추상화에서 핵심적인 역할을 수행하는 인터페이스의 핵심 내용과 그 활용을 이해할 수 있다.
    • 추상 클래스와 인터페이스의 차이를 설명할 수 있다.

추상이란?

  • “사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것"

자바에서 추상화란?

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

abstract 제어자

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

  • abstract의 가장 핵심 개념은 미완성.
  • 추상 메서드는 메서드의 시그니처만 있고 바디가 없는 메서드를 의미
abstract class AbstractExample { // 추상 메서드가 최소 하나 이상 포함돼있는 추상 클래스
	abstract void start(); // 메서드 바디가 없는 추상메서드
}
  • 추상 클래스는 앞서 설명한대로 미완성 설계도이기 때문에 메서드 바디가 완성이 되기 전까지 이를 기반으로 객체 생성이 불가능하다.

추상클래스

마완성 클래스를 만드는 이유?

  1. 추상 클래스는 상속 관계에 있어 새로운 클래스를 작성하는데 매우 유용하기 때문에.
  2. 메서드의 내용이 상속을 받는 클래스에 따라서 종종 달라지기 때문에 상위 클래스에서는 선언부만을 작성하고, 실제 구체적인 내용은 상속을 받는 하위 클래스에서 구현하도록 비워둔다면 설계하는 상황이 변하더라도 보다 유연하게 대응할 수 있다.
  • 추상 클래스를 사용하면 상속을 받는 하위 클래스에서 오버라이딩을 통해 각각 상황에 맞는 메서드 구현이 가능하다는 장점이 있다.
  • 추상 클래스는 자바 객체지향 프로그래밍의 마지막 기둥인 추상화를 구현하는데 핵심적인 역할을 수행한다.
  • 여러 사람이 함께 개발하는 경우, 공통된 속성과 기능임에도 불구하고 각각 다른 변수와 메서드로 정의되는 경우 발생할 수 있는 오류를 미연에 방지할 수 있다.
  • 구체화에 반대되는 개념으로 추상화를 생각해보면, 상속계층도의 상층부에 위치할 수록 추상화의 정도가 높고 그 아래로 내려갈수록 구체화된다고 정리

final 키워드

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

위치의미
클래스변경 또는 확장 불가능한 클래스, 상속 불가
메서드오버라이딩 불가
변수값 변경이 불가한 상수
final class FinalEx { // 확장/상속 불가능한 클래스
	final int x = 1; // 변경되지 않는 상수

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

인터페이스

두 개의 다른 대상을 연결

  • 컴퓨터 프로그래밍에서 사용하는 인터페이스도 “서로 다른 두 시스템, 장치, 소프트웨어 따위를 서로 이어주는 부분 또는 그런 접속 장치"라 정의
  • 인터페이스는 추상 메서드와 상수만을 멤버로 가질 수 있다. > 추상 클래스 보다 추상화 정도가 더 높다.
  • 인터페이스는 기본적으로 추상 메서드의 집합으로 이뤄져있다.

인터페이스의 구조

클래스 작성과 유사하지만 class 대신 interface 키워드를 사용한다.

  • 일반 클래스와 다르게, 내부의 모든 필드가 public static final로 정의되고, 앞서 간단하게 언급한 staticdefault메서드 이외의 모든 메서드가 public abstract로 정의된다.
  • 인터페이스 안에서 상수를 정의하는 경우에는 반드시 public static final로, 메서드를 정의하는 경우에는 public abstract로 정의해야 일부분 또는 전부 생략 가능. >생략된 부분은 컴파일러가 자동으로 추가.

인터페이스의 구현

추상클래스 처럼 인터페이스도 그 자체로 인스턴스를 생성할 수 없고, 메서드 바디를 정의하는 클래스를 따로 작성해야 한다.

class 클래스명 implements 인터페이스명 {
		... // 인터페이스에 정의된 모든 추상메서드 구현
}
  • 인터페이스를 구현한 클래스는 해당 인터페이스에 정의된 모든 추상메서드를 구현해야한다.
  • 어떤 클래스가 특정 인터페이스를 구현한다 = 해당 클래스에게 인터페이스의 추상 메서드를 반드시 구현하도록 강제하는 것
  • 어떤 클래스가 어떤 인터페이스를 구현한다는 것은 그 인터페이스가 가진 모든 추상 메서드들을 해당 클래스 내에서 오버라이딩하여 바디를 완성한다라는 의미

인터페이스의 다중 구현

= 하나의 클래스가 여러 개의 인터페이스를 구현할 수 있다.

다만 인터페이스는 인터페이스로부터만 상속이 가능하고, 클래스와 달리 Object 클래스와 같은 최고 조상이 존재하지 않는다.

class ExampleClass implements ExampleInterface1, ExampleInterface2, ExampleInterface3 {
				... 생략 ...
}
  • 인터페이스는 클래스와 달리 다중 구현이 가능한 이유?
    • 클래스에서 다중 상속이 불가능했었던 핵심적인 이유: 부모 클래스에 동일한 이름의 필드 또는 메서드가 존재하는 경우 충돌이 발생하기 때문
    • 반면 인터페이스는 애초에 미완성된 멤버를 가지고 있기 때문에 충돌이 발생할 여지가 없고, 따라서 안전하게 다중 구현이 가능

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

인터페이스의 장점

  • 일반적인 인터페이스의 기능처럼 역할과 구현을 분리시켜 사용자 입장에서는 복잡한 구현의 내용 또는 변경과 상관없이 해당 기능을 사용할 수 있다는 점이다.

  • 인터페이스를 매개변수로 받게 한다.

     >코드의 변경을 최소화하여 효율성을 높일 수 있다. 
  • 개발자의 입장에서도 선언과 구현을 분리시켜 개발시간을 단축
    할 수 있고, 독립적인 프로그래밍을 통해 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화할 수 있다

인터페이스 활용

  1. 인터페이스는 상수와 추상 메서드만을 구성 멤버로 가진다.
public interface Customer {
	// 상수
	// 추상 메서드
}
  1. implements 키워드 사용
public class CafeCustomerA implements Customer {

}

public class CafeCustomerB implements Customer {

}
  1. 인터페이스와 참조변수의 형변환을 사용해 Customer타입을 매개변수로 전달하여 추가적인 메서드 작성을 생략할 수 있다.
  2. 인터페이스에 추상 메서드 추가 > 시그니처만 있고 바디는 없는 형태. 바디는 하위 객체에 생성해준다.
public interface Customer {
  public abstract String getOrder();
}

public class CafeCustomerA implements Customer {
  public String getOrder(){
		return "a glass of iced americano";
	}
}

public class CafeCustomerB implements Customer {
  public String getOrder(){
		return "a glass of strawberry latte";
	}
}
  1. CafeOwner 클래스를 재정의하여 매개변수로 Customer 타입이 입력될 수 있게끔 만들어주면, 매개변수의 다형성에 의해 Customer
    를 통해 구현된 객체 모두가 들어올 수 있다.
public class CafeOwner {
  public void giveItem(Customer customer) {
    System.out.println("Item : " + customer.getOrder());
  }
}
interface Customer {
  String getOrder();
}

class CafeCustomerA implements Customer {
  public String getOrder(){
		return "a glass of iced americano";
	}
}

class CafeCustomerB implements Customer {
  public String getOrder(){
		return "a glass of strawberry latte";
	}
}

class CafeOwner {
  public void giveItem(Customer customer) {
    System.out.println("Item : " + customer.getOrder());
  }
}

public class OrderExample {
    public static void main(String[] args) throws Exception {
        CafeOwner cafeowner = new CafeOwner();
        Customer cafeCustomerA = new CafeCustomerA();
        Customer cafeCustomerB = new CafeCustomerB();

        cafeowner.giveItem(cafeCustomerA);
        cafeowner.giveItem(cafeCustomerB);
    }
}

// 출력값
Item : a glass of iced americano
Item : a glass of strawberry latte

= 중요한 점은 메서드의 개수가 줄어든 것 보다 CafeOwner 클래스가 더 이상 손님에게 의존적인 클래스가 아닌 독립적인 기능을 수행하는 클래스가 되었다는 점이다.

0개의 댓글