[JAVA] 형변환(기본형변환/참조형변환[upcasting/downcasting])

@욕심쟁이

·

2020. 9. 7. 17:38

반응형

예제1.형변환 

- 어떤 데이터 타입을 다른 데이터 타입으로 변환하는것

- 기본(Primitive Type)형변환, 참조(Reference Type) 형변환

1. 기본형변환

- 기본데이터타입 8가지중 boolean을 제외한 나머지 7가지 타입끼리의 형변환

- 작은 타입에서 큰차입으로 변환시 자동형변환, 큰타입에서 작은타입으로 변환시 강제 형변환

- 강제형변환시 반드시 형변환 연산자를 사용하여 변환될 타입을 명시해야함

→ 단, 강제형변환 후에는 오버플로우가 발생할 위험이 있음

2. 참조 형변환(Reference형변환)

- 클래스간의 형변환(참조형 변수끼리의 형변환)

- 반드시 상속관계에서만 가능한 변환

- 자식(서브클래스)타입에서 부모(슈퍼클래스)타입으로 변환시 자동 형변환(업캐스팅)

- 부모(슈퍼클래스)에서 자식(서브클래스타입으로 변환시 강제 형변환(다운캐스팅)

 

3. 캐스팅이란?

- 캐스팅(casting)이란 타입을 변환하는 것을 말하며 형변환이라고도 한다. 자바의 상속 관계에 있는 부모와 자식 클래스 간에는 서로 간의 형변환이 가능하다.

- 부모 클래스인 상속 관계의 상위 클래스를 수퍼 클래스, 그리고 자식 클래스인 하위 클래스를 서브 클래스라고 정의

 

1) 업캐스팅(Up Casting)

- 자식 클래스(서브클래스)가 부모 클래스(슈퍼클래스)의 타입으로 캐스팅(상위(부모) 형으로 형변환)

- 슈퍼클래스 타입으로 서브클래스의 인스턴스를 참조하는 것

   → 서브클래스 인스턴스를 슈퍼클래스 타입으로 변환하는 것

 - 묵시적(자동) 형변환이 일어남

 - 참조가능한 영역이 축소됨(슈퍼클래스 타입에서 선언된 변수, 메서드만 접근 가능함)

 - 하나의 슈퍼클래스 타입으로 여러 서브클래스 인스턴스의 공통된 멤버에 접근가능 하도록 해 줌!

- 업캐스팅시 자식클래스(서브클래스)에 정의되어있는 멤버변수, 메서드는 접근 불가
  → 자식클래스(서브클래스)에 오버라이드한 메서드를 통해

     자식클래스(서브클래스)에 존재하는 멤버변수 접근, 조작가능
- 메소드 실행시 메소드가 실행되는 인스턴스의 변수를 사용함 

<기본문법>

슈퍼클래스 타입 변수명 = 서브클래스 인스턴스;

2) 다운캐스팅(Down Casting)

 - 부모 클래스(슈퍼클래스)가 자식 클래스(자식클래스)의 타입으로 캐스팅

 - 서브클래스 타입으로 부모클래스의 인스턴스를 참조하는 것

   → 슈퍼클래스 인스턴스를 서브클래스 타입으로 변환하는 것

 - 참조가능한 영역이 확대됨(서브클래스 타입에서 선언된 변수, 메서드에 모두 접근 가능함)

 - 단, 실제 존재하지 않는 영역에 대한 접근 위험성이 발생함

 - 묵시적(자동) 형변환이 일어나지않음

   → 반드시 형변환 연산자를 사용한 명시적(강제)형변환 필수!

- 명시적으로 형변환 후에도 실행 시점에서 오류발생 할 수 있음

   → 따라서, 일반적인 상황에서의 다운캐스팅은 허용되지않음(바로 다운캐스팅 X)

   → 이전에 이미 업팅캐스팅 된 인스턴스를 다시 다운캐스팅하는경우에만 안전하게 변환이 일어남

        (업캐스팅 후 다운캐스팅만 가능)

<기본문법>

서브클래스 타입 변수명 = (서브클래스타입)슈퍼클래스 인스턴스;

  예시)

public class Ex {

	public static void main(String[] args) {
		
		Child c = new Child(); //인스턴스생성 ->서브클래스에대한 인스턴스 생성
		//서브클래스의 인스턴스c를 통해 접근가능한 메서드 :2개
		c.childPrn();//자신읜 클래스에서 정의한 메서드 호출
		c.parentPrn(); //상속받은 메서드 호출
		
		//Parent()타입변수 p를 선언하여 Child클래스의 인스턴스 전달
		Parent p = new Parent();
		//서브클래스의 인스턴스 주소를 슈퍼클래스 타입 변수에 전달
		p=c; //c가 가리키는 인스턴스주소와 p가 가리키는 인스턴스 주소가 같다.
		// =>Child타입 -> Parent타입으로 변환됨(업캐스팅)
		// =>업캐스팅은 자동 형변환이 가능하므로 형변환 연산자 생략 가능
		
		//Parent타입 변수p를 통해 접근가능한 메서드 :1개
		p.parentPrn(); //상속받은 메서드는 호출가능
		//p.childPrn(); //(오류발생)서브클래스에서 정의한 메서드는 호출불가
		//=>업캐스팅 후에는 참조영역에 대한 축소가 발생하므로
		//상속된멤버외의 서브클래스에서 정의한 멤버는 접근 불가능 하게 됨
		// 즉, 서브클래스의 멤버는 보이지 않게됨
		System.out.println("p와 c의주소값이 값은가? " + (p == c));
		System.out.println("=====================================");
		
		
		Parent p2 = new Parent();
		p2.parentPrn();
		
		//다운캐스팅을 하게되면 참조영역에대한 확대가일어남
//		Child c2 = p2;//오류 다운캐스팅은 자동 형변환이 지원되지 않음
		// => 명시적(강제)형변환필요 = 형변환 연산자 사용
		//Child c2 = (Child)p2; //서브클래스 타입을 명시하여강제형변환
		//=>형변환 후에도 오류발생
		//c2.childPrn();//parent 인스턴스에 존재하지 않는 메서드
		//존재하지않는 영역에 대한 참조의 위험 때문에 다운캐스팅은 다종형변환이 지원되지 않으며 강제형변환을 하더라도 실행 시점에서 논리적오류가 받생
		System.out.println("====================================");
		
		//다운 캐스팅이 허용되는 경우
//		Child c3 = new Child();//슈퍼클래스인스턴스 생성
//		Parent p3 = c3;//업캐스팅
		Parent p3 = new Child(); //위두문장을 하나로 결합가능 업캐스팅 
		// 슈퍼클래스(Parent)타입으로 접근 가능한 메서드:1개
		p3.parentPrn();//상속받은 메서드
		//이미 업캐스팅된 이스턴스를 다시 다운 캐스팅
		//Child c3 = p3;// 명시적 형변환 필요
		Child c3 = (Child)p3;//다운캐스팅(실행시 오류발생하지않음!)
		
		//서브클래스타입으로 접근가능한 메서드:2개
		c3.childPrn(); //서브클래스에서 정의한 메서드
		c3.parentPrn(); //상속받은 메서드
		//결론!
		//이전에 이미 업캐스팅되어 참조영역이 축소되었던 인스턴스를 
		//다시 다운캐스팅을 통해 참조영역이 확대되면
		//접근 범위에 아무런 문제가 없으므로 사용이 가능!
		//따라서, 래퍼런스 형변환 시에는 상속관계를 고려하여야 한다!
		// 알맞은 형변환 방식을 선택해서 변환해야한다!
		
	}
}
class Parent{
	
	//메서드 생성
	public void parentPrn() {
		System.out.println("슈퍼클래스의 parentPrn()메서드!");
	}
}
class Child extends Parent{ 
	//메서드 생성
	public void childPrn() {
		System.out.println("childPrn()메서드!");
	}	
}

3. 기본데이터타입 vs 참조데이터타입

 - 두종류의 데이터 타입에 대한 값을 전달할 경우 해당값을 복사하여 전달

 - 기본데이터 타입은 실제값의 복사가 일어남

   → 전달받은 (복사된)데이터(복사된 실제값)를 변경하더라고 원본 데이터에는 아무런 영향이 없다!

 - 참조데이터타입은 주소값의 복사가 일어남

   → 전달받은 데이터(복사된 주소값)의 데이터(실제값)을 변경하면 변경된 내용이 동일하게 적용되어 있다!

       (즉, 동일한 위치의 주소를 공유하므로 변경사항 공유)

예제)

public class Ex {

	public static void main(String[] args) {
		//기본데이터타입 vs 참조데이터타입
		//-두종류의 데이터 타입을 메서드 호출시 전달할 경우 데이터를 복사하여 전달
		int money = 100000; //기본형, 로컬변수(메인메서드안)
		System.out.println("1. 변경전 money : "+money);
		//primitiveChange()메서드를 호출하여 int형 변수 money의 값 전달
		primitiveChange(money);//실제값(100000)을 복사하여 전달
		//원본데이터는 아무런 영향을 받지 않는다!
		System.out.println("2. 변경후  money : "+money);
		System.out.println("=======================================");
		
		MyMoney mm = new MyMoney();//MyMoney인스턴스 생성
		System.out.println("3. 변경전 mm.money = "+ mm.money);
		//primitiveChange()메서드를 호출하여 MyMoney인스턴스의 인스턴스변수인 기본데이터타입money파라미터로 전달
		
		primitiveChange(mm.money);//인스턴스변수, 멤버변수이자 기본형변수  //mm의 주소값을 찾아가서 mymoney의 money의 100000;값을 들고온다.
		//메서드에 전달하는 변수가 인스턴스 내의 변수라 하더라도
		//기본데이터타입일 경우 값을 복사하여 전달함.
		//따라서, 실제값을 복사하여 전달받은 메서드에서값을 변경하더라고
		//원본 데이터는 아무런 영향을 받지않는다!
		System.out.println("4. 변경 후 mm.money = " + mm.money);
		
		System.out.println("=================================");
		MyMoney mm2 =  new MyMoney();
		System.out.println("5. 변경 전 mm2.money = "+ mm2.money);
		
		refernceChange(mm2);// 주소값을 복사하여 전달
		//참조변수가 가진 주소값을 복사하여 메서드에 전달 한 뒤
		//메서드 내에서 해당 참조변수에 접근하여 인스턴스 변수값을 변경하면
		//원본 데이터가 있는 곳의 값을 변경하게 되므로 원본데이터도 영향을 받는다.
		//=> 즉, 원본 데이터와 전달받은값이 가리키는 인스턴스가 동일하므로
		//한곳에서 값을 변경하면 다른곳에서도 동일한 값을 사용하게됨
		System.out.println("6. 변경 후 mm2.money = " + mm2.money);
	}
	
	public static void primitiveChange(int money) {
		//외부로 부터 전달받은 데이터(실제값)를 변경
		System.out.println("3-1. 메서드 호출 후 변경전 money = " + money);
		money-=20000;
		System.out.println("3-2. 메서드 호출 후 변경 후 money = " + money);
	}
	
	public static void refernceChange(MyMoney mm) {
		//외부로부터 전달받은 데이터(주소값)에 접근하여
		//해당 인스턴스 내의 멤버변수 값(실제값)을 변경
		System.out.println("5-1. 메서드 호출 후 변경전 money = " + mm.money);
		mm.money-=20000;
		System.out.println("5-2. 메서드 호출 후 변경 후 money = " + mm.money);
	}

}
class MyMoney{
	int money = 1000000;
}

4. 다형성(Polymorphism)

- 하나의 클래스타입으로 여러 서브클래스타입 인스턴스를 참조하는것

- 업캐스팅을 의미함

   → 서브클래스에서 메서드 오버라이딩을 수행했을 때 슈퍼클래스타입으로 업캐스팅 후 메서드호출 시

      오버라이딩된 메서드가 호출되어 코드는 동일하나 실행결과가 달라지게됨

- 다형성을 통해코드의 통일성을 향상시킬수있으나 해당 인스턴스의 상세 속성에 접근하려면 다운캐스팅을 통해 서브클래스 타입으로 변환한뒤 접근가능함

- 상속 : 변수, 메서드 오버라이드 가능

5. 업캐스팅시 오버라이딩

동적바인딩 : 코드상의 실행 할 메서드와 컴파일 후 실행 시점에서 실행되는메서드가 달라짐

- 오버라이딩 된 메서드가 존재할 경우 업캐스팅 후에도 오버라이딩 된 메서드가 호출됨

   → 즉 메서드호풀시 참조타입이 누준지는 중요하지않고 실제인스턴스가 누군지가 중요함

예제1.

public class Ex {

	public static void main(String[] args) {

		Truck t = new Truck();
		t.dump();
		t.speedDown();
		t.speedUp();	
		
		Taxi tx = new Taxi();
		tx.drop();
		tx.lift();
		tx.speedUp();
		tx.speedDown();
		
		System.out.println("=================업캐스팅=====================");
		
		Car car = t;
		car.speedDown();
		car.speedUp();

		System.out.println("=================다운캐스팅=====================");
		t = (Truck)car;
		t.dump();
		t.speedDown();
		t.speedUp();	
		
		car = tx;
		//Taxi클래스에서 speedUp(),speedDown()메서드를 오버라이딩
		//따라서 업캐스팅 후에도 오버라이딩된 taxi의메서드 호출
		car.speedDown();
		car.speedUp();
	
		System.out.println("=================다운캐스팅=====================");
	
		tx = (Taxi)car;
		tx.drop();
		tx.lift();
		tx.speedDown();
		tx.speedUp();
	}
}
class Car {
	public void speedUp() {
		System.out.println("Car의 속력증가!");
	}
	public void speedDown() {
		System.out.println("Car의 속력감소!");
	}
	
}

class Truck extends Car{
	
	public void dump() {
		System.out.println("Trunck의 짐심기");
	}

	@Override
	public void speedUp() {
		System.out.println("트럭의 속력증가");
	}

	@Override
	public void speedDown() {
		System.out.println("트럭의 속력감소");
	}
}

class Taxi extends Car{
	public void lift() {
		System.out.println("Taxi승객 승차!");
	}
	public void drop() {
		System.out.println("Taxi승객 하차!");
	}
	
	@Override
	public void speedUp() {
		System.out.println("택시의 속력증가");
	}

	@Override
	public void speedDown() {
		System.out.println("택시의 속력감소");
	}
}

 

예제2.

public class Test {

	public static void main(String[] args) {
		Circle c = new Circle();
		c.rad=3;
		c.draw();
		
		Rectangle r = new Rectangle();
		r.line1 = 10;
		r.line2 = 20;
		r.draw();
		
		Triangle tg = new Triangle();
		tg.a=10;
		tg.b=20;
		tg.c=30;
		tg.draw();
		System.out.println("=========================");
		Shape s = c;
		s.draw();
		
		s = r;
		s.draw();
		
		s=tg;
		s.draw();		

		
		System.out.println("================================");
		
		shapeDraw(new Circle());
		shapeDraw(new Rectangle());
		shapeDraw(new Triangle());
	}
	
	public static void shapeDraw(Shape s) {
		s.draw();
	}
	
}
class Shape{
	public void draw() {
		System.out.println("도형 그리기");
	}
}
class Circle extends Shape{
	double rad;

	@Override
	public void draw() {
		System.out.println("반지름"+rad+"를 이용하여 원그리기!");
	}
	

}
class Rectangle extends Shape{
	int line1, line2;
	@Override
	public void draw() {
		System.out.println(line1+","+line2+"직선두개 사용하여 사각형 그리기!");
	}
}
class Triangle extends Shape{
	int a, b, c;
	@Override
	public void draw() {
		System.out.println(a+","+b+","+c+"직선 세개 사용하여 삼각형 그리기!");
	}
}

예제3.

public class Ex2 {
	public static void main(String[] args) {
		Parent2 p2 = new Parent2();
		if(p2 instanceof Child2) {
			//판별결과가true이면 무조건 CHild2차입으로 변환가능 
			//=>서브클래스를 이미 업캐스팅 해놓은 상태이므로 형변환 가능하다는 True가 리턴됨
			System.out.println("p -> Child2타입으로 형변환 가능");
			//Child2 c = p2;//자동형변환은 불가능한 관계이므로 
			Child2 c = (Child2)p2;
		}else {
			//판별결과가 true이면 어떠한 변환도 불가능
			//->슈퍼클래스 타입 인스턴스이므로 다운 캐스팅 불가!
			System.out.println("p -> Child2타입으로 형변환 불가능");
		}
		System.out.println("==============================================");
		
		//SmartPhone인스턴스(sp)생성
		SmartPhone 내폰 = new SmartPhone();
		//내폰은 SmartPhone입니까?
		if(내폰 instanceof SmartPhone) {   //변수명 instanceof 타입
			System.out.println("내폰은 SmartPhone이다.");
			SmartPhone 동생폰 = 내폰;
			동생폰.call();
			동생폰.sms();
			동생폰.kakaoTalk();
			동생폰.youtube();
		}
		System.out.println("==============================================");
		
		if(내폰 instanceof HandPhone) {
			System.out.println("내폰은 HandPhone입니다.");
			System.out.println("그러므로 HandPhone으로 형변환 가능하다!");
			HandPhone 엄마폰= 내폰;
			System.out.println("내폰은 HandPhone의 모든 기능을 포함한다!");
			엄마폰.call();
			엄마폰.sms();
		} 
		System.out.println("==============================================");
		
		HandPhone 엄마폰 = new HandPhone();
		
		if(엄마폰 instanceof SmartPhone) {
			System.out.println("엄마폰은 SmartPhone입니다.");
		}else {
			System.out.println("엄마폰은 SmartPhone이 아닙니다.");
		}
		System.out.println("==============================================");
		
		HandPhone 엄마폰2 = new SmartPhone();
		if(엄마폰2 instanceof SmartPhone) {
			System.out.println("엄마폰2은 SmartPhone입니다.");
			SmartPhone 내폰2 = (SmartPhone)엄마폰2;
			System.out.println("엄마폰은 SmartPhone의 모든기능을 포함");
			System.out.println("따라서 SmartPhone의 형변환 후에도");
			System.out.println("정상적으로 SmartPhone의 모든 기능 사용가능");
			내폰2.call();
			내폰2.sms();
			내폰2.kakaoTalk();
			내폰2.youtube();
		}else {
			System.out.println("엄마폰은 SmartPhone이 아닙니다.");
		}
		
		
		
	}
}

class Parent2{
	
}

class Child2 extends Parent2{
}
//----------------------------------------------

class HandPhone{
	
	String phoneNumber;
	
	public void call() {
		System.out.println("HandPhone의 전화기능!");
	}
	public void sms() {
		System.out.println("HandPhone의 문자기능!");
	}
}

class SmartPhone extends HandPhone{
	
	String osName;
	public void kakaoTalk() {
		System.out.println("SmartPhone의 카카오 기능!");
	}
	public void youtube() {
		System.out.println("SmartPhone의 유튜브 기능!");
	}
	
	
}

 

 

반응형