본문 바로가기
뒷북 정리 (국비 교육)/java

[Java] step09. Extends

by 규글 2022. 4. 19.

step09. Extends

public class Phone extends Object{ // extends Object 는 생략 가능 
	// field
	public int test = 0;
	
	// constructor
	public Phone() {
		System.out.println("Phone()  생성자 호출됨");
	}
	
	// 전화 거는 method 
	public void call() {
		System.out.println("전화를 걸어요");
	}
}

 Extend는 사전적으로 '넓히다, 확장하다, 연장하다, 포함하다.' 의 의미인데, 수업에서는 '상속' 이라는 의미로도 배웠다. 일단 위의 Phone class는 Object class가 확장된 class이다.(수업에서 강사님은 상속받았다고 표현했다.) 이때 Object class는 class 들의 근간(the root of class)이 되는 것으로, 이를 상속받으면 Object class의 method를 기본으로 가지게 된다고 한다. 모든 class의 위로 올라가게 되면 Object class가 있다고 했다. 그래서 Object class의 경우 extends Object는 생략이 가능하다. 이렇게 확장해서 만든 class를 자식 class, 확장 시킨 주체가 되는 class를 부모 class라고 했다.

 

public class HandPhone extends Phone{
	// constructor
	public HandPhone() {
		System.out.println("HandPhone() 생성자 호출됨");
	}
	
	// method
	public void mobileCall() {
		System.out.println("이동중에 전화를 걸어요");
	}
	// 사진 찍는 method 
	public void takePicture() {
		System.out.println("30만 화소의 사진을 찍어요");
	}
}

 Phone class를 확장한 HandPhone class이다. 이 친구는 Phone class를 확장한 것이기 때문에 Phone class의 method를 사용할 수 있다.

 

public class SmartPhone extends HandPhone{
	// constructor
	public SmartPhone() {
		
		System.out.println("SmartPhone() 생성자 호출됨");
	}
	
	public void doInternet() {
		System.out.println("인터넷을 해요");
	}
	// @Override 는 부모의 method를 재정의한 것이라고 표시 해주는 annotation(주석)
	@Override
	public void takePicture() {
		//super.takePicture();
		System.out.println("1000만 화소의 사진을 찍어요");
	}
}

 다음 SmartPhone class는 HandPhone class를 확장한 것이다. 때문에 HandPhone class의 method를 사용할 수 있다. 우선 이 class에서 등장하는 override는 사전적으로 "기각하다. 우선하다. 중단시키다." 의 의미를 가진다. 즉, 부모 class에 정의되어 있는 method와 같은 이름을 가진 것이 자식 class에도 있다면 부모 class의 method를 중단시키고, 보다 우선하는 method를 재정의하겠다고 표시해주는 annotation(주석)인 것이다.

 그리고 이어서 super를 보도록 하자. 이전에 배웠던 this를 생각해보면 this는 class 내에서 해당 class 자체를 참조할 때 사용했다. 즉, this는 해당 객체 그 자체를 가리키는 것이었다. 비슷하게 super는 부모 class를 가리킨다. 이때, 여러 부모 class가 있어도 그 단계는 무관하다.

 

- 01

		// Phone class로 객체 생성해서 참조값을 지역 변수에 담기 
		Phone p1=new Phone();
		Object p2=new Phone();
		
		// HandPhone class로 객체 생성해서 참조값을 지역 변수에 담기
		HandPhone p3=new HandPhone();
		Phone p4=new HandPhone();
		Object p5=new HandPhone();
		
		// 이미 만들어진 객체의 참조값을 다른 type 으로 받아보기
		Phone p6=p3;
		Object p7=p3;
		//String str=p3; //오류!

 

 이 main method를 실행하면 왼쪽 이미지와 같은 console 창의 메시지를 얻을 수 있다. 가만 보면 신기한 것 같다. HandPhone 객체를 만들었기 때문에 constructor에 작성해둔 메시지가 출력된다는 것은 알겠는데, 부모 class인 Phone class의 constructor에 작성해준 메시지까지 함께 출력이 되고 있다. 왜 그럴까?

 

 

 

 그것은 객체를 만들었을 때, 왼쪽 이미지와 같이 만들어지기 때문이라고 했다. 객체를 만들게 되면 heap 영역에 해당 객체를 만들 때 필요한 부모 class의 객체까지 모두 생성되고, 그 전체가 하나의 참조값으로 표기된다고 한다. 부모 class의 객체가 생성되었기 때문에 해당 constructor가 동작하면서 console 창에서 메시지를 함께 볼 수 있었던 것이다. 그래서 HandPhone 객체는 HandPhone type과 함께 부모 class의 type으로도 변수를 만드는 것이 가능하다.

 

 

- 02

	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;
		
		MainClass02.usePhone(p1);
	}
    
	public static void usePhone(Phone p) {
		p.call();
		SmartPhone sp = (SmartPhone)p;
		sp.doInternet();
	}

 SmartPhone 객체도 마찬가지이다. 부모 class의 이름으로 type을 받는 것이 가능하며, 이전 예제의 경우처럼 console 창에서 모두의 메시지를 확인할 수 있다. 이처럼 하나의 객체가 여러 type이 될 수 있는 것을 Polymorphism(다형성) 이라고 한다. 예를 들어 '나' 라는 사람의 type이 사람, 국민, 백수, 남자 등이 될 수 있는 것과 같은 관점이다.

 

 하지만 new로 생성한 객체는 하나 뿐이기에 참조값은 하나이다. 그런데 각기 다른 type으로 새롭게 변수를 만드는 것이 대체 무슨 소용이 있을까? SmartPhone 하나만 있으면 모든 method를 사용할 수 있는데, 굳이 사용할 수 있는 method를 축소하면서까지 다른 type으로 만드는 것이 무슨 소용이냐는 것이다.

 예시 하단의 method를 보자. 자식 객체는 부모의 type이기도 하다. 그래서 다른 method에서 SmartPhone type을 받으려고 한다면 SmartPhone type인 p1만 가능하다. HandPhone type을 받으려고 한다면 p1과 p2가 가능하다. 인터넷을 사용하는 method는 SmartPhone class에 만들어져 있다. 때문에 이 method를 사용하려고 한다면 Phone type으로는 불가능하다. 그래서 casting으로 type에 변화를 주어서 해결할 수 있다.

 

- 03

		// HandPhone 객체를 생성해서 HandPhone type 지역변수 p1 에 담기 
		HandPhone p1=new HandPhone();
        
		// 인터넷을 해야 한다. 즉 SmartPhone type  객체가 필요하다
		// p1 안에 있는 값을 이용해서 인터냇을 할수 있을까?
		SmartPhone p2=(SmartPhone)p1; // 실행시에 ClassCastException 발생
        
		// 와 신난다 인터넷 하자~
		p2.doInternet();

 하지만 HandPhone 객체를 만들고, 해당 객체를 SmartPhone으로 casting 하는 것은 가능할까? 불가능하다. 이 경우는 HandPhone 객체를 만들면서 Object와 Phone 객체가 함께 만들어지는 것이지만, SmartPhone은 만들어지지 않기 때문에 casting이 불가능하다. 이런 경우에는 실행 시 Class Cast Exception이 발생한다. 이전 예시의 usePhone method에 Phone 객체를 인자로 넣어주어도 같은 exception이 발생한다.

 

 다시금 정리해보도록 하자.

 extends 는 기존 class를 확장해서 새롭게 class를 만드는 것이다. 부모 class와 자식 class 모두 type이 될 수 있다. 하지만 예시의 class를 Me type과 Programmer type으로 받는 것은 큰 차이가 있다. 확장시켜 새로 만든 class를 객체로 만들었지만, 새로 만든 method는 사용할 수 없다. 변수 앞에 선언된 type은 사용 설명서의 역할을 한다. 뭔가 객체를 만들었기 때문에 해당 method를 가지고 있는 것은 맞는데, 설명서가 구식이기 때문에 해당 method를 사용할 수 없는 것이다. 그래도 부모 class에 정의된 기능은 사용할 수 있다.

 기능이 마음에 안들면 기능을 수정할 수 있는데, 이것이 override이다. 혹은 확장한 class에 있는 method와 다른 새로운 method를 만드는 것도 가능하다. 사실 이렇게 누군가 만든 method를 상속받아 사용하는 것은 굉장히 편한 것이다.

 근데 굳이 상속을 받아야 할까? override 때문에라도 상속을 받아야 한다. 일단 method를 만들기는 한데, 그 사용 용도가 다르다면 필요 동작을 재정의할 필요가 있다. 이럴때는 상속을 받아야 한다.

 이렇게 상속받았을 때 super는 부모 class들을 의미한다. 그래서 super에 점을 찍어 부모 class의 method를 불러낼 수 있는데, 이를 override에 넣어서 사용할 경우가 있긴 하다고 했다.

 

 java에서는 자식 type으로 객체를 호출해서 부모 type으로 받는 경우가 많다고 한다. 이러면 프로그래밍이 유연해진다고 했다. 그런데 어떤 의미에서의 유연함인지 와닿지가 않는다. 직접 실무를 하면서 경험하면 자연스럽게 알게 된다고 했다.

 

public class Car {
	// field
	public Engine engine;
    
	// Engine 객체를 인자로 전달 받는 constructor
	public Car(Engine engine) {
		// field에 저장하기
		this.engine=engine;
	}
	// method 
	public void drive() {
		if(this.engine==null) {
			System.out.println("Engine 객체가 없어서 달릴 수 없어요");
			return; // method 끝내기
		}
		System.out.println("달려요");
	}
}

 이전 Phone에 이은 Car class 예시이다. 객체를 생성할 때 Engine 객체를 전달할 수 있으며, 해당 객체를 생성할 때 Engine 객체의 값이 Car class의 engine field의 값이 된다. Constructor가 저렇게 생겼기 때문에 Engine 객체를 올바르게 전달해주지 않는다면 객체가 생성되지 않는다.

 

public class Truck extends Car{
	// Engine 객체를 생성자의 인자로 전달받는다.
	public Truck(Engine engine) {
		// 자식 생성자로 받은 객체를 부모 생성자에 전달해 주어야 부모 객체가 생성된다. 
		super(engine); // super() 는 부모생성자를 가리킨다.
	}
    
	// method 
	public void dump() {
		System.out.println("짐을 쏟아내요");
	}
}

 부모 객체인 Car 객체를 생성하기 위해서는 Car의 constructor도 올바르게 동작해야 한다. 때문에 Engine 객체를 전달해주어야하는데, Truck 객체가 만들어지는 순서보다 Car 객체가 만들어지는 것이 우선인 것 같다. 실제로 예시의 Car class의 constructor, 그리고 Truck class의 constructor super 윗 줄에 console 창에 출력될 메시지를 입력하려고 했더니 Truck class에서 typing 오류를 보여주었다. Truck 객체를 만들기 위해 constructor로 실행 순서가 들어오기 위해서는 부모 class의 constructor를 먼저 실행하여 해당 객체를 생성해야만 가능한 것으로 추정한다.

 

public class CampingCar extends Car{
	// constructor
	public CampingCar(Engine engine) {
		super(engine);
		// TODO Auto-generated constructor stub
	}
	
	// method
	public void doCamping() {
		// 부모 객체의 method 사용 가능, this. 생략 가능
		this.drive();
		// 부모 객체의 field 참조 가능, this. 생략 가능
		Engine e = this.engine;
	}	
}

 

 

 CampingCar class로 만든 객체에서는 부모 class로 만든 객체의 method를 사용할 수 있다. 예시에서 method의 this는 CampingCar와 Car 객체를 모두 포함한 것의 참조값을 의미한다. 실제로 객체를 만들어서 this를 출력해보면 모두 같은 값인 것을 알 수 있다.

 

 

 

// class 예약어 앞에 final 예약어를 붙이면 더 이상 상속이 안된다.
// 마지막 클래스 즉 종단 클래스가 된다. 
public final class SportsCar extends Car{
	// constructor
	public SportsCar(Engine engine) {
		super(engine);
		
	}
    
	// method
	@Override
	public void drive() {
		/*
		 *  재정의(오버라이드) 한 부모 메소드를 호출해야 할지 말지는 
		 *  상황에 따라 다르다. 
		 *  어떤 경우에는 부모의 메소드를 호출해 주지 않으면 객체가 
		 *  제대로 동작을 안하는 경우가 있다.
		 *  그런 경우에는 부모의 메소드를 반드시 호출해 주어야 한다. 
		 */
		super.drive();
		System.out.println("겁나 빨리 달려요!");
	}

}

 class 앞에 final을 붙이면 이 이하로는 더 이상 확장하는 것이 불가능하다. 즉, 예시의 SportsCar class는 더 이상 확장할 수 없는 마지막 class, 종단 class가 되는 것이다. 그리고 부모 class의 method를 override하는 경우는 상황에 따라 다르다고 한다. 어떤 경우에는 부모의 method를 호출해주지 않으면 객체가 제대로 동작하지 않는 경우가 있다고 하는데, 이럴 경우는 super를 이용해서 부모의 method를 호출해주면 된다고 한다.

 

- 04 ~ -13

 이하의 예시들은 이제까지의 내용을 기반으로 한 것들이다. 이전의 내용들과 다를 것이 없으니 쭉 읽어보고 이해에 무리가 없다면 좋겠다.

		// 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 에는 참조값이 들어 있으므로 정상적으로 사용가능

 

		// SmartPhone 객체를 생성해서 Phone type 지역변수 p1 이라는 지역 변수에 담기
		Phone p1=new SmartPhone();
		// p1 에 들어 있는 참조값을 이용해서 전화를 걸고 싶다면?
		p1.call();
		// p1 에 들어 있는 참조값을 이용해서 이동중에 전화를 걸고 싶다면?
		// p1 에 들어 있는 참조값을 HandPhone type 지역변수에 casting 해서 담는다.
		HandPhone p2=(HandPhone)p1;
		// 그러면 이동중에 전화를 걸수 있다.
		p2.mobileCall();
		
		// p1 에 들어 있는 참조값을 이용해서 인터냇을 하고 싶다면?
		SmartPhone p3=(SmartPhone)p1;
		// 그러면 인터넷을 할수 있다.
		p3.doInternet();

 

		SmartPhone p1=new SmartPhone();
		p1.mobileCall();
		p1.doInternet();
		// SmartPhone 클래스에서 오버라이드된 메소드가 호출된다. 
		p1.takePicture();

 

		// 실행 후 콘솔창을 확인하면 부모 생성자가 먼저 호출되는것을 알수 있다. 
		SmartPhone p1=new SmartPhone();

 

		// Truck 객체를 생성해서 .drive() .dump() 메소드를 호출해 보세요.
		Truck t1=new Truck(new Engine());
		t1.drive();
		t1.dump();
		
		// SprotsCar 객체를 생성해서 .drive() 메소드를 호출해 보세요.
		SportsCar s1=new SportsCar(new Engine());
		s1.drive();

 

	public static void main(String[] args) {
		//여러분들이 Car 클래스를 상속받아서 만든 클래스로 객체를 생성해서
		//아래의 useCar() 메소드를 호출해 보세요.
		SportsCar c1=new SportsCar(new Engine());
		
		MainClass09.useCar(c1);
	}
	
	public static void useCar(Car car) {
		car.drive();
	}

 

		/*
		 *  프로그래밍의 목적 : 영화를 보고 싶다.
		 *  영화를 보는 프로그래밍의 목적을 달성해 보세요.
		 */
		
		Men men=new Men(new Blood("+", "A"));
		men.seeMovie();
		
		Blood b1=new Blood("+", "O");
		Men men2=new Men(b1);
		men2.seeMovie();
		
		new Men(new Blood("-","B")).seeMovie();

 

		/*
		 *  프로그래밍의 목적: 독서를 하고 싶다.
		 *  독서하는 프로그래밍을 해 보세요.
		 */
		Woman w1=new Woman(new Blood("-", "B"));
		w1.reading();

 

	public static void main(String[] args) {
		/*
		 *  프로그래밍의 목적: 아래의 useMen()  메소드를 호출하는것
		 *  아래의 useMen() 메소드를 호출해 보세요.
		 */
		
		//Men 객체를 생성해서 참조값을 m1 이라는 지역 변수에 담기 
		Men m1=new Men(new Blood("+", "O"));
		//useMen() 메소드 호출하면서 Men 객체의 참조값 전달하기 
		MainClass13.useMen(m1);
	}
	
	public static void useMen(Men m) {
		m.walk();
		m.study();
		m.seeMovie();
	}

 

 

'뒷북 정리 (국비 교육) > java' 카테고리의 다른 글

[Java] step11. Interface  (0) 2022.04.19
[Java] step10. Abstract Class  (0) 2022.04.19
[Java] step08. Array  (0) 2022.04.17
[Java] step07. Wrapper Class  (0) 2022.04.17
[Java] step06. Constructor  (0) 2022.04.17

댓글