IJY
느리더라도 꾸준히
IJY
전체 방문자
오늘
어제
  • 분류 전체보기 (67)
    • Develop (67)
      • Java (8)
      • Go (0)
      • Test (1)
      • Web (1)
      • HTML, CSS (1)
      • TIL(Today I Learned) (18)
      • SQL (0)
      • Algorithm (27)
      • 회고 (7)
      • Troubleshooting (1)
      • Etc (3)
    • Etc (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록
  • 글쓰기

공지사항

인기 글

태그

  • REST Assured
  • init
  • 독후감
  • object
  • Class
  • 알고리즘
  • 프로그래머스
  • MVC
  • stream
  • 소수 찾기
  • Spring
  • PostConstruct
  • 12921
  • instance
  • Interceptor
  • BufferedWriter
  • 초기화
  • EntityTransaction
  • 회고
  • 재귀
  • sort
  • html
  • java
  • BufferedReader
  • 우테코 온보딩
  • web
  • recursion
  • 백준
  • API 예외 처리
  • Filter

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
IJY

느리더라도 꾸준히

Develop/TIL(Today I Learned)

[2022.05.13] OOP 심화 2/2 - 다형화, 추상화

2022. 7. 25. 03:41

OOP의 4가지 특징

  1. 상속화
  2. 캡슐화
  3. 다형화
  4. 추상화

오늘은 위 4가지 특징 중 다형화와 추상화에 대한 공부를 진행하였고, 해당 내용들을 두서없이 정리한 게시글입니다.

틀린 내용이 있다면 알려주세요!


다형성(polymorphism)

하나의 객체가 여러 가지 형태(타입)를 가질 수 있는 것을 의미

Java에서는 한 타입의 참조 변수를 통해 여러 타입의 객체를 참조할 수 있는 것을 의미

=> 상위 클래스 참조 변수를 통해 하위 클래스의 객체를 참조할 수 있도록 허용한 것

위 문장은 "참조 변수의 제어 가능한 메모리 범위가 참조하려는 객체의 메모리 범위보다 같거나 작아야 한다"라고 표현할 수 있다.

 

예제 코드로 동작을 알아보자

class Vehicle {
	public void printInfo() {
		System.out.println("I'm Vehicle!");
	}
}

class Car extends Vehicle {
	@Override
	public void printInfo() {
		System.out.println("I'm Car!");
	}
}

class Bike extends Vehicle {
	@Override
	public void printInfo() {
		System.out.println("I'm Bike!");
	}
}

public class Main {
	public static void main(String[] args) {
		Vehicle vehicle = new Vehicle();
		Car car = new Car();
		Vehicle bike = new Bike();	// 상위 클래스인 Vehicle 참조 변수로 하위 클래스인 Bike 객체 참조
		// Car test = new Vehicle();	// 하위 클래스인 Car 참조 변수로 상위 클래스인 Vehicle 객체 참조시 에러 발생

		vehicle.printInfo();
		car.printInfo();
		bike.printInfo();
	}
}
I'm Vehicle!
I'm Car!
I'm Bike!

예제 코드의 29라인을 보면 상위 클래스인 Vehicle 참조 변수로 하위 클래스인 Bike 객체를 참조하도록 되어있는 걸 확인할 수 있다.

이렇게 작성 후 bike의 printInfo() 메서드를 동작시켰을 때, 실제로 Bike class에서 오버라이딩한 printInfo() 메서드로 동작하는 것을 알 수 있다.

반대로 30라인을 보면 하위 클래스인 Car 참조 변수로 상위 클래스인 Vehicle 객체를 참조하도록 되어 있는데 코드의 주석을 풀어보면 에러가 발생함을 확인할 수 있다.

이는 하위 클래스인 Car가 제어할 수 있는 메모리 범위가 상위 클래스인 Vehicle 객체의 메모리 범위를 넘기 때문이다.

왜 하위 클래스가 상위 클래스보다 메모리 범위가 크냐? 이전에 상속에서 배운 내용인데 하위 클래스는 상위 클래스의 확장(extend)이기 때문

그렇다면 왜 굳이 다형성을 사용하느냐? 장점이 뭐냐?

자주 등장하는 내용이지만 코드의 중복을 줄여서 보다 편리하고 효과적인 코드를 짤 수 있게 된다는 이유이다.

메서드 오버라이딩과 메서드 오버로딩을 다형성의 한 예시라고 할 수 있다.

 

참조 변수의 타입 변환

참조 변수의 타입 변환을 다르게 설명하면 제어할 수 있는 멤버의 개수를 조절하는 것

타입 변환을 하기 위한 조건은

  1. 서로 상속관계에 있는 클래스
  2. 하위 > 상위 클래스 타입으로의 변환(업캐스팅)은 형변환 연산자 생략 가능
  3. 상위 > 하위 클래스 타입으로의 변환(다운캐스팅)은 반드시 형변환 연산자 명시 필요

예제 코드로 자세히 알아보자 (위 예제 코드의 class가 정의되어 있다는 가정)

public class Main {
	public static void main(String[] args) {
		Car car = new Car();
		Vehicle vehicle = car;		// 상위 클래스인 Vehicle 타입으로의 변환(업캐스팅)이므로 형변환 연산자 생략
		Car car2 = (Car) vehicle;	// 하위 클래스인 Car 타입으로의 변환(다운캐스팅)이므로 형변환 연산자 명시
		// Bike bike = (Bike) car;	// Car 클래스와 상속관계가 아닌 Bike 클래스로의 변환이므로 에러 발생
		// Bike bike = (Bike) vehicle;	// 하위 클래스인 Bike 타입으로의 변환(다운캐스팅)이므로 컴파일 에러는 X
						// 하지만 현재 vehicle이 참조하고 있는 객체는 Car 클래스의 인스턴스이므로 프로그램 실행시 형변환 에러 발생
	}
}

main() 메서드의 5번째 라인을 보면 참조 변수의 타입과는 별개로 실제 참조하는 인스턴스가 무엇이냐에 따라 형변환 에러가 발생할 수 있음을 확인할 수 있다.

따라서 위 3가지 조건은 컴파일 시 확인이 되어 에러가 발생하지만 위 5번째 라인과 같은 상황은 실행되기 전까진 에러가 발생하는지 알 수 없다.

 

instanceof 연산자

위 타입 변환 조건들 및 예제 코드의 main() 메서드 5번째 라인과 같은 상황을 프로젝트가 커진 상황에서 하나하나 확인해서 작성하기란 매우 힘든 일이 될 것이다.

그렇기에 이러한 상황을 해결하기 위한 연산자가 instanceof 연산자라고 할 수 있다.

instanceof 키워드는 참조 변수의 타입 변화가 가능한 지 여부를 boolean 타입으로 반환해 주는 Java의 연산자로써

타입 변환 여부는 '객체를 어떤 생성자로 만들었는가'와 '클래스가 서로 상속관계에 있는가'로 알 수 있기 때문에 이를 판단해 주는 연산자라고 볼 수 있다.

사용법은 "참조_변수 instanceof 타입" 형태로 사용하며 true가 반환되면 변환 가능, false가 반환되면 반환 불가능하다.

 

예제 코드로 자세히 알아보자 (위 예제 코드의 class가 정의되어 있다는 가정)

public class Test {
	public static void main(String[] args) {
		Vehicle vehicle = new Vehicle();
		System.out.println(vehicle instanceof Object);	// true
		System.out.println(vehicle instanceof Vehicle);	// true
		System.out.println(vehicle instanceof Car);	// false : 하위 클래스로 타입 변환 불가
		System.out.println(vehicle instanceof Bike);	// false : 하위 클래스로 타입 변환 불가

		Car car = new Car();
		System.out.println(car instanceof Object);	// true
		System.out.println(car instanceof Vehicle);	// true
		// System.out.println(car instanceof Bike);	// 상속 관계가 아니므로 컴파일단에서 에러 발생
		
		Vehicle car2 = new Car();
		System.out.println(car2 instanceof Object);	// true
		System.out.println(car2 instanceof Vehicle);	// true
		System.out.println(car2 instanceof Bike);	// false : 위 예제 코드 main() 메서드의 5번째 라인과 동일한 경우
	}
}

추상화(Abstraction)

객체들의 공통되는 속성과 기능을 추출하여 하나의 클래스로 정의하거나 핵심이 되는 속성과 기능을 제외한 불필요한 것들은 추려내는 것

Java에서는 추상 클래스와 인터페이스를 통해서 추상화를 구현

 

abstract 제어자

Java의 기타 제어자 중 하나, 클래스와 메서드 앞에 붙여서 사용

메서드 앞에 사용하게 되면 해당 메서드를 "추상 메서드(abstract method)"라 부르고,

클래스 앞에 사용하게 되면 해당 클래스를 "추상 클래스(abstract class)"라고 부르게 된다.

추상 클래스란?

클래스에 추상 메서드가 하나라도 포함되는 경우, 이를 추상 클래스라 한다.

추상 메서드란?

메서드 시그니처(반환 타입, 메서드명, 매개변수)만 있고 메서드 바디는 없는 메서드

위 경우에 해당하는 메서드와 클래스인 경우 abstract 제어자를 명시해 줘야 한다.

abstract class Test {		// 추상 메서드가 포함되어 있기에 추상 클래스가 된다.
	abstract void test();	// 메서드 바디가 없는 추상 메서드
}

이처럼 추상 클래스의 경우 추상 메서드의 동작이 미구현인 상태이기에 객체를 생성할 수 없는 상태가 된다.

(위 예제 코드의 Test 클래스가 있다는 가정)
public class Main {
	public static void main() {
		Test test = new Test();	// 에러 발생
	}
}

 

 

추상 클래스(abstract class)

위에서 설명했듯이 객체도 생성할 수 없는 추상 클래스를 왜 쓰는 것일까?

추상 클래스는 상속을 위한 상위 클래스로 활용하려는 목적으로 사용하게 된다.

추상 메서드를 정의함으로써 상속받는 하위 클래스들에서 구현할 메서드의 원형을 알려주게 되므로 인터페이스의 역할을 하게 되며, 이로써 다형성을 실현하게 된다.

또한 추상 클래스를 상속받은 하위 클래스들은 추상 메서드를 전부 구현하지 않으면 에러가 발생하게 된다.

즉, 이로 인해 발생되는 이점으로는

  1. 상속을 받기에 메서드명이 통일되어 코드의 통일성 및 유지 보수 편의성 증대
  2. 상속을 받아와 추상 메서드의 동작만 구현하면 되기 때문에 시간 절약 및 편의성 증대
  3. 협업 시 규격의 정의로써 사용되어 구현 및 유지 보수 편의성 증대

예제 코드로 사용법을 알아보자

abstract class Vehicle {
	public String category;
	public abstract void start();
}

class Car extends Vehicle {
	public Car() {
		this.category = "자동차";
	}

	public void start() {	// method overriding > 기능 구현
		System.out.println("차를 출발시킵니다.");
	}
}

class Bike extends Vehicle {
	public Bike() {
		this.category = "자전거";
	}

	public void start() {	// method overriding > 기능 구현
		System.out.println("자전거를 출발시킵니다.");
	}
}

public class Main {
	public static void main(String[] args) {
		Car car = new Car();
		Bike bike = new Bike();
		
		car.start();
		bike.start();
	}
}
차를 출발시킵니다.
자전거를 출발시킵니다.

결론적으로 추상화를 구체화의 반대되는 개념으로 보면 되고, 상속계층도의 상층부에 위치할수록 추상화의 정도가 높고 하층부로 내려올수록 구체화된다고 볼 수 있다.

즉, 상층부에 위치할수록 공통적인 속성과 기능들이 정의되어 있고 하층부에 위치할수록 공통적인 부분들이 없어진다.

 

final 제어자

Java의 기타 제어자 중 하나, 클래스, 필드, 지역 변수 앞에 붙여서 사용하며 어디에 사용하느냐에 따라 의미가 조금씩 다르다.

위치 의미
클래스 클래스 상속 불가
메서드 오버라이딩 불가
변수 값 변경이 불가한 상수화

각각 의미는 조금씩 다르지만 공통적인 동작으로 변경이 불가능하게 되므로 final 뜻 그대로 최종 값으로 지정한다고 볼 수 있다.

final class FinalClass {	// 상속 불가능한 클래스가 된다
	final int x = 10;		// 변경 불가능한 상수가 된다
	
	final void getNum() {
		// return x;		// 사실상 이렇게 구현하면 되지만 예를 들기 위해서 아래와 같이 구현
		final int lv = x;	// 변경 불가능한 상수가 된다
		return lv;
	}
}

 

인터페이스(interface)

Java에서 클래스들이 구현해야 하는 동작을 지정하는 데 사용되는 추상 자료형, 프로토콜과 유사

클래스와 다르게 인터페이스는 다중 상속을 받을 수 있다.

인터페이스 구현 시 포함할 수 있는 항목은

  1. 상수 필드(constant field)
  2. 추상 메서드(abstraction method)
  3. 디폴트 메서드(default method) - Java8부터 포함 가능
  4. 정적 메서드(static method) - Java8부터 포함 가능

인터페이스를 구현하는 방법은 클래스 구현 방법과 비슷하다. class 키워드 대신 interface 키워드를 사용하면 된다.

interface InterfaceTest {	// InterfaceTest라는 인터페이스 정의
	public static final int CONSTANTS_NUM = 10;
	String IF_STRING = "인터페이스 문자열 상수";	// public static final 생략 가능

	public abstract String getString();
	int getNum();	// public abstract 생략 가능
}

위 예제 코드에서 확인 가능하듯 인터페이스에는 필드에 상수만 사용 가능하기 때문에 "public static final" 키워드를 사용해야 하지만 생략하여 사용할 수 있다. 이렇게 생략된 부분은 컴파일러가 컴파일 과정에서 자동으로 추가해 준다. 추상 메서드 정의도 마찬가지.

또한 추상 클래스와 마찬가지로 인터페이스를 상속받는 클래스에서는 인터페이스 내에 정의된 모든 추상 메서드를 구현하지 않으면 에러가 발생한다.

 

그럼 이제 오늘 배운 내용을 모두 포함하는 예제 코드를 보도록 하자

interface VehicleInterface {	// VehicleInterface 인터페이스 정의
	public abstract void ride(boolean on);
	public abstract void run();
	// void speedUp(int speed);	// 코드가 길어져서 미구현
	// void speedDown(int speed);	// 코드가 길어져서 미구현
	// void stop();	// 코드가 길어져서 미구현
}

/* VehicleInterface 인터페이스를 상속받으면서 ride() 메서드만 구현하고
   run() 메서드는 미구현이므로 추상화 클래스로 정의된다. */
abstract class Vehicle implements VehicleInterface  {
	public String category;	// 일반 인스턴스 변수 정의

	public Vehicle() {	// 기본 생성자(default constructor) 정의
		category = "탈것";
	}

	public Vehicle(String category) {	// 생성자 오버로딩(constructor overloading)
		this.category = category;
	}


	public void showCategory() {	// 일반 메서드 정의
		System.out.println("카테고리 : " + category);
	}

	@Override
	public void ride(boolean on) {	// VehicleInterface 인터페이스의 ride() 메서드 오버라이딩
		if(on)
			System.out.println(category + "에 탑승합니다.");
		else
			System.out.println(category + "에서 내립니다.");
	}
}

class Car extends Vehicle {	// Vehicle 추상화 클래스를 확장하는 Car 클래스 정의
	public Car() {	// 기본 생성자 정의
		super("자동차");	// 상위 클래스의 생성자 호출
	}

	@Override
	public void run() {	// VehicleInterface 인터페이스의 run() 메서드 오버라이딩
		System.out.println("자동차에 시동을 걸고 출발합니다.");
	}
}

class Bike extends Vehicle {	// Vehicle 추상화 클래스를 확장하는 Bike 클래스 정의
	public Bike() {	// 기본 생성자 정의
		super("자전거");	// 상위 클래스의 생성자 호출
	}

	@Override
	public void run() {	// VehicleInterface 인터페이스의 run() 메서드 오버라이딩
		System.out.println("자전거 페달을 밟으며 출발합니다.");
	}
}

public class Test {
	public static void main(String[] args) {
		Car car = new Car();
		Bike bike = new Bike();
		Vehicle[] v_arr = { new Car(), new Bike() };	// 배열을 이용한 다형화

		car.showCategory();
		bike.showCategory();
		System.out.println();	// 한줄 띄우기 - 출력의 가독성 때문에 넣었을 뿐입니다.

		car.ride(true);
		bike.ride(false);
		System.out.println();	// 한줄 띄우기 - 출력의 가독성 때문에 넣었을 뿐입니다.

		for(Vehicle v : v_arr) {	// 배열의 멤버를 전부 돌면서 반복 실행
			v.showCategory();
			v.ride(true);
			v.run();
			System.out.println();	// 한줄 띄우기 - 출력의 가독성 때문에 넣었을 뿐입니다.
		}
	}
}
카테고리 : 자동차
카테고리 : 자전거

자동차에 탑승합니다.
자전거에서 내립니다.

카테고리 : 자동차
자동차에 탑승합니다.
자동차에 시동을 걸고 출발합니다.

카테고리 : 자전거
자전거에 탑승합니다.
자전거 페달을 밟으며 출발합니다.

예제 코드가 길지만.. 인터페이스, 추상 클래스, 추상 메서드, 상속, 배열을 이용한 다형성 구현 등을 전부 포함하는 코드입니다.

VehicleInterface 인터페이스를 통해서 탈것의 공통된 동작의 틀을 정의하고 (추상화)

VehicleInterface 인터페이스를 상속받는 Vehicle 추상 클래스에서 탈것에 대한 공통 속성(category)과 기능(ride())을 오버라이딩하고 (추상화 및 다형화)

Vehicle 추상 클래스를 상속받는 Car와 Bike 클래스에서 각각 속성에 맞춰 생성자를 통한 category 변수를 설정하고 각각의 기능에 따른 run() 메서드를 오버라이딩한다. (다형화)

'Develop > TIL(Today I Learned)' 카테고리의 다른 글

[2022.05.24] 자료구조/알고리즘 - 재귀  (0) 2022.07.25
[2022.05.23] 모의 기술 면접  (0) 2022.07.25
[2022.05.12] OOP 심화 1/2 - 상속화, 캡슐화  (0) 2022.07.25
[2022.04.28] Web 기초 - CSS  (0) 2022.07.25
[2022.04.27] Web 기초 - HTML  (0) 2022.07.25
    'Develop/TIL(Today I Learned)' 카테고리의 다른 글
    • [2022.05.24] 자료구조/알고리즘 - 재귀
    • [2022.05.23] 모의 기술 면접
    • [2022.05.12] OOP 심화 1/2 - 상속화, 캡슐화
    • [2022.04.28] Web 기초 - CSS
    IJY
    IJY
    개발 관련 공부한 내용을 정리하는 블로그입니다. 느리더라도 꾸준히 포스팅을 하려고 노력합니다.

    티스토리툴바