다형성은 객체지향 프로그래밍의 꽃!

 

다형성(Polymorphism)이란?

다형성은 이름 그대로 다양한 형태, 여러 형태를 의미한다.

프로그래밍에서 다형성은 한 객체가 여러 타입의 객체로 취급될 수 있는 능력을 의미한다.

보통 하나의 객체는 하나의 타입으로 고정되어 있는데, 다형성을 사용하면 하나의 객체가 다른 타입으로 사용될 수 있다.

 

  • 다형성을 이해하기 위해 알아야할 개념
    • 다형적 참조 : 부모는 자식을 품을 수 있다.
    • 메서드 오버라이딩

 

 

다형적 참조 : 부모는 자식을 품을 수 있다

// Parent가 Child이 부모 클래스일 때

// 가능 
// 부모 타입의 변수가 자식 인스턴스를 참조
Parent poly = new Child(); 

// 불가능, 컴파일 오류 발생
Child poly = new Parent();

위 코드처럼 Child 인스턴스를 Parent 타입으로 설정이 가능하고, poly.parentMethod() 실행시 아래와 같은 과정으로 함수가 호출된다.

 

 

자바에서 부모 타입은 자신은 물론이고, 자신을 기준으로 모든 자식 타입을 참조할 수 있다. 이처럼 다양한 형태를 참조할 수 있다고 해서 다형적 참조라고 한다.

 

다형적 참조의 한계

Parent poly = new Child(); 인 상태에서 poly.childMethod()를 호출하면 컴파일에러가 발생한다. 왜냐하면 poly의 타입은 Parent이고, 여기에 찾는 메서드가 없다면 Parent의 부모를 탐색한다(상속 관계는 부모로만 찾아서 올라갈 수 있기 때문). 하지만 childMethod()는 Parent의 자식 클래스에 있는 메서드이므로 부모에서 자식으로 함수를 호출할 수 없다. 따라서 poly.childMethod()를 찾지 못해 컴파일 에러가 발생한다.

 

 

캐스팅

  • 업캐스팅 : 자식 타입 → 부모 타입 으로 형변환
  • 다운캐스팅 : 부모 타입 → 자식 타입 으로 형변환
((Child) poly).childMethod(); // 다운캐스팅을 통해 부모타입을 자식타입으로 변환 후 기능 호출
((Child) 참조값).childMethod(); // 참조값을 읽은 다음 자식 타입으로 다운캐스팅

이때 poly의 타입이 변하는 것이 아니다. poly의 타입은 Parent로 유지되고, poly의 참조값을 꺼내고 꺼낸 참조값이 Child 타입이 된다.

 

 

업캐스팅은 생략할 수 있는 반면, 다운캐스팅은 생략할 수 없다.

이 개념은 잘 알아둬야 한다.

  • 다운캐스팅이 위험한 이유

이유는 다운캐스팅은 잘못하면 심각한 런타임 오류가 발생할 수 있기 때문이다.

Parent parent2 = new Parent();
Child child2 = (Child) parent2; // 다운 캐스팅 -> ClassCaseException 발생
child2.childMethod(); // 실행 불가

위 코드를 실행하면 런타임 오류인 ClassCastException이 발생한다. 이 예외가 발생하는 이유는 다음과 같다.

parent2 변수에 Parent 인스턴스의 참조값을 저장했기 때문에 해당 위치(참조)에는 Child 인스턴스가 없고, Parent 인스턴스만 있을 뿐이다. 이때 Child로 다운캐스팅 후 Child의 메서드를 호출한다고해도 Child 인스턴스 자체가 없기 때문에 ClassCastException(사용할 수 없는 타입으로 다운캐스팅할 때 발생) 예외가 발생한다.

 

→ 다운 캐스팅의 경우, 부모 타입은 모두 함께 생성되지만 자식타입의 인스턴스는 생성되지 않기 때문에 존재하지 않는 하위 타입으로 캐스팅하는 문제가 발생할 수 있다.

 

  • 업캐스팅이 안전한 이유

업캐스팅의 경우, 이런 문제가 절대로 발생하지 않는다. 왜냐하면 객체 생성시 해당 타입의 상위 부모 타입이 모두 함께 생성되기 때문이다. 따라서 메모리 상에 자식 인스턴스 포함 그의 모든 부모 인스턴스가 모두 존재하기 때문에 항상 안전해야 한다. 따라서 업캐스팅을 생략할 수 있다.

 

따라서, 웬만하면 다운캐스팅은 사용하지 말자!

 

instanceof

instanceof는 참조형 변수가 참조하는 대상이 다양할 때, 어떤 인스턴스를 참고하는지 확인하기 위한 키워드이다. 즉, 인스턴스의 타입을 확인할 때 사용한다.

// value 변수의 타입이 B 타입 혹은 B의 자식 타입 -> true
// B b = value(new A())가 성립하면 true
value instanceof B 

// 예시
new Parent() instanceof Parent // true
new Child instanceof Parent // true

 

 

메서드 오버라이딩

메서드 오버라이딩은 기존 기능을 덮어 새로운 기능으로 재정의한다는 뜻이다. 메서드 오버라이딩에서 꼭 기억해야하는 것은 다음과 같다.

오버라이딩 된 메서드가 항상 우선권을 가진다!

즉, 더 하위 자식에서 오버라이딩 된 메서드가 우선권을 갖는 것이다.

 

 

위 그림에서 처럼 Parent타입으로 method()를 호출해도, 그 자식 타입인 Child에서 method()를 오버라이딩 했다면 Child의 method()를 호출한다.

 

정리

다형성에서 기억해야할 것은 두가지다. 다형적 참조와 메서드 오버라이딩!

  • 다형적 참조 : 하나의 변수 타입으로 다양한 자식 인스턴스를 참조할 수 있는 기능
  • 메서드 오버라이딩 : 기존 기능을 하위 타입에서 새로운 기능으로 재정의. 오버라이딩된 메서드가 우선권을 가진다.

 

 

Reference

인프런 '김영한의 실전 자바 - 기본편'

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

[자바/기본] 12. 다형성과 설계  (0) 2024.01.17
[자바/기본] 11. 다형성2  (0) 2024.01.17
[자바/기본] 9. 상속  (0) 2024.01.16
[자바/기본] 8. final  (0) 2024.01.16
[자바/기본] 7. 자바 메모리 구조와 final  (0) 2024.01.16

상속 관계

상속은 객체 지향 프로그래밍의 핵심 요소 중 하나로, 기존 클래스의 필드와 메서드를 새로운 클래스에서 재사용 할 수 있게 해준다. 하나의 대상만 extends 키워드로 상속받을 수 있다.

  • 부모 클래스(슈퍼 클래스)
    • 상속으로 자신의 필드와 메서드 코드를 제공하는 클래스
    • 자식 클래스를 정보를 모르고, 접근이 불가능하다
  • 자식 클래스(서브 클래스)
    • 부모 클래스로부터 필드와 메서드를 상속받는 클래스
    • 부모 클래스 정보를 알고 있고, 접근이 가능하다

 

다중 상속

 

다중 상속이란 하나의 자식 클래스가 두 개 이상의 부모 클래스를 가질 수 있는 것을 의미한다. 하지만 자바에서 다중 상속은 다이아몬드 문제과 클래스 계층 구조이 복잡화? 때문에 불가능하다.

다이아몬드 문제는 같은 메서드 명을 가진 부모메서드 명이 중복될 때 어떤 것을 사용해야 할지 모르는 문제를 의미한다. 위에서 AirplaneCar는 Airplane의 move()를 오버라이드 해야할지, Car의 move()를 오버라이드 해야할지 정할 수 없다. 따라서 자바에서 자식 클래스는 하나의 부모 클래스를 가질 수밖에 없다.

 

상속과 메모리 구조

Car 클래스를 상속받는 ElectricCar가 있는 상황에서 메모리 구조는 다음과 같다.

 

new ElectricCar()를 호출하면 ElectricCar 뿐만 아니라 상속 관계에 있는 Car까지 함께 포함해서 인스턴스를 생성한다. 참조값 x001에 Car, ElectricCar 두가지 클래스 정보가 공존하고 있다.

→ 상속 관계를 사용하면 하나의 참조값에 자식/부모 클래스가 함께 생성되고, 공간도 각각 구분된다.

 

electricCar.charge() 호출

electricCar.charge()를 호출하면 electricCar는 ElectricCar의 인스턴스이므로, ElectricCar의 charge()를 선택해 호출한다.

이때 메서드는 호출하는 변수의 타입(클래스)을 기준으로 선택하는 것을 알 수 있다.

 

electricCar.move() 호출

electricCar.move()를 호출시 move()를 찾는 과정은 다음과 같다.

 

 

x001로 이동 → 호출 타입인 ElectricCar에서 move() 찾음 (없음) → 부모 클래스인 Car에서 move() 찾음 (있음) → Car의 move() 호출

 

핵심

  • 상속 관계의 객체를 생성하면 그 내부에는 부모와 자식 인스턴스가 모두 생성된다.
  • 상속 관계의 객체를 호출할 때, 호출자의 타입(변수의 타입)으로 대상 타입을 찾는다.
    • ex. ElectricCar electricCar = new ElectricCar(); 에서 변수 electricCar의 타입인 ElectricCar가 타겟된다
  • 현재 타입에서 기능을 찾지 못하면 상위 부모 타입으로 기능을 찾아서 실행한다.
    • 기능을 찾지 못하면 컴파일 오류가 발생한다.

 

상속과 메서드 오버라이딩

메서드 오버라이딩(Method Overriding)은 부모에게서 상속받은 기능을 자식이 재정의 하는 것을 의미한다.

  • @Override
    • 상위 클래스의 메서드를 오버라이드하는 것을 나타내는 애노테이션(프로그램이 읽을 수 있는 특별한 주석)이다
    • 실수로 오버라이딩을 못하는 경우를 방지하고 코드의 명확성을 보장할 수 있다
    • 오버라이딩 규칙(메서드명, 오버라이딩 여부)을 지키지 않으면 컴파일 에러가 발생한다

 

메서드 오버라이딩 조건

  • 메서드 시그니처 : 메서드 시그니처가 동일해야 한다(이름, 매개변수 타입, 순서, 개수)
  • 반환 타입 : 반환 타입이 같아야 한다. 반환 타입이 하위 타입일 수 있다.
  • 접근 제어자 : 오버라이딩 메서드의 접근 제어자는 상위 클래스의 메서드보다 더 제한적이어서는 안된다
  • 예외 : 오버라이딩 메서드는 상위 클래스의 메서드보다 더 많은 체크 예외를 throws로 선언할 수 없다.
  • static, final, private : 키워드가 붙은 메서드는 오버라이딩 될 수 없다
  • 생성자 오버라이딩 : 생성자는 오버라이딩 할 수 없다

→ @Override 붙이고, 컴파일 에러 발생하지 않게만 작성하자!

 

super

super - 부모클래스에 대한 참조

부모와 자식의 필드명이 같거나 메서드가 오버라이딩 되어있으면, 자식 클래스의 필드와 메서드가 사용되기 때문에 자식에서 부모의 필드나 메서드를 호출할 수 없다. 이때 super 키워드를 사용하면 부모를 참조할 수 있다. 즉, super를 사용하면 부모의 필드, 메서드에 접근 가능하다.

 

super - 생성자

상속 관계의 인스턴스를 생성하면 메모리 내부에는 자식과 부모 클래스가 각각 다 만들어진다. Child를 만들면 부모인 Parent까지 함께 만들어지는 것이다. 따라서 자식, 부모 각각의 생성자도 모두 호출되어야 한다.

규칙 : 상속 관계를 사용하면 자식 클래스의 생성자에서 부모 클래스의 생성자를 반드시 호출해야 한다

  • 상속 관계의 생성자 호출은 결과적으로 부모 → 자식 순서로 실행된다. 부모 데이터를 먼저 초기화한 후 자식 데이터를 초기화한다.
  • 상속 관계에서 부모의 생성자를 호출할 때는 super(…)를 사용하면 된다. 이때 super는 생성자 첫줄에 위치해야한다(안그럼 컴파일 에러 난다).

 

 

Reference

인프런 '김영한의 실전 자바 - 기본편'

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

[자바/기본] 11. 다형성2  (0) 2024.01.17
[자바/기본] 10. 다형성1  (0) 2024.01.17
[자바/기본] 8. final  (0) 2024.01.16
[자바/기본] 7. 자바 메모리 구조와 final  (0) 2024.01.16
[자바/기본] 6. 접근 제어자  (0) 2024.01.16

 

final

final 키워드가 붙으면 값을 못 바꾼다고 생각하면 된다. 그래서 특정 변수의 값을 할당한 이후에 변경하지 않아야 할 때 final을 사용하면 된다.

  • final 지역변수
    • 지역변수에 final을 설정하면 최초 한 번만 할당할 수 있다.
    • 이후 값을 변경할 수 없다. 변경을 시도할 때 컴파일 에러가 발생한다
    • 매개변수에 final을 설정하면 메서드 내부에서 매개변수의 값을 변경할 수 없다
  • static final - 상수
    • 상수를 만들 때 사용한다
    • static final 변수명은 대문자에 _(언더 스코어)의 형태로 작성해야 한다. CONSTANT_VALUE **처럼 말이다
    • 상수는 공용으로 사용되므로 주로 public을 붙여 public static final 로 많이 사용한다.
    • 상수는 런타임에 변경할 수 없다. 그래서 변경하려면 프로그램 종료 후 코드를 변경한 다음 다시 실행해야 변경사항이 반영된다.
    • 중앙에서 값을 하나로 관리할 수 있다는 장점이 있다

 

final 대신 static final을 사용해야 하는 이유

 

final value = 10; 처럼 필드를 설정한다면 매 인스턴스마다 값이 10이 할당될 것이다. 그런데 변수가 final 이므로 변경도 못하는데 매 인스턴스마다 value = 10 이 할당되어 있으니 결국 메모리 낭비가 될 것이다. 따라서 final에 static 을 붙여서 공용으로 사용하는 값(MY_VALUE = 10)을 만들어 모든 인스턴스가 이 하나를 가지고 사용하면 메모리 낭비가 줄어들 것이다. 따라서 상수를 만들려면 웬만해선 static final로 만들자!

 

final과 상속/오버라이딩

final을 사용하면 클래스는 상속이 불가능하고, 메서드는 오버라이딩이 불가능하다.

 

 

Reference

인프런 '김영한의 실전 자바 - 기본편'

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

[자바/기본] 10. 다형성1  (0) 2024.01.17
[자바/기본] 9. 상속  (0) 2024.01.16
[자바/기본] 7. 자바 메모리 구조와 final  (0) 2024.01.16
[자바/기본] 6. 접근 제어자  (0) 2024.01.16
[자바/기본] 5. 패키지  (0) 2024.01.04

static과 자바 메모리 구조를 모호하게 알고 있었는데, 강의로 완전히 이해한 것 같다. 

static을 알기 위해서는 자바의 메모리 구조를 알아야 한다! 

 

자바 메모리 구조

자바의 메모리 구조는 크게 3가지로 나뉘는데, 각각 메서드 영역, 스택 영역, 힙 영역이 있다.

 

메서드 영역

프로그램을 실행하는데 필요한 **공통 데이터(클래스 정보, static, 상수)**를 관리한다.

이 영역은 프로그램의 모든 영역에서 공유한다.

  • 클래스 정보 : 클래스 실행 코드(바이트 코드), 필드/메서드/생성자 코드 같은 모든 실행 코드
  • static 영역 : static 변수 보관
  • 런타임 상수 풀 : 프로그램 실행에 필요한 공통 리터럴 상수(ex. “hello”) 보관
  • 메서드 영역과 인스턴스

 

스택 영역

실제 프로그램이 실행되는 영역이고, 메서드 호출과 지역변수(매개변수 포함)를 관리한다.

스레드 수만큼 스택 영역이 생성되고, 메서드 실행할 때마다 스택 프레임이 하나씩 쌓인다.

메서드가 종료되면 스택 프레임이 제거되고, 이때 지역 변수도 함께 제거된다.

스택 프레임이 모두 제거되면 프로그램도 종료된다.

  • 스택 프레임

 

힙 영역

객체(인스턴스), 배열이 생성되고 **GC(가비지 컬렉션)**이 이뤄지는 영역이다.

  • GC의 대상이 되는 조건

 

static 변수

static은 공통으로 사용되는 변수 혹은 메서드가 있을 때 사용하는 키워드로, 메서드 영역에서 관리하는 변수이다. 클래스 자체에 소속되어 있다. 멤버 변수나 메서드, 클래스 앞에 붙을 수 있다.

 

  • static 멤버 변수
    • static 변수, 정적 변수, 클래스 변수라고도 부른다
    • 메서드 영역에서 관리 한다
    • 생성주기는 프로그램 실행 시점에 딱 한번 만들어지고, 프로그램 종료 시점에 제거된다

 

  • static 변수 접근 방법
    • 클래스명.변수명 (ex. Data.count)

 

  • 필드 종류
    • 인스턴스 변수
      • static이 붙지 않음
      • 인스턴스를 생성해야 접근할 수 있는 변수이고, 인스턴스에 소속되어 있다
      • 인스턴스 생성할 때마다 새로 생성된다
    • 클래스 변수
      • static이 붙은 멤버 변수
      • 인스턴스 생성 없이 접근 가능한 변수이고, 클래스 자체에 소속되어 있다
      • 자바 프로그램 시작 시 딱 1개만 생성되고, 여러 곳에서 공유될 수 있다

 

static 메서드

메서드만 존재하는 클래스인 경우 굳이 인스턴스를 생성하고 메서드에 접근하는 것이 불필요할 수 있다. 이 경우 클래스 안에 메서드를 static으로 설정해 인스턴스 설정 없이 메서드에 접근할 수 있게 하는 것이 더 좋은 방법일 수 있다.

static 메서드는 클래스 레벨에서 호출할 수 있어서 정적 메서드 혹은 클래스 메서드라고도 한다.

 

  • 예시
public class DecoUtil2 {
	
    public static String deco(String str) {
        return "*" + str + "*";
    }
}

// static 메서드 호출
DecoUtil2.deco("hello"); -> 인스턴스 생성없이 메서드에 접근할 수 있다

 

 

  • 정적 메서드 사용법
    • static 메서드는 static만 사용 가능
      • static 자체가 메서드 영역에 속하기 때문에 같은 클래스 영역에 있는 정적 변수/메서드만 접근 가능
      • static 메서드는 정적 메서드, 정적 변수만 사용 가능
      • static 메서드가 인스턴스 변수/메서드를 사용할 수 없다
    • 모든 곳에서 static을 호출할 수 있다
      • static 메서드는 공용 기능이기 때문에 모든 곳에서 static을 호출할 수 있다

 

정적 메서드가 인스턴스 변수/메서드를 호출할 수 없는 이유

정적 메서드는 클래스의 이름으로 바로 호출하기에 인스턴스처럼 참조값의 개념이 없다. 하지만 인스턴스에 접근하려면 참조값(주소값)을 알아야 하는데, 정적 메서드는 참조값 없이 호출하기 때문에 정적메서드는 인스턴스 변수/메서드를 호출할 수 없다.

 

Reference

인프런 '김영한의 실전 자바 - 기본편'

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

[자바/기본] 9. 상속  (0) 2024.01.16
[자바/기본] 8. final  (0) 2024.01.16
[자바/기본] 6. 접근 제어자  (0) 2024.01.16
[자바/기본] 5. 패키지  (0) 2024.01.04
[자바/기본] 4. 생성자  (0) 2024.01.04

+ Recent posts