16일차(1)/java(14) : Extends, 다형성
- extends : 상속(확장)
Step08_Extends 프로젝트 생성
<Phone>
package test.mypac;
//가상의 옛날 가정용 전화기 객체를 생성할 설계도라고 가정
public class Phone {
//생성자
public Phone() {
System.out.println("Phone 생성자 호출됨");
}
//전화 거는 메소드
public void call() {
System.out.println("전화를 걸어요");
}
}
<HandPhone>
package test.mypac;
/*
* 이미 존재하는 Phone 클래스를 extends(상속)받아서 HandPhone 클래스 정의하기
*/
public class HandPhone extends Phone{
public HandPhone() {
System.out.println("HandPhone 생성자 호출됨");
}
//메소드
public void mobileCall() {
System.out.println("이동중에 전화를 걸어요");
}
//사진 찍는 메소드
public void takePicture() {
System.out.println("30만 화소의 사진을 찍어요");
}
}
public class HandPhone extends Phone{}
- 위 Phone 클래스의 자식관계로 작성된 클래스라는 뜻!
<MainClass01>
package test.main;
import test.mypac.HandPhone;
public class MainClass01 {
public static void main(String[] args) {
/*
* Phone 클래스를 상속받은 HandPhone 클래스로 객체를 생성해서
* 그 참조값을 p1이라는 이름의 지역변수에 담기
*/
HandPhone p1=new HandPhone();
/*
* HandPhone 클래스의 생성자를 호출해서 객체를 생성했더니
* Phone 클래스의 생성자도 호출되는 것을 알 수 있다.
*
* 따라서 HandPhone객체도 생성되고, Phone객체도 같이 생성되었다는 것을 유추할 수 있다.
*
* Phone 객체와 HandPhone 객체가 생성되어서 같은 사물함에 들어간다.
* 즉, 하나의 참조값으로 Phone의 기능, HandPhone의 기능을 모두 사용할 수 있다.
*/
p1.call();
p1.mobileCall();
p1.takePicture();
}
}
- MainClass01 을 run 해보면 호출한 건 HandPhone뿐인데 Phone 생성자가 같이 호출되었다.
- 상속관계로 연결되어 있으면 상속관계의 부모클래스로 객체가 먼저 생성된다.
- 이 생성된 객체는(Phone 객체와 HandPhone 객체) 생성되어서 ★같은 사물함에 들어간다.★
즉, 하나의 참조값으로 Phone의 기능, HandPhone의 기능을 모두 사용할 수 있다.
- 상속이 되면 분리되는 것이 아니라 하나로 묶여서 동일한 사물함으로 들어간다. 하나의 사물함 키 사용!
그 사물함 키 하나만 있으면 상속된 클래스의 기능을 함께 사용할 수 있다.
- HandPhone에서 설정한 메소드(파란색)뿐만 아니라 부모(Phone)로부터 물려받은 메소드(빨간색)도 같이 쓸 수 있다.
- 하나의 사물함 키로 같이 꺼내올 수 있는데, 그 사물함 키를 지금 p1에 넣어둔 것!
- 표시하지 않은 나머지들은? object 클래스에 들어있는 것으로 나온다.
- 모든 상속관계를 쭉 위로 거슬러 올라가 보면 object 클래스가 있다.
→ object 클래스는 모든 클래스에서 기본적으로 상속받게 되어 있다.
- 어떤 클래스도 상속받지 않으면 자동으로 extend Object가 들어간다고 생각하면 된다!(생략되어 있는 것)
- Phone을 상속받은 Handphone에는 Object로부터 상속받은 객체가 없다.
하지만 Phone이 기본적으로 Object를 상속받았기 때문에 Object에 정의된 기능도 전부 사용할 수 있다.
- 동일한 사물함에 object, phone, handphone 클래스에서 생성된 기능이 다 들어가 있는 것!
- 객체에 기본적으로 있는 다양한 기능들은 모두 object로 물려받은 기능들이다.
java의 모든 클래스에는 object로 물려받은 기능들이 있다.
- call이라는 기능은 그대로 물려받고, mobileCall, takePicture 는 갖고 있음.
call이라는 기반기능은 더 만들 필요가 없고, 추가로 기능을 확장한 것이다.
→ 부모클래스 phone이 가지고 있었던 기능도 같이 쓸 수 있다.
- 만약 잘 만들어진 특정 클래스가 있다면, 그걸 상속해서 어떤 클래스를 정의하면
내가 만든 클래스(또는 객체)가 훨씬 더 다양한 기능을 가질 수 있다.
ex) 프로그래밍을 잘하는 누군가가 멋진 기능을 가진 Wonderful이라는 클래스를 만들어 놓았다면,
그러면 우리는 그 클래스를 상속받는 클래스를 만들어서 객체를 생성하면
내가 만든 클래스로 생성한 객체도 멋진 기능을 수행할 수 있다!
public MyClass extends Wonderful{
public void doSomething(){
}
}
MyClass m=new MyClass();
m.doSomething
m.xxx
- 부모 클래스 Wonderful에서 물려받은 기능들을 더 다양하게 사용할수 있을 것!
Q) 그럼 그냥 new Wonderful 해서 쓰면 안되는지?
A) 보통 저런 멋진 코드는 유틸리티 개발자/프레임워크 개발자가 주로 만들텐데,
내가 만들고자 하는 것을 그 사람들이 정확히 다 만들 수는 없다.
다른 사람들이 만든 클래스를 상속받은 다음, 내 상황에 맞게끔 기능을 붙여서 사용하는 것이 가장 이상적일 것.
없는 메소드는 추가로 넣고, 있는 기능을 수정하고, 덮어쓰기(override)하기도 하면서...
<MainClass02> : 다형성(polymorphism)
package test.main;
import test.mypac.HandPhone;
import test.mypac.Phone;
/*
* 지역변수가 필드 앞에 선언하는 data type은(참조 데이터타입)
* 그 안에 들어있는 참조값을 사용 설명서라고 생각하면 된다.
* 그렇기 때문에 그 지역변수나 필드에 . 을 찍으면 사용설명서에 명시된 기능만 사용할 수 있다.
* java의 모든 객체는 다형성을 가질 수 있다.
* 다형성은 type이 여러개라는 의미이다.
*/
public class MainClass02 {
public static void main(String[] args) {
//다형성 확인하기!
HandPhone p1=new HandPhone();
//어떤 객체의 참조값을 부모type 으로 받을 수 있다.
Phone p2=new HandPhone();
Object p3=new HandPhone();
}
}
ex) 드릴이라고 써있는 박스가 있다.
안에는 드릴이 아니고 드릴이 들어있는 사물함 키가 들어있다
→ heap영역의 20번 사물함을 찾아가서 안에있는 드릴을 찾아서 사용
- 박스 : 변수or필드
- 박스의 이름(드릴) : data type
- 박스를 열어봤는데 키가 없으면 : null
- 사물함 번호(20) : id(참조값)
Phone p2=new HandPhone();
- HandPhone 객체를 생성한 것. 부모타입으로도 받아진다!
- 한 객체의 참조값을 다양한 타입으로 받을 수 있다.
- 한 객체가 다양한 타입일 수 있다! 이것을 다형성이라고 부른다.
상속 관계이기 때문에, handphone은 phone 타입이기도 하고 object 타입이기도하다.
ex) 어떤 사람이 남자type, 인간type, 학생type 이기도 한 것처럼!
ex) 참치김밥은 김밥type 이면서 음식type 이기도 하다.
- 박스가 하나 있다.
박스에 참치김밥이라고 써있으면 참치김밥밖에 못 담는다.(소고기김밥x)
박스에 김밥이라고 써있으면 다른 김밥을 다 담을 수 있다.
박스에 먹을것이라고 써있으면 먹을것을 다 담을 수 있다.
- 박스가 3개가 있고, heap영역 24번 사물함에 참치김밥이 들어있다.
사물함 키(24)는 3개의 박스 모두에 담을 수 있다.
그러면 참치김밥은 다양한 타입을 갖고 있다고 할 수 있다.
- 박스를 사용하는 사용자 입장에서는?
'먹을것 상자'에서 꺼낸다면 김밥이 아닌 '먹을것'이라고 생각하고 사용해야 한다.
'김밥 상자'에서 꺼낸다면 참치김밥이 아닌 '김밥'이라고 생각하고 사용해야 한다.
Phone p2=new HandPhone();
- 이 코드의 경우, HandPhone이라는 객체를 생성했지만 Phone이라는 type의 변수에 담았다.
→ Phone이라는 박스에 담겨있기 때문에 HandPhone의 기능은 사용할 수 없다!
- 실제로는 HandPhone임에도 불구하고, 객체는 HandPhone의 기능을 다 가지고 있지만,
p3 변수에 지정한 사용설명서(type)가 object이므로 object로 간주하고 사용해야 한다.
Q) 그러면 왜 phone, object type에 담는가?
기능을 동일하게 사용하지 못할거라면 HandPhone객체를 생성해서 HandPhone 타입에 담는게 좋지 않나?
A) HandPhone 객체를 생성한 다음 부모 클래스의 타입으로 받는 것인데,
이렇게 부모 타입으로 사용하면 프로그래밍이 유연해진다.
ex) 집에올때 먹을것 사가지고 와~
집에올때 김밥 사가지고 와~
집에올때 김*네에서 파는 참치김밥 사가지고 와~
→ 이 세 가지를 비교해보면 심부름의 종류가 완전히 다르다.
'먹을것'이라고 지정했을 때 선택권이 넓어지고, 유연해진다.
부모타입(더 넓은 타입)을 사용하면 프로그래밍이 유연해진다. 지정한 변수에 담을 수 있는 가짓수(선택지)가 많아진다!
ex) 배열을 만든다고 할 때,
먹을 것 배열 { 라면, 김밥, 피자, ... } → 먹을것이면 무엇이든 담을 수 있다.
김밥 배열 { 소고기김밥, 참치김밥, 야채김밥, ... } → 김밥밖에 못 담는다.
참치김밥 배열 {김밥*국 참김, 김*네 참김, ... } → 오직 참치김밥만 담을 수 있다.
- 3개의 객체가 만들어지는 것을 확인할 수 있다. (new가 3개!) 참조값 모두 다르다.
- 각각 같은 사물함 안에 부모 객체의 기능도 만들어져 있지만 하나의 객체로 인식한다.
ex) 김밥으로 설명하자면...
김밥이 3개 만들어진 것. 다 다른 사물함에 들어있기 때문에 다 다른 참조값을 가지고 있는 것.
<MainClass03> : 자식타입→부모타입 담기
package test.main;
import test.mypac.HandPhone;
import test.mypac.Phone;
public class MainClass03 {
public static void main(String[] args) {
//HandPhone객체를 생성해서 그 참조값을 p1이라는 이름의 HandPhone type 지역변수에 대입하기
HandPhone p1=new HandPhone();
//p1안에 있는 참조값을 p2라는 Phone type 지역변수에 대입하기
Phone p2=p1;
//p1안에 있는 참조값을 p3라는 Object type 지역변수에 대입하기
Object p3=p1;
//자식 객체의 참조값은 부모 type 변수나 필드에 자연스럽게 담긴다.
}
}
<MainClass04> : 부모타입→자식타입 담기
package test.main;
import test.mypac.HandPhone;
import test.mypac.Phone;
public class MainClass04 {
public static void main(String[] args) {
//HandPhone 객체를 생성해서 그 참조값을 Object type p3 지역변수에 담는다.
Object p3=new HandPhone();
//Object type변수에 담긴 값을 Phone type 변수에 담기
Phone p2=(Phone)p3; //casting 연산자를 이용하면 가능하다
HandPhone p1=(HandPhone)p3; //casting 연산자를 이용하면 가능하다
}
}
- 여기에서 객체는 1개 만들어진다. 객체는 new할때 만들어지는 것!(위의 예제와 비교)
- 지역변수가 3개 만들어진 것이지 객체가 3개 만들어진 건 아니다.
ex) 김밥으로 설명하자면...
김밥은 한 사물함에 1개만 만들어졌다. 참조하는 공간(키를 담아둔 곳)이 3개인 것뿐!
- 자식타입을 부모타입에 담는 것은 문제없이 잘 담긴다. (HandPhone → Phone → Object)
- 더 좁은 범위의 타입을 더 큰 범위의 타입에 담는 것이므로!
- 하지만 반대로 부모타입을 자식타입에 담는것은 문제가 있다!
(항상 문제가 있는건 아니지만 문제가 생길 가능성이 있다)
- 넓은(포괄적인)범위에 있는 것을 좁은 범위에 담으려고 하면 안 된다
ex) 김밥 박스의 물건을 참치김밥 박스에 담으면 안 된다. 다른김밥이면 어떡해?
- 하지만 우리는 이게 Phone 타입이기 때문에 담아도 문제가 없다는 것을 알고 있다.
- 이럴 경우에는 프로그래머의 책임하에서 타입을 캐스팅casting해서 사용할 수 있다.
(Phone) 이라고 표기두면 Phone타입이라고 간주하는 것.
HandPhone p1=(HandPhone)p3;
- type을 전환한다는 것은 이 객체에 대한 사용설명서를 교체하는 효과!
(phone타입이라고 되어있지만 사실 Handphone타입이라고 알려준다.)
- p3 지역변수는 object로 정의되어 있어서, object에 해당하는 기능밖에는 쓸 수 없다.
- 하지만 p2를 Phone이라고 가정하고 담아둔다면, p2. 점을 찍으면 Phone의 기능을 사용할 수 있게 된다.
동일한 키 값에 대한 사용설명서를 교체한 것이다.
- 마찬가지로 Phone이라고 정의했더라도 HandPhone으로 캐스팅한 후 p1.에 점을 찍으면
HandPhone에서 정의한 모든 기능을 다 사용할 수 있다.
- 유연한 프로그래밍을 위해 부모객체로 받아두었다가, 필요시에 타입을 캐스팅해서 사용하면 된다.
- 대신 캐스팅은 확실히 그 타입이 맞을때 사용해야 하므로 주의!
'국비교육(22-23)' 카테고리의 다른 글
17일차(1)/java(16) : 상속, 다형성, 접근지정자 정리 (0) | 2022.10.28 |
---|---|
16일차(3)/java(15) : Extends, 접근 지정자 (0) | 2022.10.27 |
[참고] github 저장소 README.md 파일 생성 / Pull (0) | 2022.10.27 |
16일차(1)/java 퀴즈 : Array, Random 활용 예제 (0) | 2022.10.27 |
15일차(3)/java(13) : Array, for문, 확장 for문 (0) | 2022.10.26 |