class Tv {
boolean power;
int channel;
void power() {
power = !power;
}
void channelUp() {
++channel;
}
void channelDown() {
--channel;
}
}
class smartTv extends Tv{
String text;
void caption(){
}
}
위와 같은 코드에서, 우리는 인스턴스를 다루기 위해 인스턴스 타입과 일치하는 타입의 참조변수만을 사용했다. 즉
SmartTv s = new SmartTv();
Tv t = new Tv();
와 같이 사용했는데, Tv와 SmartTv클래스가 서로 상속관계에 있을 경우
Tv t = new SmartTv(); //Tv리모콘으로 SmartTv를 다룬다.
와 같이 조상 클래스 타입(TV)의 참조 변수(t)로 자손 클래스(SmartTv)의 인스턴스를 참조하는 것도 가능하다.
결론적으로는
smartTv s = new SmartTv(); //참조변수와 인스턴스 타입 일치
Tv t = new SmartTv(); //조상 타입 참조변수로 자손 타입 인스턴스 참조
같은 타입의 인스턴스지만 참조변수의 타입에 따라 사용할 수 있는 멤버의 개수가 달라진다.
그러나
SmartTv s = new Tv();
와 같이 자손 타입의 참조 변수로 조상 타입의 객체를 가리킬 수는 없다.
실제 인스턴스인 Tv의 멤버 개수보다 참조변수 s가 사용할 수 있는 멤버 개수가 더 많기 때문이다.
인스턴스 SmartTv의 기능이 7개인데 리모콘 t의 기능이 5개인건 괜찮다. 있는 기능을 안 쓰는건 괜찮은데
Tv 인스턴스의 기능이 5개인데 리모콘 s의 기능이 7개이면 안된다. 기능을 사용할 수 없기 때문이다.
q. 참조변수의 타입은 인스턴스의 타입과 반드시 일치해야하는가?
a. 일치하는 것이 보통이지만 일치하지 않을 수도 있다.
q. 참조변수가 조상타입일 때와 자손타입일 때의 차이는?
a. 참조변수로 사용할 수 있는 멤버의 개수가 달라진다.
SmartTv리모콘으로는 7개 사용 가능, Tv리모콘으로는 5개 사용가능
q. 자손타입의 참조 변수로 조상타입의 객체를 가리킬 수 있는가?
a. ㄴㄴㄴ
Tv t = new SmartTv(); //OK, 조상타입(Tv)의 참조변수(t)로 자손타입(SmartTv) 가리킴
SmartTv s = new Tv(); //NOOOOO
class Car{}
class FireEngine extends Car{}
class Ambulance extends Car{}
위와 같은 Car클래스, 그리고 이를 상속받는 FireEngine,Ambulance클래스가 있다고 할 때
Car c = (Car)f; //조상인 Car타입으로 형변환(업캐스팅,생략가능)
FireEngine f2 = (FireEngine)c; //자손인 FireEngine타입으로 형변환(다운캐스팅, 생략불가)
Ambulance a = (Ambulance)f; //에러, 상속관계가 아닌 클래스 간 형변환 불가
class Car {
String color;
int door;
void drive() {
System.out.println("drive");
}
void stop() {
System.out.println("Stop");
}
}
class FireEngine extends Car{
void water(){
System.out.println("water");
}
}
public class Poly {
public static void main(String[] args) {
Car car = null; //초기화는 했으나 아무것도 저장하는 것이 없음, 참조하는 곳이 없음 == 객체가 없음.
FireEngine fe = new FireEngine();
FireEngine fe2 = null;
fe.water();
car = (Car)fe; //생략가능
//참조변수 fe의 값을 car에 저장해서 car로도 FireEngine인스턴스를 다룰 수 있다.
//그러나 car로는 water()를 다룰 수 없다.
//car.water(); //호출불가
fe2 = (FireEngine)car; //생략불가
fe.water();
}
}
그런데 만약에 아래와 같이 참조변수 = null 일 때,
Car car = null;
FireEngine fe = null;
FireEngine fe2 = (FireEngine)car;
Car car2 = (Car)f2;
즉 객체가 없을 때에도 형변환에는 문제가 없다.
그런데 멤버변수를 사용하려고 하면 NullPointerException이 발생한다. 객체가 없을 때 메서드를 호출할 수 없기 때문이다.
Car car = null;
FireEngine fe = null;
FireEngine fe2 = (FireEngine)car;
Car car2 = (Car)f2;
null.drive(); //에러
다음 예시를 보자.
Car c = new Car();
FireEngine fe = (FireEngine) c;
fe.water();
이 경우에는 ClassCastException이 발생한다.
Car타입의 참조변수 c는 4개만 사용할 수 있는데
fe도 c가 가리키는 Car객체를 가리키고 있다.
이 때 Car에는 water()가 없기 때문에 Water메서드를 호출하게 되면 에러가 발생한다.
즉 참조변수 간의 형변환 자체가 중요한게 아니라, 실제 인스턴스가 무엇인지가 중요하다.
void doWork(Car c){ //Car c의 자리에는 Car의 자손들도 들어올 수 있다.
if(c instanceof FireEngine){ //형변환이 가능한지 확인, c가 가리키는 객체가 FireEngine인지
FireEngine fe = (FireEngine)c; //형변환
fe.water();
Car타입 리모콘인 c로는 water()를 호출할 수 없으므로
리모콘을 FireEngine타입으로 바꿔서 호출한다.
class Car {
String color;
int door;
void drive() {
System.out.println("drive");
}
void stop() {
System.out.println("Stop");
}
}
class FireEngine extends Car{
void water(){
System.out.println("water");
}
}
public class Poly{
public static void main(String[] args) {
FireEngine fe= new FireEngine();
System.out.println(fe instanceof Object);
System.out.println(fe instanceof Car);
System.out.println(fe instanceof FireEngine);
}
}
output
true
true
true
class Vehicle{
public void run(){
System.out.println("차량이 달립니다");
}
}
class Driver{
public void drive(Vehicle vehicle){ //Vehicle타입의 매개값을 받음
vehicle.run();
}
}
class Bus extends Vehicle{
@Override
public void run(){
System.out.println("버스가 달립니다");
}
}
class Taxi extends Vehicle{
@Override
public void run(){
System.out.println("택시가 달립니다");
}
}
public class DriverEx {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus); //자동 타입 변환: Vehicle vehicle = bus;
driver.drive(taxi); //자동 타입 변환: Vehicle vehicle = taxi;
}
}
버스가 달립니다
택시가 달립니다
class Product {
int price;
int bonusPoint;
} //제품
class Tv extends Product {
}
class Computer extends Product {
}
class Buyer {
int money = 1000;
int bonusPoint = 0;
//고객
//물건 구입
void buy(Tv t) {
money = money - t.price;
bonusPoint = bonusPoint + t.bonusPoint;
}
void buy(Computer c) {
money = money - c.price;
bonusPoint = bonusPoint + c.bonusPoint;
}
}
이 코드에서 계속 오버로딩을 하고 있는데, 구입하는 종류가 늘어날 떄 마다 새로운 buy 메서드를 추가해야한다.buy(Phone p), buy(ipad i)처럼..
이 때 buy메서드에 Tv타입, Computer타입 대신 Product타입을 사용하고
조상타입의 참조변수로 자손 객체를 가리키면 된다.
void buy(Product p){
money -= p.price;
bonusPoint += p.bonusPoint;
}
Product p1 = new Tv(); //조상 참조변수로 자손 객체 가리킴
Product p2 = new Computer();
class Product {
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = (int) (price / 10.0);
}
}
class Tv1 extends Product{
Tv1(){
//조상클래스의 생성자 Product(int price)를 호출
super(100);
}
public String toString(){
return "Tv";
}
}
class Computer extends Product {
Computer() {
super(200);
}
public String toString() {
return "Computer";
}
}
class Buyer {
int money = 1000;
int bonusPoint = 0;
void buy(Product p) { //new Tv1(), new Computer(). 조상타입의 참조변수 p가 자식 객체들을 가리킨다.
if (money < p.price) {
System.out.println("잔액 부족");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
System.out.println(p + "을/를 구입"); //p.toString()과 같음
}
}
class Poly {
public static void main(String[] args) {
Buyer b = new Buyer();
b.buy(new Tv1()); //buy(Product p)
b.buy(new Computer()); //buy(Product p)
System.out.println("현재 남은 돈은 " + b.money + "만원");
System.out.println("현재 보너스 점수는 " + b.bonusPoint + "점");
}
}
조상 Product의 참조변수로 여러 자손들을 가리키는
Product p1 = new Tv();
Product p2 = new Computer();
Product p3 = new Audio();
를 배열로 바꾸면 다음과 같다
Product p[] = new Product[3];
p[0] = new Tv();
p[1] = new Computer();
p[2] = new Audio();
class Product {
int price;
int bonusPoint;
Product(int price) {
this.price = price;
bonusPoint = (int) (price / 10.0);
}
Product(){}
}
class Tv1 extends Product{
Tv1(){
//조상클래스의 생성자 Product(int price)를 호출
super(100);
}
public String toString(){
return "Tv";
}
}
class Computer extends Product {
Computer() {
super(200);
}
public String toString() {
return "Computer";
}
}
class Buyer {
int money = 1000;
int bonusPoint = 0;
Product[] cart = new Product[10]; //구입한 물건을 담을 카트
int i = 0; //카트 인덱스
void buy(Product p) {
if (money < p.price) {
System.out.println("잔액 부족");
return;
}
money -= p.price;
bonusPoint += p.bonusPoint;
cart[i++] = p;
System.out.println(p + "을/를 구입");
}
void summary() {
int sum = 0;
String itemList = "";
for (int i = 0; i < cart.length; i++) {
if (cart[i] == null)
break;
sum += cart[i].price;
itemList += cart[i] + ", ";
}
System.out.println("총 금액은 " + sum + "만원");
System.out.println("구입한 제품은 " + itemList);
}
}
class Poly {
public static void main(String[] args) {
Buyer b = new Buyer();
b.buy(new Tv1());
b.buy(new Computer());
b.summary();
}
}
Tv을/를 구입
Computer을/를 구입
총 금액은 300만원
구입한 제품은 Tv, Computer,