📚 자바의 정석을 정리한 내용입니다.
운전하는 법은 한 번만 배우면 어떤 자동차든 운전할 수 있다. 자동차 브랜드나 내부 구현에 따라 달라지지 않는다. 동일한 인터페이스를 가지고 있기 때문이다. 이게 다형성이다.
OOP에서 다형성이란 여러가지 형태를 가질 수 있는 능력. 말하자면 조상클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있는 것이다.
class Tv {
boolean power;
int channel;
void power{ power = !power;}
void channelUp() { ++channel;}
void channelDown() { --channel;}
}
class CaptionTv extends Tv {
String text;
void caption() {}
}
CationTv의 인스턴스를 Tv caption = new CaptionTv() 이런 식으로 생성할 수도 있다.
다만 이럴 경우 CaptionTv가 가지고 있는 caption()는 사용할 수 없다.
class Car {
String color;
int door;
void drive() {
System.out.println("drive, brrr....");
}
void stop() {
System.out.println("stop...");
}
}
class FireEngine extends Car {
void water() {
System.out.println("water...");
}
}
public class CastingTest {
public static void main(String[] args) {
Car car = null;
//FireEngine fire = (FireEngine)new Car(); //ERROR
FireEngine fireEngine = new FireEngine();
FireEngine fireEngine2 = new FireEngine();
fireEngine.water();
car = fireEngine;
//car.warter //사용 불가
car.stop();
fireEngine2 = (FireEngine)car;
fireEngine2.water();
}
}
형변환은 참조변수 타입을 변환하는 것일 뿐, 인스턴스를 변환하는 것은 아니다. 참조변수는 단지 참조하는 인스턴스에서 사용할 수 있는 멤버의 범위(개수)를 조절한다.
자손 -> 조상으로 형변환은 가능하지만
조상 -> 자손으로 형변환은 불가능하다.
왜?
실제 인스턴스인 Car의 멤버의 개수보다 FireEngine이 사용할 수 있는 멤버 개수가 더 많기 때문이다.
참조변수가 참조하고 있는 인스턴스의 실제타입을 알아볼 때 쓴다. boolean타입으로 return하기 때문에 주로 조건문에서 유효성 검사할 때 사용한다.
instanceof의 결과가 true라는 것은 참조변수가 검사한 타입으로 형변환이 가능하다는 뜻이다.
class InstanceofTest{
public static void main(String[] args) {
MotorCyle motorCycle = new MotorCyle();
if(motorCycle instanceof MotorCyle) {
System.out.println("MotorCyle Instance");
}
if(motorCycle instanceof Cycle) {
System.out.println("Cycle Instance");
}
if(motorCycle instanceof Object) {
System.out.println("Object Instance");
}
}
}
class Cycle {}
class MotorCyle extends Cycle{}
/* 결과
MotorCyle Instance
Cycle Instance
Object Instance
*/
MotorCycle클래스는 자기 자신인 MotorCycle타입이면서, 조상인 Cycle타입이면서, 그의 조상인 Object타입이기도하다.
이처럼 instanceof는 자신의 타입 뿐만 아니라 조상타입에도 true를 반환한다. 그러므로 MotorCycle타입은 Cycle타입으로도, Object타입으로도 형변환 할 수 있다.
메서드의 경우 조상 클래스의 메서드를 자손 클래스에서 오버라이딩 했다면, 참조변수 타입에 상관없이 실제 인스턴스의 메서드(오버라이딩한 메서드)가 호출된다.
하지만 멤벼변수의 경우 이름이 같을 경우 참조 변수에 따라 값이 달라진다.
class Super {
int x = 10;
}
class Sub extends {
int x = 20;
}
class Main {
public static void main(String[] args) {
Super s = new Super();
Sub sub = new Sub();
System.out.println(s.x); // 10
System.out.println(sub.x); //20
}
}
매개변수로 조상타입의 참조변수를 설정하면, 모든 자손타입의 참조변수를 매개변수로 받을 수 있다.
바로 위 예제를 참고하여, add(Super x)라는 메서드가 있다고 하자. 그럼 Super를 상속받는 Sub도 매개변수로 받을 수 있다는 뜻이다.
package com.javaex.ch7;
/*
* 가격과 포인트 점수를 정의해놓은 클래스
* 모든 상품이 가지고 있어야 할 것들을 정의한다.
* */
class Product {
int price;
int bonusPoint;
/*
* Product를 상속하는 클래스들은 조상클래스의 생성자super()를 호출할 때 인자로 가격을 입력해야 한다.
* Product()는 입력받은 가격으로 보너스포인트를 연산해서 변수에 저장한다.
* */
Product(int price) {
this.price = price;
bonusPoint = (int)(price/10.0); //가격의 10%를 적립한다.
}
}
class Tv extends Product {
Tv() {
/*조상클래스의 생성자를 호출할 때 가격을 입력*/
super(100); //Tv는 100만원이다.
}
//toString 오버라이딩
@Override
public String toString() { return "Tv"; }
}
class Computer extends Product {
Computer() {super(200);}
@Override
public String toString() {return "Computer";}
}
/*
* 구매자의 정보를 정의한 클래스
* */
class Buyer {
int money = 1000;
int bonusPoint = 0;
/*
* 구매자Buyer가 상품Product를 구매하면 이 메서드를 호출한다.
* Product Type이기 때문에 Product를 상속하는 모든 클래스를 매개변수로 받을 수 있다.(매개변수의 다형성)
* 구매자가 가진 돈과 상품 가격을 비교(유효성검사) 후, 돈이 충분하다면
* 1.가진 돈에서 상품 가격을 뺀다.
* 2.보너스 점수를 계산한 다음, 구매자의 보너수점수 변수에 저장한다.
* */
void buy(Product product) {
if(money < product.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money -= product.price;
bonusPoint += product.bonusPoint;
System.out.println(product +"상품을 구매하셨습니다.");
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
buyer.buy(new Tv());
buyer.buy(new Computer());
System.out.println("남은 잔액 "+buyer.money+"만원");
System.out.println("현재 포인트는 "+buyer.bonusPoint+"점");
}
}
같은 클래스를 상속 받는 자손 클래스들을 묶어서 객체 배열을 만들 수 있다.
그러니까 상속 받는 클래스만 같다면, 타입이 달라도 하나의 배열로 다룰 수 있다는 것이다. 위의 예제를 약간 수정해서 배열을 만들었다.
package com.javaex.ch7;
import java.util.Arrays;
/*
* 가격과 포인트 점수를 정의해놓은 클래스
* 모든 상품이 가지고 있어야 할 것들을 정의한다.
* */
class Product {
int price;
int bonusPoint;
/*
* Product를 상속하는 클래스들은 조상클래스의 생성자super()를 호출할 때 인자로 가격을 입력해야 한다.
* Product()는 입력받은 가격으로 보너스포인트를 연산해서 변수에 저장한다.
* */
Product(int price) {
this.price = price;
bonusPoint = (int)(price/10.0); //가격의 10%를 적립한다.
}
Product() {}
}
class Tv extends Product {
Tv() {
/*조상클래스의 생성자를 호출할 때 가격을 입력*/
super(100); //Tv는 100만원이다.
}
//toString 오버라이딩
@Override
public String toString() { return "Tv"; }
}
class Computer extends Product {
Computer() {super(200);}
@Override
public String toString() {return "Computer";}
}
class Audio extends Product {
Audio() {super(50);}
@Override
public String toString() {return "Audio";}
}
/*
* 구매자의 정보를 정의한 클래스
* */
class Buyer {
int money = 1000;
int bonusPoint = 0;
//구입한 제품을 저장하기 위한 배열
Product[] item = new Product[10]; //10개를 담을 수 있는 장바구니
int i = 0; //배열에 사용할 index
/*
* 구매자Buyer가 상품Product를 구매하면 이 메서드를 호출한다.
* Product Type이기 때문에 Product를 상속하는 모든 클래스를 매개변수로 받을 수 있다.(매개변수의 다형성)
* 구매자가 가진 돈과 상품 가격을 비교(유효성검사) 후, 돈이 충분하다면
* 1.가진 돈에서 상품 가격을 뺀다.
* 2.보너스 점수를 계산한 다음, 구매자의 보너수점수 변수에 저장한다.
* */
void buy(Product product) {
if(money < product.price) {
System.out.println("잔액이 부족합니다.");
return;
}
money -= product.price;
bonusPoint += product.bonusPoint;
item[i++] = product;
System.out.println(product +"상품을 구매하셨습니다.");
}
/*구매한 상품 정보를 보여주는 메서드*/
void summary() {
int sum = 0; //총가격
String itemList = ""; //상품 목록
for(int i=0; i<item.length;i++) {
if(item[i] == null) break;
sum += item[i].price; //상품 가격을 더한다.
itemList += item[i] +", "; //상품명을 추가한다.
}
System.out.println("구매하신 상품 목록은 " +itemList+"입니다.");
System.out.println("구매하신 상품 총액은 " +sum+"입니다.");
}
}
public class PolyArgumentTest {
public static void main(String[] args) {
Buyer buyer = new Buyer();
buyer.buy(new Tv());
buyer.buy(new Computer());
buyer.buy(new Audio());
buyer.summary();
System.out.println("남은 잔액 "+buyer.money+"만원");
System.out.println("현재 포인트는 "+buyer.bonusPoint+"점");
}
}