티스토리 뷰

728x90
반응형

자바에는 '상속'이라는 개념이 있다.  상속은, 자바의 중요한 특성들 중 하나이며 상속을 사용하는 이유는 좋은 프로그램을 만들기 위해서이다. 

 

자식 클래스는, 상속을 받고싶은 부모 클래스를 선택하여 상속을 받는다.

상속을 받으면, 부모 클래스의 필드와 메소드를 모두 사용할 수 있다. 

 

예제를 통해 쉽고 빠르게 알아보자!


Phone.java - call()을 메소드로 갖는 Phone클래스

package test.mypac;

public class Phone{
	//전화 건는 메소드
	public void call() {
		System.out.println("전화를 걸어요");
	}

}

 

다음은 전화를 걸 수 있는 call 메소드를 가지는 Phone 클래스이다. Phone에서 진화한 개념이 무선 전화가 가능한 핸드폰이다.폰 클래스를 상속받은 핸드폰 클래스를 만들어보자.


HandPhone.java - Phone클래스를 상속받고 mobileCall()과 takePicture()을 메소드로 갖는 SmartPhone클래스

package test.mypac;
//Phone 클래스를 확장해서 HandPhone 클래스를 정의한다.
//Phone 클래스를 상속 받아서 자식 클래스 HandPhone을 정의한다.
public class HandPhone extends Phone{
	//이동 중에 전화를 거는 메소드
	public void mobileCall() {
		System.out.println("이동 중에 전화를 걸어요!");
	}
	
	//사진 찍는 메소드
	public void takePicture() {
		System.out.println("30만 화소의 사진을 찍어요");
	}
}

 


SmartPhone.java - HandPhone 클래스를 상속받고 doInternet() 메소드를 갖는 SmartPhone 클래스

package test.mypac;

public class SmartPhone extends HandPhone{
	
	public void doInternet(){
		System.out.println("인터넷을 해요!");
	}
}

 

부모 클래스를 상속받을 때는 " 자식클래스 extends 부모클래스 "로 작성한다. 

 

핸드폰 클래스는 폰 클래스를 상속받았고, 스마트폰 클래스는 핸드폰 클래스를 상속받았다.폰 - 핸드폰 - 스마트폰 에서 느꼈겠지만, 자식 클래스일 수록 기능, 즉 method가 더 많다.

 

그러면, 예제를 통해 이 세 개의 클래스에 대해 알아보자.


1) "자식 클래스는 부모 클래스의 메소드와 필드를 사용할 수 있다."

MainClass01.java

package test.main;

import test.mypac.HandPhone;

public class MainClass01 {
	public static void main(String[] args) {
		HandPhone p1 = new HandPhone();
		//상속받은 부모클래스 (Phone)에 정의된 메소드 사용 가능
		p1.call();
		p1.mobileCall();
	}

}

p1은 HandPhone 객체의 참조값을 담은 HandPhone 타입의 지역변수이다. HandPhone 클래스가 Phone 클래스를 상속받았기 떄문에, Phone의 메소드인 call을 호출할 수 있는 모습을 확인할 수 있다. 

 

 

 

한가지 "중요한 사실"이 있다.

Phone 클래스는 언뜻 보기에 어떤 클래스도 상속받지 않은 것처럼 보이지만, 사실은 Object라는 클래스를 상속받았다.

모든 클래스 Object 클래스를 기본적으로 상속받고, Object 클래스의 상속은 extends를 생략할 수 있다.

따라서, Phone 클래스는 다음과 같이 작성할 수도 있다. 

package test.mypac;

public class Phone extends Object{
	//전화 건는 메소드
	public void call() {
		System.out.println("전화를 걸어요");
	}

}

 

Phone에 있는 call 메소드를 제외하더라도, Phone 객체를 생성하고 .을 찍으면 다양한 기본 메소드를 확인할 수 있었다. 

그 이유는 Object를 기본적으로 상속받아, Object 클래스에 있는 메소드들을 사용할 수 있기 때문이다. 

 

자바 단일상속만 가능하기 때문에, HandPhone extends Phone, Object와 같이 작성할 수는 없다.

하지만, Phone을 상속받은 HandPhone의 객체는 Object의 기본 메소드들도 사용할 수 있는데, 그 이유는, Object를 상속받은 Phone을 상속받았기 때문이다.

 


2) 자바의 특성 - 다형성(polymorphism)

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

자바에서는 이러한 다형성을 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있도록 하여 구현하고 있다.

다형성은 상속, 추상화와 더불어 객체 지향 프로그래밍을 구성하는 중요한 특징 중 하나이다.

 

그렇다면 다형성을 예시로 쉽게 살펴보자.

 

MainClass02.java - 자바의 모든 객체는 최소 두가지 이상의 타입을 가진다.

package test.main;

import test.mypac.HandPhone;
import test.mypac.Phone;

public class MainClass02 {
	public static void main(String[] args) {
		//변수 타입은 설명서 역할을 한다고 생각할 수 있다. 

		//Phone 클래스로 객체를 생성해서 참조값을 2가지 type의 지역변수에 담기
		Phone p1 = new Phone();
		Object p2 = new Phone();
		
		//HandPhone 클래스로 객체를 생성해서 참조값을 3가지 type의 지역변수에 담기
		HandPhone p3 = new HandPhone();
		Phone p4 = new HandPhone();
		Object p5 = new HandPhone();
		
		//이미 만들어진 객체의 참조값을 다른 type 변수를 만들어서 담아보기 
		Phone p6 = p3;
		Object p7 = p3;
		
	}

}

위에서, extends가 없으면 어떠한 클래스도 상속받지 않은 것처럼 보이지만, 사실은 extends Object가 생략되어 있다고 공부했다. 

 

자바는 객체를 생성해서 지역변수에 참조값을 담을 때, 자기 자신의 type의 지역변수와 부모 type의 지역변수에 모두 담을 수 있다.

 

따라서, 위의 예시에서 Phone 객체를 생성해서 Phone타입과 Object 타입의 지역변수에 참조값을 담은 모습을 확인할 수 있다.

마찬가지로, HandPhone 객체는 HandPhone, Phone, Object 타입의 지역변수에 담을 수 있다. 

 

 

지역변수는, "사용설명서"라고 이해하면 쉽다. 

얘들들어, p4에 대해서 살펴보면, 만들어진 객체는 HandPhone 객체이지만, Phone 타입의 지역 변수에 담았기 때문에, Phone타입에 속한 메소드와 필드만 사용이 가능하다.

이렇게 부모 타입의 지역 변수에 담는 이유는 "유연성" 때문인데 이에 관한 내용은 나중에 다뤄보도록 하겠다. 

 

이렇게, 하나의 객체가 최소 두가지 이상의 타입을 가지는 것을 바로 자바의 다형성이라고 한다.

 

 

또한, 예제 코드에서 이미 만들어진 객체의 참조값을 다른 type변수에 담을 수 있음 역시 확인할 수 있다. 

더 자세한 것은 다음 예제에서 살펴보자


3) 하나의 객체를 생성하여 다양한 type의 지역변수에 참조값 담아보기

MainClass03.java

package test.main;

import test.mypac.HandPhone;
import test.mypac.Phone;
import test.mypac.SmartPhone;

public class MainClass03 {
	public static void main(String[] args) {
		//SmartPhone 객체를 생성해서 SmartPhone type의 지역 변수 p1에 담기
		SmartPhone p1 = new SmartPhone();
		//p1에 담긴 참조값을 HandPhone type 지역 변수 p2에 담기
		HandPhone p2 = p1;
		//p1에 담긴 참조값을 Phone type 지역 변수 p3에 담기
		Phone p3 = p1;
		//p1에 담긴 참조값을 Object type 지역 변수 p4에 담기
		Object p4 = p1;
		
	}

}

이 코드에서, 생성된 객체는 단 하나이다.

p2는 핸드폰 타입이므로, 스마트폰 객체의 참조값을 참조해도, 핸드폰의 기능만 사용할 수 있다.

p3 역시 폰 타입이므로, 스마트폰 객체를 참조해도 폰 기능만 사용할 수 있다.

p4는 오브젝트 타입이므로, 오브젝트 기능만 사용할 수 있다. 

 

자식 객체를 생성하여 부모 type의 지역변수에 대입하는 것은 별다른 코드를 작성할 필요없이 잘 되는 모습이다.

그렇다면 부모 객체를 자식 객체에 담고 싶다면 어떻게 해야할까?

MainClass04를 살펴보자.


4) "부모 객체를 자식 객체에 담고 싶다면 casting이 필요하다!"

 

스마트폰 객체를 생성해서 폰타입 지역변수 p1에 담아보자.

Phone p1 = new SmartPhone();

 

p1을 다시 스마트폰 객체에 담으려면 casting이 필요하다.

SmartPhone p2 = (SmartPhone)p1;

 

p1에서는 폰 기능만 사용가능했지만, p2 스마트폰 기능을 모두 사용할 수 있다.

//모든 기능 사용 가능!
p2.call();
p2.mobileCall();
p2.takePicture();
p2.doInternet();

5) 캐스팅을 통한 형변환 

 

MainClass04.java 

package test.main;

import test.mypac.HandPhone;
import test.mypac.Phone;
import test.mypac.SmartPhone;

public class MainClass04 {
	public static void main(String[] args) {
		//자식 type 객체를 생성해서 부모 type 지역변수에 대입
		Phone p1 = new SmartPhone();
		//부모 type 객체를 자식 type 변수에 담을 때는 casting(형 변환)이 필요하다.
		SmartPhone p2 = (SmartPhone)p1;
		//모든 기능 사용 가능!
		p2.call();
		p2.mobileCall();
		p2.takePicture();
		p2.doInternet();
		
		
		
		usePhone(new Phone());
		usePhone(new HandPhone());
		usePhone(new SmartPhone());
	}
	public static void usePhone(Phone p) {
		//매개변수가 SmartPhone 타입이면 SmartPhone타입만 대입 가능
		//하지만 Phone 타입이면 Phone, HandPhone, SmartPhone 타입 모두 가능
		//즉, 자식타입을 사용하니 반드시 그 타입만 사용해야 함.
		//따라서, 유연성이 떨어지므로 부모 타입을 사용하고 필요에 따라 casting함
		//하지만 casting에는 책임이 따른다. => MainClass05
		//사용하는 기능은 call()만 사용한다.
		p.call();
		
	}
}

usePhone에 Phone 타입을 넘겨주도록 명시되어 있으면 (usePhone(Phone p))

 

usePhone(new Phone());
usePhone(new HandPhone());
usePhone(new SmartPhone());

 

모두 가능하다.

 

하지만, usePhone에 스마트폰 타입을 넘겨주도록 명시되어 있으면 (usePhone(SmartPhone p))

 

useePhone(new SmartPhone());

 

만 가능하다.

 

 

이번 예제는 자식 type 객체를 생성해서 부모 type 지역변수에 담고, 그 지역변수를 casting을 통해, 다시 자식 type의 변수에 담는 내용이었다.

어렵게 들릴수도 있지만 결국은 객체는 스마트폰을 생성해서 폰타입에 담았다가 다시 스마트폰 타입에 담았다는 이야기이다. 

처음에 생성한 객체가 스마트폰 타입으로, 자식 타입이었기 때문에 캐스팅으로 폰에서 스마트폰 지역변수에 담아주면 스마트폰의 기능을 모두 사용할 수 있었다. 

 

 

그렇다면, 애초에 부모 타입의 객체를 생성해서 자식 타입의 변수에 담으면 어떠한 기능들을 사용할 수 있을까? 

다음 예제가 이와 관련된 예제이다.


6) "캐스팅에는 대가가 따른다."


"어린 자녀가 스마트폰이 갖고 싶다고 조른다."

"이번 달 생활비가 모자란데 방법이 없을까?"

"구형 전화기를 주면서 스마트폰이라 우기며 스마트폰 사용설명서를 줘보자."


 

위의 내용을 코드로 표현하면 다음과 같다.

Phone p1 = new Phone();
SmartPhone p2 = (SmartPhone)p1;

 

스마트폰의 기능인 doInterneet 메소드를 p2에서 호출하려했더니 ClassCastException이라는 오류가 뜬다.

 

캐스팅을 해서 형변환을 했지만, 결국 객체 자체가 폰 타입이기 때문에 자식 클래스의 메소드와 필드는 사용할 수 없다. 

 

 


MainClass06.java

package test.main;

import test.mypac.SmartPhone;

public class MainClass06 {
	public static void main(String[] args) {
		//SmartPhone type의 지역변수 p1을 만들 준비만하고 만들어지지 않은 상태
		SmartPhone p1;
		//SmartPhone type의 지역변수 p2를 만들고 비워둔 상태(참조값이 담기지 않은)
		SmartPhone p2 = null;
		//SmartPhone type의 지역변수 p3를 만들고 참조값을 넣어둔 상태
		SmartPhone p3 = new SmartPhone();
		
		//p1은 아직 만들어지지 않았기 떄문에 문법이 성립하지 않는다.
		//p1.call();
	
		//p2는 비어있는(null이 들어있는) 상태이기 때문에
		//실행 시(runtime 시)에 NullPointerException이 발생한다.
		//p2.call();
		
		//p3에는 참조값이 들어 있으므로 정상적으로 사용 가능
	}

}

객체의 생성과 관련된 예제이다. 앞에서 많이 다뤄본 형식이므로 한번 읽어보고 넘어가면 좋을 것 같다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함