관리 메뉴

LC Studio

JAVA & Spring 강의 (자바 인강 4주차) 객체 지향 핵심 본문

Java/Java&Spring 기초 강의

JAVA & Spring 강의 (자바 인강 4주차) 객체 지향 핵심

Leopard Cat 2022. 4. 13. 00:00

클래스 상속

  • 일반적인 클레스로부터 구체적인 클레스로 확장해서 사용하는 것
  • 이미 구현된 클래스보다 더 구체적인 기능을 가진 클래스를 구현하는 것

class B extends A{

}

위와 같은 문법으로 사용하면 됨

 

ex) 포유류 클래스를 상속받는 사람 클래스

class Mammal{

}

class Human extends Mammal{

}


멤버십 시나리오 구현 예제

 

1. Customer class 작성

//Siver을 기본으로 

//하위클래스에서 접근 가능하도록 private가 아닌 protected로 선언

package ch02;

public class Customer {
	
	protected int customerID; //하위 클래스에서는 접근 가능 , protected
	protected String customerName;
	protected String customerGrade;
	int bonusPoint;
	double bonusRatio;
		
	
	public int getCustomerID() {
		return customerID;
	}

	public void setCustomerID(int customerID) {
		this.customerID = customerID;
	}

	public String getCustomerName() {
		return customerName;
	}

	public void setCustomerName(String customerName) {
		this.customerName = customerName;
	}

	public String getCustomerGrade() {
		return customerGrade;
	}

	public void setCustomerGrade(String customerGrade) {
		this.customerGrade = customerGrade;
	}

	public Customer()
	{
		customerGrade = "SILVER";
		bonusRatio = 0.01;
	}
	
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price;
	}
	
	public String showCustomerInfo() {
		return customerName + "님의 등급은 " + customerGrade + "이며, 보너스 포인트는 "+bonusPoint+"입니다."; 
	}
}

2. VIPCustomer Class 작성

//Customer Class 상속

package ch02;

public class VIPCustomer extends Customer {
	
	//상속으로 인해 아래의 중복되는 내용은 써줄필요가 없다
	//private int customerID;
	//private String customerName;
	//private String customerGrade;
	//int bonusPoint;
	//double bonusRatio;
	
	double salesRatio;
	private String agentID;
	
	public VIPCustomer() 
	{
		bonusRatio = 0.05;
		salesRatio = 0.1;
		customerGrade = "VIP";
	}
	
	public String getAgentID() {
		return agentID;
	}

}

출력결과


상속에서 클래스 생성 과정

하위 클래스를 생성하면 상위 클래스가 먼저 생성 됨

위의 예시에서 VIPCustomer Class를 호출하면 자동으로 Customer class가 호출되어 실행

그 역할을 해주는 것이 super 키워드

super()는 상위 클래스의 기본 생성자를 호출 함, 명시하지 않으면 자동으로 호출해줌

Customer()을 주석처리해도 VIPCustomer이 상속받고있기 때문에 실행된다

만약 디폴트 생성자 없애고 매개 변수가 있는 생성자 추가한다면?

public Customer(int customerID, String customerName) {
		this.customerID = customerID;
		this.customerName = customerName;
		
		customerGrade = "SILVER";
		bonusRatio = 0.01;
		System.out.println("Customer(int, String) 생성자 호출");
}

상속받는 VIPCustomer class 에서 super를 이용하여 상위 클래스의 생성자 명시적으로 호출하면 된다.

public VIPCustomer(int customerID, String customerName) {
		super(customerID, customerName);
		
		customerGrade = "VIP";
		bonusRatio = 0.05;
		salesRatio = 0.1;
		
		System.out.println("VIPCustomer(int, String) 생성자 호출");
}

 

형 변환(업캐스팅)

상위 클래스로 변수를 선언하고 하위 클래스의 생성자로 인스턴스를 생성

위의 예제에서,

Customer customerLee = new VIPCustomer();

하위 클래스는 상위 클래스의 타입을 내포하고 있으므로 상위 클래스로의 묵시적 형 변환이 가능함

(VIPCustomer이 Customer로 형 변환)

 

즉, 생성은 되지만 상위클래스로 형 변환했기 때문에 접근할 수 없다.

vc에서 호출 가능한 인스턴스가 Customer의 메서드 뿐이다


메서드 재정의하기(overring)

오버라이딩(overriding) :

상위 클래스에 정의된 메서드의 구현 내용하위 클래스에서 구현할 내용과 맞지 않는 경우 하위 클래스에서 동일한 이름의 메서드를 재정의 할 수 있음

 

상위 클래스의 Customer의 calcPrice 메서드

public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price;
	}

하위 클래스 VIPCustomer의 재정의한 calcPrice 메서드

@Override
	public int calcPrice(int price) {
		// TODO Auto-generated method stub
		bonusPoint += price * bonusRatio;
		price -= (int)(price * salesRatio);
		return price;
	}

위의 예제에서 @Override는 애노테이션(annotation) 주석이라는 뜻이다.

컴파일러에게 특별한 정보를 제공해주는 역할을 한다.

@Override = 재정의된 메서드라는 정보 제공

 

그렇다면,

Customer vc = new VIPCustomer();

위와같이 인스턴스를 생성한 경우,

vc.clacPrice를 호출하면, Customer, VIPCustomer 둘중 어느 클래스의 메서드가 호출될까?

 

정답은 VIPCustomer이다.

 

위를 설명하기 위해서는 가상 메서드의 원리에 대해 알아야한다.

 

가상 메서드의 원리

-가상 메서드 테이블(vitual method table)에서 해당 메서드에 대한 address를 가지고 있다.

-재정의된 경우는 재정의 된 메서드의 주소를 가리킨다.

 

자바의 모든 메서드는 가상 메서드이고, 각 Class별로 가상 메서드 테이블을 가지고 있다.

그리고 클래스의 해당하는 각각의 메서드들은 각각의 메서드 address를 가지고 있다. 

 

각각의 메서드 address를 가지고 있지만, address가 가르키는 메서드 영역은 같을 수 있다.

예를 들어, Customer 클래스를 상속받은 VIPCistomer의 클래스의 메서드의 경우,

재정의 @Override를 하지 않는 이상, Customer 클래스의 메서드와 같은 메서드 영역을 가르키고 있을 것이다.

결론적으로,

Customer vc = new VIPCustomer();

vc.clacPrice  의 경우에는

2가지 상황에 따라 호출되는 메서드가 다르다.

 

1. calcPrice가 재정의 되지 않은경우 => Customer 클래스의 calcPrice매서드 호출

2. calcPrice가 재정의 된 경우 => VIPCustomer 클래스의 clacPrice매서드 호출


다형성(polymorphism) 이란?

-하나의 코드가 여러 자료형으로 구현되어 실행되는 것

overriding을 활용하여 같은 같은 코드에서 여러 다른 실행 결과가 나오는 것

 

ex)

package ch06;

import java.util.ArrayList;

class Animal{
	
	public void move() {
		System.out.println("동물이 움직입니다.");
	}
	
	public void eating() {
		
	}
}

class Human extends Animal{
	@Override
	public void move() {
		System.out.println("사람이 두 발로 걷습니다.");
	}
	public void readBook() {
		System.out.println("사람이 책을 읽습니다.");
	}
}

class Tiger extends Animal{
	
	@Override
	public void move() {
		System.out.println("호랑이가 네 발로 뜁니다.");
	}
	
	public void hunting() {
		System.out.println("호랑이가 사냥을 합니다.");
	}
}

class Eagle extends Animal{
	
	@Override
	public void move() {
		System.out.println("독수리가 하늘을 날아 다닙니다.");
	}
	
	public void flying() {
		System.out.println("독수리가 양날개를 쭉 펴고 날아다닙니다.");
	}
}


public class AnimalTest {
	public static void main(String[] args) {
		
		Animal hAnimal = new Human();
		Animal tAnimal =  new Tiger();
		Animal eAnimal = new Eagle();
		
		//AnimalTest test = new AnimalTest();
		//test.moveAnimal(hAnimal);
		//test.moveAnimal(tAnimal);
		//test.moveAnimal(eAnimal);
		
		ArrayList<Animal> animalList = new ArrayList<>();
		animalList.add(hAnimal);
		animalList.add(tAnimal);
		animalList.add(eAnimal);
		
		for(Animal animal : animalList) {
			animal.move();
		}
	}
	
	public void moveAnimal(Animal animal) {
		animal.move();
	}
}

위의 코드에서 같은 move()를 호출했지만, 각기 다른 결과가 출력된다.

 

위와같이 다형성을 사용하는 이유는,

-상속과 메서드 재정의를 활용하여 확장성 있는 프로그램을 만들 수 있고,

-위의 예제에서 다른 동물을 추가하는 경우 쉽게 수정할 수 있고,

-만약 if else문 등을 활용한다면 코드가 복잡해지고, 유지보수가 어려워진다.

 

앞선 Customer class를 상속받은 GOLDCustomer class를 구현해 보았다.

package ch06;

public class GoldCustomer extends Customer {

	double salesRatio;
	
	public GoldCustomer(int customerID, String customerName) {
		super(customerID, customerName);

		customerGrade = "GOLD";
		salesRatio = 0.1;
		bonusRatio = 0.02;
	}
	
	public int calcPrice(int price) {
		bonusPoint += price * bonusRatio;
		return price - (int)(price*salesRatio);
	}
	
}

이처럼 다형성을 활용하여 쉽게 추가하고 수정할 수 있다.


상속은 언제 사용 할까?

- IS-A 관계(is a relationship : inheritance)

일반적인 개념 -> 구체적인 개념

ex) 전공 -> 정보통신, 소프트웨어, 컴퓨터

특징

- 상위 클래스의 수정이 하위 클래스에 영향을 미침

- 계층구조가 복잡할 경우 좋지 않음

 

- HAS-A 관계(composition)

이미 생성된 코드의 기능을 가져다 쓰는 것

코드 재사용의 가장 일반적인 방법


형 변환(다운캐스팅)

업캐스팅된 클래스를 다시 원래의 타입으로 형 변환

ex)

업캐스팅 

Customer vc = new VIPCustomer(); //묵시적

다운캐스팅

VIPCustomer vCustomer = (VIPCustomer)vc; //명시적


instanceof

인스턴스의 형 체크 방법,

 

Animal hAnimal = new Human();
Animal tAnimal =  new Tiger();
Animal eAnimal = new Eagle();

 

위와 같이 업캐스팅된 class 인스턴스들을

if(animal instanceof Human) {
  Human human = (Human)animal;
}

if문을 통해 원래 인스턴스의 형이 맞는지 여부를 체크한다.

ex)

package ch08;

import java.util.ArrayList;

class Animal{
	
	public void move() {
		System.out.println("동물이 움직입니다.");
	}
	
	public void eating() {
		
	}
}

class Human extends Animal{
	@Override
	public void move() {
		System.out.println("사람이 두 발로 걷습니다.");
	}
	public void readBook() {
		System.out.println("사람이 책을 읽습니다.");
	}
}

class Tiger extends Animal{
	
	@Override
	public void move() {
		System.out.println("호랑이가 네 발로 뜁니다.");
	}
	
	public void hunting() {
		System.out.println("호랑이가 사냥을 합니다.");
	}
}

class Eagle extends Animal{
	
	@Override
	public void move() {
		System.out.println("독수리가 하늘을 날아 다닙니다.");
	}
	
	public void flying() {
		System.out.println("독수리가 양날개를 쭉 펴고 날아다닙니다.");
	}
}


public class AnimalTest {
	public static void main(String[] args) {
		
		Animal hAnimal = new Human();
		Animal tAnimal =  new Tiger();
		Animal eAnimal = new Eagle();
		
		AnimalTest test = new AnimalTest();
		//test.moveAnimal(hAnimal);
		//test.moveAnimal(tAnimal);
		//test.moveAnimal(eAnimal);
		
		ArrayList<Animal> animalList = new ArrayList<>();
		animalList.add(hAnimal);
		animalList.add(tAnimal);
		animalList.add(eAnimal);
		
		for(Animal animal : animalList) {
			animal.move();
		}
		
		test.testDownCasting(animalList);
		
		
	}
	
	public void testDownCasting (ArrayList<Animal> list) {
		
		for(int i=0;i<list.size();i++) {
			Animal animal = list.get(i);
			
			if(animal instanceof Human) {
				Human human = (Human)animal;
				human.readBook();
			}
			else if(animal instanceof Tiger) {
				Tiger tiger = (Tiger)animal;
				tiger.hunting();
			}
			else if(animal instanceof Eagle) {
				Eagle eagle = (Eagle)animal;
				eagle.flying();
			}
			else {
				System.out.println("unsupported type");
			}
		}
	}
	
	public void moveAnimal(Animal animal) {
		animal.move();
	}
}

추상 클래스

구현 코드 없이 메서드의 선언만 있는 추상 메서드를 포함한 클래스

ex)

public abstract void display();
public abstract void typing();

-> { } 선언부가 없다.

 

new를 통해 인스턴스화 할 수 없다.

ex)

public abstract class Computer {

public abstract void display();
public abstract void typing();

 

}

위와같은 컴퓨터 클래스가 있다면, Computer c = new Computer X 이렇게 생성못함

 

abstract 예약어를 사용하여 표현한다.

주로 상속을 위해 만든 클래스이다. 

만약 상속받은 클래스에서도, 추상 메서드를 선언하지 않는다면,

상속받은 클래스 또한, abstract 예약어를 클래스 앞에 붙여야한다.

 

옆으로 기울어진 italic 글씨체는 추상클래스

package ch09;

public abstract class Computer {

	public abstract void display();
	public abstract void typing();
	
	public void turOn() {
		System.out.println("전원을 켭니다");
	}
	
	public void turnOff() {
		System.out.println("전원을 끕니다");
	}
}
package ch09;

public class Desktop extends Computer{

	@Override
	public void display() {
		System.out.println("Desktop display");
	}

	@Override
	public void typing() {
		System.out.println("typing dispaly");
	}
	
	@Override
	public void turnOff() {
		System.out.println("Desktop turnOff");
	}
	
}
package ch09;

public abstract class NoteBook extends Computer {

	@Override
	public void display() {
		System.out.println("NoteBook display");
	}
}
package ch09;

public class MyNoteBook extends NoteBook {

	@Override
	public void typing() {
		System.out.println("MyNoteBook typing");
	}
	
}

 

package ch09;

public class ComputerTest {

	public static void main(String[] args) {
		
		//Computer computer = new Computer(); //에러, 추상클래스는 선언할 수 없음
		Computer desktop = new Desktop(); //업케스팅
		desktop.display();
		 //단 Computer에서 정의한 메서드만 사용가능, Desktop에서 생성한 메서드 사용불가
		
	}

}

추상 클래스 응용

템플릿 메서드 패턴

-> 추상 메서드나 구현 된 메서드를 활용하여 코드의 흐름(시나리오)를 정의하는 메서드

ex)

final public void run() {
startCar();
drive();
wiper();
stop();
turnOff();
washCar();
}

위와 같이 final로 선언하여 흐름을 선언하고,

하위클레스에서 이를 수정할 수 없음

package ch10;

public abstract class Car {

	public abstract void drive();
	public abstract void stop();
	public abstract void wiper();
	
	public void startCar() {
		System.out.println("시동을 켭니다.");
	}
	
	public void turnOff() {
		System.out.println("시동을 끕니다.");
	}
	
	public void washCar() {} //구현내용만 없고, 구현된 것이기 때문에 하위 클래스에서 정의할 필요없음
	
	final public void run() { //하위 클래스에서 재정의하지 못함
		startCar();
		drive();
		wiper();
		stop();
		turnOff();
		washCar();
	}
}

Car 클래스에서 final run() 메서드를 선언하여 흐름을 정의한다.

package ch10;

public class ManualCar extends Car{

	@Override
	public void drive() {
		System.out.println("사람이 운전합니다.");
		System.out.println("사람이 핸들을 조작합니다.");
	}

	@Override
	public void stop() {
		System.out.println("장애물 앞에서 브레이크를 밟아 멈춥니다.");
	}

	@Override
	public void wiper() {
		// TODO Auto-generated method stub
		
	}

}
package ch10;

public class AICar extends Car{

	@Override
	public void drive() {
		System.out.println("자율 주행을 합니다.");
		System.out.println("자동차가 스스로 방향을 바꿉니다.");
	}

	@Override
	public void stop() {
		System.out.println("장애물 앞에서 스스로 멈춥니다.");
	}

	@Override
	public void wiper() {
		// TODO Auto-generated method stub	
	}

	@Override
	public void washCar() {
		System.out.println("자동 세차를 합니다.");
	}

}

Car을 상속받는 AICar과 ManualCar에서는 추상 메서드를 각각정의한다.

이런 식의 패턴을

템플릿 메서드 패턴이라 한다.

반응형