simDev1234
심플하고 차분하게
simDev1234
전체 방문자
오늘
어제
  • 분류 전체보기
    • Computer Science
      • Basic Math
      • Data Structure
      • Algorithm
      • Database
      • OS
    • Language
      • Java
      • Kotlin
      • SQL
    • Framework
      • Spring
      • Orm&Mapper
      • 프로젝트로 스프링 이해하기
      • 스프링 라이브러리
    • Infra
      • Cloud
      • Docker
      • Redis
      • AWS, Azure
      • Device
    • Etc
      • CleanCoding
    • Git,Github

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바메모리구조
  • 스프링
  • scanner #next() #nextLine()
  • 참조타입
  • controllerTest
  • JVM메모리구조
  • 자바프로그래밍
  • 참조변수
  • 자바
  • 404
  • null
  • 자바프로그램
  • 컨트롤러

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
simDev1234

심플하고 차분하게

객체지향 언어의 특징 3_다형성 (사용 편의성)
Language/Java

객체지향 언어의 특징 3_다형성 (사용 편의성)

2022. 7. 22. 21:45

|  다형성 (사용 편의성)

다형성이란, 하나의 객체가 여러가지 타입을 가질 수 있는 것을 의미한다.

다형성은 오버로딩과 오버라이딩을 통해 구현할 수 있다.

오버라이딩이 가능한 이유는 뭘까? 그건 동적 바인딩 때문이다.

여기서 동적 바인딩이란 메서드가 실행 시점에서 성격이 결정되는 것을 말한다.

종류 정적바인딩(static binding) 동적바인딩(Dynamic binding)
정의 컴파일 시간에 성격이 결정 실행 시간(런타임)에 성격이 결정
예시 C언어 컴파일 시간에 변수의 데이터타입이 결정
ex. int *p;   --  미리 데이터타입 지정
javascript 런타임에 값에 따라 변수의 데이터타입 결정
ex. var c = 1;  --  미리 데이터타입 지정x
ex. Person p = new Person(), p.method()
-- p 생성 전엔 p가 가리키는 곳 어딘지 알 수 없음
장단점 컴파일 시간에 많은 정보가 확정되어있어 실행 효율↑
값이 변하지 않아서 안정적
런타임에 자유롭게 성격이 바뀌므로 적응성↑
메모리 공간의 낭비, 느린 속도

- 자바에서는 static 메소드의 경우 정적바인딩을, 일반 메소드의 경우 동적바인딩을 사용한다.

 

|  다형성의 장단점

장점 유지보수가 쉽다 하나의 타입으로 여러 가지의 객체를 관리할 수 있어 유지보수가 쉽다.
재사용성이 증가 객체를 재사용하기가 쉬워 개발자의 코드 재사용성이 높아진다.
느슨한 결합 부모타입으로 자식인스턴스를 참조할 경우 클래스간 의존성이 낮아지며,
확장성이 높고, 결합도가 낮아진다.
단점 프로그램의 가독성이 낮아짐 실제 인스턴스가 무엇인지 instanceof가 없으면 알기 어렵다
디버깅의 어려움 -

 

|  다형성 구현 - 오버로딩과 오버라이딩

다형성을 개념적으로, 구현적으로 나누기도 하는 걸 봤었는데, 결국 핵심은 오버로딩과 오버라이딩이었다.

여기에 어떤 분들은 추가적으로 함수형 인터페이스를 함께 소개하기도 해서, 아래와 같이 넣어보았다.

 

오버로딩(중복 정의) 같은 이름의 메소드를 매개변수를 다르게 해서 적재
오버라이딩(재정의)  똑같은 선언부의 메소드를 다른 내용으로 재정의 
함수형 인터페이스 한 개의 추상 메소드를 가지고 있는 인터페이스

 

1. 오버로딩 

오버로딩의 조건

1. 메소드의 이름이 같아야 한다.
2. 매개 변수의 갯수 또는 타입이 달라야한다.
3. 반환 타입이 다른 것은 관계 없다.

- 오버로딩의 대표적인 예로, PrintStream의 println()이 있다.

public class PrintStream extends FilterOutputStream
        implements Appendable, Closeable
{

    public void println() {
        newLine();
    }

    public void println(boolean x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void println(char x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void println(int x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

    public void println(long x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }

}

 

2. 오버라이딩

오버라이딩의 조건

1. 상위 클래스를 상속 또는 구현해야한다.
2. 물려 받은 메소드를 구현할 때는 선언부가 동일해야 한다.
3. 하위 클래스는 상위 클래스보다 접근제어자가 더 공개적이거나 같아야 한다.
(ex. 상위가 public -> 하위도 public, 상위가 protected -> 하위는 protected or public)
4. final과 private인 메소드는 오버라이딩이 불가하다.

[1] 상위 클래스를 상속하고 메소드를 오버라이딩하는 경우 

- 지난번 상속에서 사용한 Vehicle를 조금 변형해서 '움직인다(move)' 기능만 만들어 보았다.

- Vehicle을 상속하는 Car, Train, Airplane은 move를 오버라이딩하여 저마다의 소리를 낸다.

package polymorphism;

class Vehicle {
	
	public void move() {
		System.out.println("unknown");
	}
	
}

class Car extends Vehicle{
	
	public void move() {
		System.out.println("부릉부릉");
	}
}

class Train extends Vehicle{
	
	public void move() {
		System.out.println("칙칙폭폭");
	}
}

class Airplane extends Vehicle{
	
	public void move() {
		System.out.println("위잉위잉");
	}
}

- 메인 함수에서의 출력

 각 하위 인스턴스들을 부모 참조변수인 Vehicle로 통일하여 참조하는 걸 아래에서 볼 수 있다.

 이렇게 부모 참조변수로 자식 인스턴스를 참조하는 것을 업캐스팅이라고 한다.

업캐스팅
의미 상위 객체 참조 변수로 하위 인스턴스를 참조하는 것
제약사항 상위 객체 참조 변수를 쓰면, 하위 객체의 맴버를 참조할 수 없다. (= 하위 객체 변수를 car.xxx식으로 쓸 수 없다)

ex. Vehicle을 상속받은 Car객체엔 Car만의 맴버 "gasDisplacement"와 "drive()"가 있다.
      Vehicle car = new Car(); <-- 이 경우에, car.gasDisplaement와 car.drive() 를 쓸 수가 없다 
package polymorphism;

public class Test {
	public static void main(String[] args) {
		Vehicle car = new Car();
		car.move();
		System.out.println();
		
		Vehicle train = new Train();
		train.move();
		System.out.println();
		
		Vehicle air = new Airplane();
		air.move();
		System.out.println();
	}
}
부릉부릉

칙칙폭폭

위잉위잉

 

[2] 인터페이스를 사용하는 경우

- 위와 같이만 만들 경우에는 사실 다형성의 장점인, '느슨한 결합'을 온전히 지키기 어렵다.

- 왜냐하면 여전히 부모의 객체를 수정할 때, 자식에게 미치는 영향이 있기 때문에 (객체 간 의존성 ↑) 자식 입장에서 부모의 정보를 잘 알고 있어야 하기 때문이다.

- 그렇기에 공통 기능, '움직인다(move)'를 뽑아 movable인터페이스를 만드는 것이 느슨한 결합을 만들기에 더 좋다.

package polymorphism;

public interface Movable {
	public abstract void move();
}

class Car implements Movable{

    @Override
    public void move() {
    	System.out.println("부릉부릉");
    }
}

class Train implements Movable{
    
    @Override
    public void move() {
    	System.out.println("칙칙폭폭");
    }
}

class Airplane implements Movable{
    
    @Override
    public void move() {
    	System.out.println("위이위잉");
    }
}

- 메인 함수에서의 출력

package polymorphism;

import java.util.Arrays;
import java.util.List;

public class Test {
	public static void main(String[] args) {
            List<Movable> movables = Arrays.asList(new Car(), new Airplane(), new Train());
            for (Movable one : movables) {
                one.move();
            }
	}
}

 

3. 함수형 인터페이스

함수형 인터페이스는 한 개의 추상 메소드를 가진 인터페이스를 말한다.

함수형 인터페이스는 람다식을 통해 간편하게 사용할 수 있는데,

앞서 쓴 movable 인터페이스를 람다식으로 바꿔 쓰면 아래와 같다.

// 함수형 인터페이스 사용
public class Test {
	public static void main(String[] args) {
            List<Movable> movables = Arrays.asList(new Car(), () -> System.out.println("다그닥다그닥"));
            for (Movable one : movables) {
                one.move();
            }
	}
}

 

4. 객체 타입을 확인하는 instanceof, 지양해야할까?

instanceof 를 통해 부모참조변수가 참조하는 인스턴스가 무엇인지 알 수 있다.

그런데 instanceof를 사용하는 것을 지양하라는 이야기가 있다. 왜 그런 걸까?

아래의 코드를 보면, instanceof를 사용해서 실제 참조하는 인스턴스가 무엇인지 볼 수 있다.

class Test {
    public static void main(String[] args) {
        List<Movable> movables = Arrays.asList(new Car(), new Airplane(), new Train());

        for (Movable one : movables) {
            if (one instanceof Car) {
                System.out.println("This is a Car");
                one.move();
            } else if (one instanceof Airplane) {
                System.out.println("This is an Airplane");
                one.move();
            } else if (one instanceof Train) {
                System.out.println("This is a Train");
                one.move();
            }
        }
    }
}

이렇게 실제 인스턴스를 보게 주게 되면 외부의 객체에 정보를 고스란히 노출하는 것이기 때문에, 캡슐화가 깨지게 된다.

캡슐화란, 객체가 가진 상태나 행위를 다른 이가 사용하거나 보지 못하도록 숨기는 것

[출처] https://tecoble.techcourse.co.kr/post/2021-04-26-instanceof/

따라서, 되도록 instanceof는 사용하는 것을 지양하는 것이 좋다.

 

 

[ 참고 ]

자바의 정석

스프링 입문을 위한 자바 객체 지향의 원리와 이해

instanceof의 지양, https://tecoble.techcourse.co.kr/post/2021-04-26-instanceof/

https://tecoble.techcourse.co.kr/post/2020-10-27-polymorphism/

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=next7885&logNo=130068105493 

https://life-with-coding.tistory.com/485

http://www.tcpschool.com/java/java_polymorphism_concept

https://secretroute.tistory.com/entry/140819

https://todayscoding.tistory.com/16

'Language > Java' 카테고리의 다른 글

JAVA 라이브러리 - Java.lang 패키지  (0) 2022.08.08
객체지향 언어의 특징 4_캡슐화 (정보 은닉)  (0) 2022.07.22
객체지향 언어의 특징 2_상속 (재사용 + 확장)  (0) 2022.07.20
객체지향 언어의 특징 1_추상화 (모델링)  (0) 2022.07.20
객체지향 프로그래밍이란?  (0) 2022.07.18
    'Language/Java' 카테고리의 다른 글
    • JAVA 라이브러리 - Java.lang 패키지
    • 객체지향 언어의 특징 4_캡슐화 (정보 은닉)
    • 객체지향 언어의 특징 2_상속 (재사용 + 확장)
    • 객체지향 언어의 특징 1_추상화 (모델링)
    simDev1234
    simDev1234
    TIL용 블로그. * 저작권 이슈가 있는 부분이 있다면 댓글 부탁드립니다.

    티스토리툴바