Development/JavaEssential

java - 형변환(casting)

linuxism 2014. 1. 5. 15:48


3.1. 형변환(Casting)이란?


모든 리터럴과 변수에는 타입이 있다는 것을 배웠다. 프로그램을 작성하다 보면, 서로 다른 타입의 값으로 연산을 수행해야하는 경우가 자주 발생한다. 
모든 연산은 기본적으로 같은 타입의 피연산자(Operand)간에만 수행될 수 있으므로, 서로 다른 타입의 피연산자간의 연산을 수행해야하는 경우, 연산을 수행하기 전에 형변환을 통해 같은 타입으로 변환해주어야 한다. 


형변환이란, 변수 또는 리터럴의 타입을 다른 타입으로 변환하는 것이다. 


예를 들어 int형 값과 float형 값의 덧셈연산을 수행하려면, 먼저 두 값을 같은 타입으로 변환해야하므로, 둘 다 int형으로 변환하던가 또는 둘 다 float형으로 변환해야한다. 



3.2 형변환 방법


기본형과 참조형 모두 형변환이 가능하지만, 기본형과 참조형 사이에는 형변환이 성립되지 않는다. 기본형은 기본형으로만 참조형은 참조형으로만 형변환이 가능하다. 

형변환 방법은 매우 간단하며 다음과 같다. 


(타입이름)피연산자 


피연산자 앞에 변환하고자 하는 타입의 이름을 괄호에 넣어서 붙여 주기만 하면 된다. 여기에 사용되는 괄호는 특별히 캐스트연산자(형변환 연산자)라고 하며, 형변환을 캐스팅(Casting)이라고도 한다. 
캐스트연산자는 수행결과로 피연산자의 값을 지정한 타입으로 변환하여 반환한다. 이 때, 형변환은 피연산자의 원래 값에는 아무런 영향도 미치지 않는다. 

[예제2-7] CastingEx1.java

class CastingEx1 

      public static void main(String[] args) 
      { 
            double d = 100.0; 
            int i = 100; 
            int result = i + (int)d; 

            System.out.println("d=" + d); 
            System.out.println("i=" + i); 
            System.out.println("result=" + result); 
      } 

[실행결과]
d=100.0 
i=100 
result=200 


double변수 d와 int변수 i의 덧셈 연산을 하기 위해 캐스트연산자를 이용해서 d를 int형으로 변환하여 덧셈연산을 수행하였다. 그리고 그 결과를 int변수 result에 저장하였다. 
int형과 int형의 연산결과는 항상 int형 값을 결과로 얻으므로 result의 타입을 int형으로 하였다. 
결과의 첫째 줄을 보면 d=100.0으로 형변환 후에도 d에 저장된 값에는 변함이 없다. 캐스트연산자를 이용해서 d를 형변환 하였어도 d에 저장된 값에는 변함이 없음을 알 수 있다. 단지, 형변환 한 결과를 덧셈에 사용했을 뿐이다. 
[참고]캐스트 연산자는 우선순위가 매우 높기 때문에 덧셈연산 전에 형변환이 수행되었다. 



3.3 기본형의 형변환



8개의 기본형 중에서 boolean을 제외한 나머지 7개의 기본형 간에는 서로 형변환이 가능하다. 

[표2-10]기본형간의 형변환 

[참고]char형은 십진수로 0~65535의 코드값을 갖는다. 문자 'A'의 코드는 십진수로 65이다.

각 자료형 마다 표현할 수 있는 값의 범위가 다르기 때문에 범위가 큰 자료형에서 범위가 작은 자료형으로의 변환은 값 손실이 발생할 수 있다. 
예를 들어 실수형을 정수형으로 변환하는 경우 소수점 이하의 값은 버려지게 된다. 위의 표에서도 알 수 있듯이 float리터럴인 1.6f를 int로 변환하면 1이 된다. 

반대로 범위가 작은 자료형에서 큰 자료형으로 변환하는 경우에는 절대로 값 손실이 발생하지 않으므로 변환에 아무런 문제가 없다. 
[참고]실수형을 정수형으로 변환할 때 소수점 이하의 값은 반올림이 아닌 버림으로 처리된다는 점에 유의하도록 하자. 

[예제2-8] CastingEx2.java

class CastingEx2 

      public static void main(String[] args) 
      { 
            byte b = 10; 
            int i = (int)b; 
            System.out.println("i=" + i); 
            System.out.println("b=" + b); 
            
            int i2 = 300; 
            byte b2 = (byte)i2; 
            System.out.println("i2=" + i2); 
            System.out.println("b2=" + b2); 
      } 

[실행결과]
i=10 
b=10 
i2=300 
b2=44 


byte형 값을 int형으로, int형 값을 byte형으로 변환하고 그 결과를 출력하는 예제이다. byte와 int는 모두 정수형으로 각각 1 byte(8 bit)와 4 byte(32 bit)의 크기를 갖으며, 표현할 수 있는 값의 범위는 byte가 -27 ~ 27-1(-128~127), int가 -232 ~ 232-1이다. 

byte의 범위를 int가 포함하고 있으며, int가 byte보다 훨씬 큰 표현 범위를 갖고 있다. 아래 그림에서 볼 수 있는 것과 같이 byte값을 int값으로 변환하는 것은 1 byte에서 4 byte로 나머지 3 byte(24자리)를 단순히 0으로 채워 주면 되므로 기존의 값이 그대로 보존된다. 
하지만, 반대로 int값을 byte값으로 변환하는 int값의 상위 3 byte(24자리)를 잘라내서 1 byte로 만드는 것이므로 기존의 int값이 보존될 수도 있고 그렇지 않을 수도 있다. 

 
[표2-10]byte와 int간의 형변환 

원칙적으로는 모든 형변환에 캐스트연산자를 이용한 형변환이 이루어져야 하지만, 값의 표현범위가 작은 자료형에서 큰 자료형의 변환은 값의 손실이 없으므로 캐스트 연산자를 생략할 수 있도록 했다. 
그렇다고 해서 형변환이 이루어지지 않는 것은 아니고, 캐스트 연산자를 생략한 경우에도 JVM의 내부에서 자동적으로 형변환이 수행된다. 
반면에 값의 표현범위가 큰 자료형에서 작은 자료형으로의 변환은 값이 손실될 가능성이 있으므로, JVM이 자동적으로 형변환하지 않고 프로그래머에게 캐스트 연산자를 이용하여 형변환을 하도록 강요하고 있다. 

위의 예제에서도 int i = (int)b;를 int i = b;와 같이 캐스트 연산자를 생략할 수 있으나 byte b2 = (byte)i2;에서는 캐스트 연산자를 생략해서 byte b2 = i2;와 같이 할 수 없다. 
값의 표현범위가 큰 자료형에서 작은 자료형으로의 형변환에 캐스트 연산자를 사용하지 않으면 컴파일시 에러가 발생한다. 



[그림2-3]기본형의 자동형변환이 가능한 방향 

위의 그림은 형변환이 가능한 7개의 기본형을 왼쪽부터 오른쪽으로 표현할 수 있는 값의 범위가 작은 것부터 큰 것의 순서로 나열한 것이다. 
화살표방향으로의 변환, 즉 왼쪽에서 오른쪽으로의 변환은 캐스트 연산자를 사용하지 않아도 자동형변환이 되는 변환이며, 그 반대 방향으로의 변환은 반드시 캐스트 연산자를 이용한 형변환을 해야 한다. 

보통 자료형의 크기가 큰 것일 수록 값의 표현범위가 크기 마련이지만, 실수형은 정수형과는 값을 표현하는 방식이 다르기 때문에 같은 크기일지라도 실수형이 정수형보다 훨씬 더 큰 표현 범위를 갖기 때문에 float와 double이 같은 크기인 int와 long보다 오른쪽에 위치한다. 
short과 char은 모두 2 byte의 크기를 갖지만, short의 범위는 -215 ~ 215-1(-32768~32767)이고 char의 범위는 0~216-1(0~65535)이므로 서로 범위가 달라서 둘 중 어느 쪽으로의 형변환도 값 손실이 발생할 수 있으므로 자동적으로 형변환이 수행될 수 없다. 

위의 그림을 참고하여 형변환 시에 캐스트 연산자를 생략할 수 있는지 또는 캐스트 연산자를 사용해야하는지를 결정하도록 한다. 

<< 요약정리 >>
 1. boolean을 제외한 나머지 7개의 기본형들은 서로 형변환이 가능하다. 
 2. 기본형과 참조형 간에는 서로 형변환이 되지 않는다. 
 3. 서로 다른 타입의 변수간의 연산에는 형변환이 요구되지만, 값의 범위가 작은 타입에서 큰 타입으로의 변환은 생략할 수 있다. 



출처 - http://cafe.naver.com/javachobostudy/21






객체의 형변환 예제 입니다.
Animal.java    Dog.java    Duck.java    InheriTest.java

※ 객체의 형변환은 상속괸계가 있을 때 하위클래스에서 상위클래스로의 
    형변환이 가능하나 상속관계가 없고 상위클래스에서  하위클래스로의 
    형변환은 불가능하다 .

1. Animal class

public class Animal {
     public String name;
 
     public String toString(){
          return name;
     }
}

2. Dog class

public class Dog extends Animal{
     public String name;
 
     public void back(){
          System.out.println("멍멍~~~~~");
     }
}

3. Duck class

public class Duck extends Animal {
     public String name;
 
     public void fly(){
          System.out.println("파닥파닥!!");
     }
}

4. InheriTest class

public class InheriTest {

     public static void main(String[] args) {
          Dog dog = new Dog();
          Animal duck = new Duck();
          Animal animal = new Animal();
  
          dog.name = "바둑이";
          duck.name = "도날드 덕";
          animal.name = "짐승";
  
          System.out.println(dog.name);
          System.out.println(duck.name);
          // 자식클래스를 부모클래스로 형변환 가능
          System.out.println(((Animal)dog).name);
          // 원래 자식 클래스로 되돌림
          System.out.println(((Duck)duck).name);
          // 부모클래스를 자식클래스로 형변환 불가 Runtime Exception 에러가 난다.
          System.out.println(((Duck)animal).name);
     }

}



출처 - http://pdw213.egloos.com/3088336






4) 객체의 캐스팅


객체도 앞에 설명한 변환뿐 아니라 (명시적인) 캐스팅이 필요한 경우가 있다. 물론 묵시적으로 시스템에 의하여 변환이 자동으로 이루어질 수 없는 경우에 강제적으로 프로그래머가 객체 타입을 바꾸기 위하여 사용되는 것이다. 예를들어 하위클래스 객체로 상위클래스를 객체를 바꾸려면 명시적인 캐스팅을 해야한다.


객체와 객체참조의 개념을 확실히 이해하는 것이 필요하다. 참조는 객체를 가리키는 주소를 담고 있은 변수이다. 객체는 메모리상에 잡혀 있고 사용자는 직접 객체를 엑세스 할 수 없으며 반드시 객체 참조를 통하여만 엑세스할 수 있다.


Freshman kim = new Freshman();


위에서 kim은 Freshman 타입의 객체 하나를 가리키는 참조변수이다. 객체참조 변수의 타입은 위와 같이 선언문에 명시되며 컴파일시 정해진다. 즉, 변수 kim의 타입은 Freshman이다. 한편 참조 변수가 가리킬수 있는 객체의 종류는 클래스, 인터페이스, 그리고 어레이 세가지이다.


그런데 이제 문제가 될 수 있은 것은 참조변수, 예를들면 위에서 kim의 타입이 필요에 따라 프로그램 실행 도중에 Freshman 뿐 아니라 Human, Student, Object 등으로 바뀌어야 하는 경우가 있다는 것이다.(예를들면 미리 만들어 놓은 메소드를 사용하는 경우 인자로 상위의 객체 타입을 받도록 되어있을 수가 있다.) 즉, 컴파일 시의 타입과 실행시의 타입이 달라질 수 있으며 이로 인하여 객체의 캐스팅은 다소 복잡하게 되는 것이다.


한편 객체의 캐스팅의 대상은 (변환과 달리) 다음과 같은 네가지 타입으로 분류하여야 한다.


- 클래스 (final)

- 클래스 (non-final)

- 인터페이스 타입

- 어레이 타입


객체의 캐스팅은 아래와 같은 형태로 이루어진다.


OldType A = new OldType();

NewType B = new NewType();

B = (NewType) A; // 캐스팅


위에서 A 또는 B로 각각 네가지 타입이 올 수 있으므로 모두 16 가지의 타입 변환 조합이 있을 수 있으며 이들의 "컴파일시의" 허용관계를 아래 표에 정리하였다.


(guide 115)

 

객체의 컴파일시의 캐스팅의 허용 원칙은 다음과 같다.


- OldType과 NewType이 모두 클래스인 경우 한 클래스는 반드시 다른 클래스의 하위클래스여야 한다.

- OldType과 NewType이 모두 어레이인 경우 각 어레이는 모두 참조변수 타입의 어레이여야 하고 각 엘레먼트 단위로 캐스팅이 가능해야 한다.

- 인터페이스와 non-final 클래스 간에는 항상 캐스팅이 가능하다.

 

프로그램 실행중의 객체의 캐스팅의 허용 원칙은 다음과 같다.

- NewType이 클래스인 경우 캐스팅될 클래스는 반드시 NewType 이거나 NewType을 상속한 것이어야 한다.

- NewType이 인터페이스인 경우 캐스팅될 클래스는 반드시 NewType을 구현하고 있어야 한다.


(예) 앞의 Student 클래스 참조


Student a;

Junior park = new Junior();


park = (Junior) a; // 컴파일 오류는 없으나 실행중 exception 발생


위의 마지막 라인은 컴파일시에는 오류가 발생하지 않는다. 왜냐하면 Junior 타입과 Student 타입은 서로 상속관계이므로. 그러나 이 코드는 실행중 ClassCastException 예외가 발생한다.


이러한 예외를 피하려면 다음과 같이 하위클래스의 생성자를 사용하여 a를 생성하여야 한다.


Student a = new Junior(); // 하위클래스의 생성자를 사용하는 방법

park = (Junior) a; // 예외가 발생하지 않음


객체를 인터페이스로 캐스팅하는 예를 보자


Freshman kim, lee ;

Exam mid_term;

kim = new Freshman();

mid_term = kim; // O.K. 클래스 타입을 인터페이스 타입으로 변환

lee = mid_term; // 컴파일 오류발생. 자동변환을 허용하지 않는다.


위의 마지막 라인에서 클래스와 인터페이스간의 변환은 일체 허용하지 않는다. 따라서 다음과 같이 하여야 한다.


lee = (Freshman) mid_term; // O.K. 캐스팅은 허용



출처 - http://blog.naver.com/one21212121?Redirect=Log&logNo=120011492440






업캐스팅

- 특정 객체가 하위 클래스의 형에서 상위의 클래스의 형을 캐스팅되는 것.

 

업캐스팅과 연결되는 기술들

- 상속(Inheritance)

- 오버라이딩(Overriding)

- 가상 메서드(Virtual Method)

- 추상 클래스(Abstract Class)

- 인터페이스(Interface)

 

다운캐스팅

- 업캐스팅(Upcasting)한 것을 다시 원래의 형으로 복원시켜 주는 작업

 

다운캐스팅의 특징

- 업캐스팅된 것만 다운캐스팅 시킬 수 있다.

- 다운캐스팅은 강제 캐스팅을 원칙으로 한다.

 

형 확인 후 다운 캐스팅

class OkDown extends Object{ 

    //내용없음

 

public class OkDownMain{

    public static void main(String[] args) {

        OkDown d = new OkDown(); 

        Object obj = d; //업캐스팅은 묵시적으로 이루어진다. 

        System.out.println("Object obj => " + obj);

 

        if( obj instanceof OkDown ) { //형확인 

            OkDown okt = (OkDown)obj; //업캐스팅된 것을 다시 다운캐스팅-강제캐스팅  

            System.out.println("OkDown t => " + okt);

        }else{

            System.out.println("형이 맞지 않습니다.");

        }

    } 


출처 - http://blog.naver.com/doorimini/70170741248