Java 변수의 타입은 크게 참조 타입, 원시 타입 두 개로 나눌 수 있다.
먼저 참조형 변수의 캐스팅에 대해 알아보자
참조형 변수의 캐스팅
먼저 참조형 변수( 배열, 리스트, 클래스) 등의 상속관계간 캐스팅은 업 캐스팅, 다운 캐스팅의 두 종류가 있다.
업 캐스팅 : 자식 클래스 타입 → 부모 클래스 타입
다운 캐스팅 : 부모 클래스 타입 → 자식 클래스 타입
[업 캐스팅]
자식클래스의 멤버나 함수의 개수는 부모클래스의 멤버나 함수의 개수보다 많거나 같은데,
자식 클래스를 부모 클래스로 형변환(캐스팅) 하게 되면 자식 클래스만 가지고 있던 멤버변수, 함수를 사용하지 못하게 된다.
업 캐스팅은 명시적으로 캐스팅하지 않아도 된다!
class Food{
// 음식에 대한 공통적인 특성
float taste;
}
class KoreanFood extends Food{
// Food 부모 클래스에 정의된 특성들을 그대로 차용하거나 변경(overriding)
// 한식의 경우에만 필요한 특성의 정의 추가
float kimchi;
}
public static void main(String[] args){
KoreanFood breakfast = new KoreanFood();
Food food = breakfast; // implicit 암시적 캐스팅
Food food_e = (Food)breakfast; // explicit 명시적 캐스팅
food.kimchi // <-- 접근 불가
}
KoreanFood 형식에서 Food 형식으로 업 캐스팅 한 모습이다.
food는 이제 KoreanFood 형식의 객체가 아니므로 kimchi 필드에 더 이상 접근할 수 없다.
[다운 캐스팅]
부모클래스를 자식클래스로 캐스팅하는 경우 애초에 캐스팅 할 때부터 많은 제약이 따른다.
애초에 조금만 생각해보면 업 캐스팅과 반대로(정확히 말하면 반대 개념이라고 생각하면 안된다.) 부모클래스에는 자식클래스의 변수 kimchi가 없는데, 내 kimchi를 포함하지 않은 상태의 breakfast 변수를 KoreanFood 클래스 타입이라고 인정해달라고 하는 것과 같다.
컴파일러 입장에서는 정말 쌩뚱맞은 부탁이 아닐 수 없는데, 말만 들어도 안될 것 같은 이 다운캐스팅은 언제 사용되냐면 바로,
이전에 업 캐스팅 되었던 객체를 다시 자식클래스로 되돌려 놓는 것 이라고 할 수 있다.
예를들면 KoreanFood, ChineseFood, FranchFood 등 각국의 음식 클래스타입들을 꼭 하나의 Food라는 클래스타입 이여야만 할 수 있는 연산들을 진행한 후에 다시 원래대로 되돌려 놓는 상황을 생각할 수 있다.
class Food{
// 음식에 대한 공통적인 특성
float taste;
}
class KoreanFood extends Food{
// Food 부모 클래스에 정의된 특성들을 그대로 차용하거나 변경(overriding)
// 한식의 경우에만 필요한 특성의 정의 추가
float kimchi;
}
public static void main(String[] args){
KoreanFood breakfast = new KoreanFood();
Food food = breakfast; // implicit 암시적 업 캐스팅
//뭔가 연산
// 지금 food엔 kimchi 멤버가 없음
KoreanFood breakfast2 = (KoreanFood)food; // 다운 캐스팅
// 지금 breakfast2 엔 kimchi 멤버가 있음
!
}
다운캐스팅에서 가장 유의해야할 것은 컴파일러가 알아채지 못하는 오류가 존재한다는 것이다.
class Food{
// 음식에 대한 공통적인 특성
float taste;
}
class KoreanFood extends Food{
// Food 부모 클래스에 정의된 특성들을 그대로 차용하거나 변경(overriding)
// 한식의 경우에만 필요한 특성의 정의 추가
float kimchi;
}
public static void main(String[] args){
Food lunch = new Food();
KoreanFood lunch2 = (KoreanFood)lunch; // 다운 캐스팅 불가
}
위의 경우에는 처음에 언급했던 그냥 부모클래스타입의 변수를 자식클래스타입으로 다운캐스팅 하는 것이다.
컴파일단계에서 오류가 없고(빨간줄 안뜸) 런타임 단계에서 ClassCastException 이 발생할 것이다.
물론 예외클래스의 이름을 보고 얼추 무슨 문제인지 알 수 있겠지만, 예외가 발생할 수 있지만 컴파일러 문제가 발생하지 않는 것은 큰 문제가 된다.
원활한 협업을 위한 디펜시브 프로그래밍 철학에 맞춰 예외처리를 잘 하고 가드(Guard) 절을 잘 활용하자
그래서 다운캐스팅때 사용될 수 있는 가드는 뭐냐?
instanceof 로 다운 캐스팅 가능 확인하기
public static void main(String[] args){
Food lunch = new Food();
// 1. 다운캐스팅을 해야징
if( lunch instanceof KoreanFood){
// true의 경우라면 lunch는 지금 Food 클래스타입이지만,
// 사실은 KoreanFood클래스타입이였다가 업캐스팅 되었다는 소리.
// 원하던 로직 구현
KoreanFood lunch2 = (KoreanFood)lunch; // 다운 캐스팅 불가
}
// 일반적인 가드절인 널체크, indexOutOfRange 체크, 등과 같은 역할,
// lunch 객체가 KoreanFood 클래스의 객체인지 검사
if( lunch instanceof KoreanFood == false){
//false의 경우라면 lunch는 애초에 KoreanFood 클래스타입인 적이 없었다.
//컨벤션에 맞거나 알아보기 쉬운 예외 던지기
throw new DownCastingFailedException();
}
}
원시 타입 캐스팅
참조 타입의 경우에는 상속관계와 업,다운 캐스팅이 있었다.
원시 타입의 경우에는 아마 업,다운캐스팅이라고 부르진 않지만
명시적, 암시적 캐스팅의 개념은 비슷한 부분이 있다.
원시 타입 변수의 캐스팅에 대해서 기초적인부분은 생략하고,
long myLong = 10L;
int myInt = myLong; // 빨간줄, 컴파일러가 소리를 지르고 있다.
int myInt = (int) myLong; // ok, 컴파일러가 한시름 놓았다.
먼저 long to int 캐스팅의 경우 다운 캐스팅과 비슷하다고 보면 된다. 사용자가 실수했을 것을 컴파일러가 염려해서 이렇게 말한다고 생각하면 된다.
컴파일러 : “너 이 long 타입 변수 myLong 에 Int 타입의 변수가 담을 수 없는 정수값이 들어있으면 값이 자동으로 작아질텐데 알고는 있는거야?”
여기서 알고는 있는거야? 가 빨간줄이다.
그래서 3번째 줄에서 우리는 나 그런 위험을 알고있어, Int 타입의 범위를 넘지 않는걸 확신하거나, 넘으면 넘은만큼은 버리기로 다짐했어. 라는 뜻으로 (int) 명시적 캐스팅을 해준 것.
아래는 최근 가장 헷갈렸던 원시타입 캐스팅 예제
import java.util.*;
import java.util.stream.*;
class Solution {
public Optional<int[]> findPoint(int[] l1, int[] l2){
long a = l1[0], b = l1[1], e = l1[2];
long c = l2[0], d = l2[1], f = l2[2];
long denom = (a*d - b*c);
if(denom == 0)
return Optional.empty();
long numerX = (b*f - e*d);
long numerY = (e*c - a*f);
if(numerX % denom == 0 && numerY % denom == 0){
return Optional.of(new int[]{(int)(numerX / denom), (int)(numerY / denom)});
}
return Optional.empty();
}
public String[] solution(int[][] line) {
List<int[]> points = new ArrayList<int[]>();
for(int i = 0 ; i < line.length; i++){
for(int j = i+1; j<line.length; j++){
Optional<int[]> point = findPoint(line[i], line[j]);
if(point.isPresent())
points.add(point.get());
}
}
// x 오름차순
points.sort((a1, a2) -> a1[0] - a2[0]);
int minX = points.get(0)[0];
int maxX = points.get(points.size()-1)[0];
// y 오름차순
points.sort((a1, a2) -> a1[1] - a2[1]);
int minY = points.get(0)[1];
int maxY = points.get(points.size()-1)[1];
char[][] result = new char[maxY-minY+1][maxX-minX+1];
for(int i = 0 ; i < result.length; i++)
Arrays.fill(result[i], '.');
for(int[] point : points){
result[point[1] - minY][point[0]-minX] = '*';
}
List<String> reversedAnswer = Arrays.stream(result)
.map(String::valueOf)
.collect(Collectors.toList());
Collections.reverse(reversedAnswer);
return reversedAnswer.stream()
.toArray(String[]::new);
}
}
가장 문제였던 부분은 여긴데,
public Optional<int[]> findPoint(int[] l1, int[] l2){
int a = l1[0], b = l1[1], e = l1[2];
int c = l2[0], d = l2[1], f = l2[2];
long denom = (long)(a*d - b*c); // <- 여기서
문제에서 코드에 ad - bc 계산을 모두 마친 후 long 타입으로 캐스팅 하면 될 것이라고 생각했는데,
내가 선언한 a~f 까지의 변수는 -100,000 ~ 100,000 사이의 값을 가지므로 애초에 a*d에서 integer overflow가 발생할 수 있었다.
따라서 오류가 발생하지 않으려면 아래처럼 작성해야한다.
long denom = ((long)a*d - (long)b*c);
(long) a*d 에서 문제가 발생하지 않았다는 것은 a*d 보다 (long) a, 즉 a의 long 형식으로의 캐스팅 연산이 먼저 일어났음을 의미한다. 이는 괄호 연산자가 * 연산보다(사실 가장) 우선순위가 높기 때문이다.

Java 변수의 타입은 크게 참조 타입, 원시 타입 두 개로 나눌 수 있다.
먼저 참조형 변수의 캐스팅에 대해 알아보자
참조형 변수의 캐스팅
먼저 참조형 변수( 배열, 리스트, 클래스) 등의 상속관계간 캐스팅은 업 캐스팅, 다운 캐스팅의 두 종류가 있다.
업 캐스팅 : 자식 클래스 타입 → 부모 클래스 타입
다운 캐스팅 : 부모 클래스 타입 → 자식 클래스 타입
[업 캐스팅]
자식클래스의 멤버나 함수의 개수는 부모클래스의 멤버나 함수의 개수보다 많거나 같은데,
자식 클래스를 부모 클래스로 형변환(캐스팅) 하게 되면 자식 클래스만 가지고 있던 멤버변수, 함수를 사용하지 못하게 된다.
업 캐스팅은 명시적으로 캐스팅하지 않아도 된다!
class Food{
// 음식에 대한 공통적인 특성
float taste;
}
class KoreanFood extends Food{
// Food 부모 클래스에 정의된 특성들을 그대로 차용하거나 변경(overriding)
// 한식의 경우에만 필요한 특성의 정의 추가
float kimchi;
}
public static void main(String[] args){
KoreanFood breakfast = new KoreanFood();
Food food = breakfast; // implicit 암시적 캐스팅
Food food_e = (Food)breakfast; // explicit 명시적 캐스팅
food.kimchi // <-- 접근 불가
}
KoreanFood 형식에서 Food 형식으로 업 캐스팅 한 모습이다.
food는 이제 KoreanFood 형식의 객체가 아니므로 kimchi 필드에 더 이상 접근할 수 없다.
[다운 캐스팅]
부모클래스를 자식클래스로 캐스팅하는 경우 애초에 캐스팅 할 때부터 많은 제약이 따른다.
애초에 조금만 생각해보면 업 캐스팅과 반대로(정확히 말하면 반대 개념이라고 생각하면 안된다.) 부모클래스에는 자식클래스의 변수 kimchi가 없는데, 내 kimchi를 포함하지 않은 상태의 breakfast 변수를 KoreanFood 클래스 타입이라고 인정해달라고 하는 것과 같다.
컴파일러 입장에서는 정말 쌩뚱맞은 부탁이 아닐 수 없는데, 말만 들어도 안될 것 같은 이 다운캐스팅은 언제 사용되냐면 바로,
이전에 업 캐스팅 되었던 객체를 다시 자식클래스로 되돌려 놓는 것 이라고 할 수 있다.
예를들면 KoreanFood, ChineseFood, FranchFood 등 각국의 음식 클래스타입들을 꼭 하나의 Food라는 클래스타입 이여야만 할 수 있는 연산들을 진행한 후에 다시 원래대로 되돌려 놓는 상황을 생각할 수 있다.
class Food{
// 음식에 대한 공통적인 특성
float taste;
}
class KoreanFood extends Food{
// Food 부모 클래스에 정의된 특성들을 그대로 차용하거나 변경(overriding)
// 한식의 경우에만 필요한 특성의 정의 추가
float kimchi;
}
public static void main(String[] args){
KoreanFood breakfast = new KoreanFood();
Food food = breakfast; // implicit 암시적 업 캐스팅
//뭔가 연산
// 지금 food엔 kimchi 멤버가 없음
KoreanFood breakfast2 = (KoreanFood)food; // 다운 캐스팅
// 지금 breakfast2 엔 kimchi 멤버가 있음
!
}
다운캐스팅에서 가장 유의해야할 것은 컴파일러가 알아채지 못하는 오류가 존재한다는 것이다.
class Food{
// 음식에 대한 공통적인 특성
float taste;
}
class KoreanFood extends Food{
// Food 부모 클래스에 정의된 특성들을 그대로 차용하거나 변경(overriding)
// 한식의 경우에만 필요한 특성의 정의 추가
float kimchi;
}
public static void main(String[] args){
Food lunch = new Food();
KoreanFood lunch2 = (KoreanFood)lunch; // 다운 캐스팅 불가
}
위의 경우에는 처음에 언급했던 그냥 부모클래스타입의 변수를 자식클래스타입으로 다운캐스팅 하는 것이다.
컴파일단계에서 오류가 없고(빨간줄 안뜸) 런타임 단계에서 ClassCastException 이 발생할 것이다.
물론 예외클래스의 이름을 보고 얼추 무슨 문제인지 알 수 있겠지만, 예외가 발생할 수 있지만 컴파일러 문제가 발생하지 않는 것은 큰 문제가 된다.
원활한 협업을 위한 디펜시브 프로그래밍 철학에 맞춰 예외처리를 잘 하고 가드(Guard) 절을 잘 활용하자
그래서 다운캐스팅때 사용될 수 있는 가드는 뭐냐?
instanceof 로 다운 캐스팅 가능 확인하기
public static void main(String[] args){
Food lunch = new Food();
// 1. 다운캐스팅을 해야징
if( lunch instanceof KoreanFood){
// true의 경우라면 lunch는 지금 Food 클래스타입이지만,
// 사실은 KoreanFood클래스타입이였다가 업캐스팅 되었다는 소리.
// 원하던 로직 구현
KoreanFood lunch2 = (KoreanFood)lunch; // 다운 캐스팅 불가
}
// 일반적인 가드절인 널체크, indexOutOfRange 체크, 등과 같은 역할,
// lunch 객체가 KoreanFood 클래스의 객체인지 검사
if( lunch instanceof KoreanFood == false){
//false의 경우라면 lunch는 애초에 KoreanFood 클래스타입인 적이 없었다.
//컨벤션에 맞거나 알아보기 쉬운 예외 던지기
throw new DownCastingFailedException();
}
}
원시 타입 캐스팅
참조 타입의 경우에는 상속관계와 업,다운 캐스팅이 있었다.
원시 타입의 경우에는 아마 업,다운캐스팅이라고 부르진 않지만
명시적, 암시적 캐스팅의 개념은 비슷한 부분이 있다.
원시 타입 변수의 캐스팅에 대해서 기초적인부분은 생략하고,
long myLong = 10L;
int myInt = myLong; // 빨간줄, 컴파일러가 소리를 지르고 있다.
int myInt = (int) myLong; // ok, 컴파일러가 한시름 놓았다.
먼저 long to int 캐스팅의 경우 다운 캐스팅과 비슷하다고 보면 된다. 사용자가 실수했을 것을 컴파일러가 염려해서 이렇게 말한다고 생각하면 된다.
컴파일러 : “너 이 long 타입 변수 myLong 에 Int 타입의 변수가 담을 수 없는 정수값이 들어있으면 값이 자동으로 작아질텐데 알고는 있는거야?”
여기서 알고는 있는거야? 가 빨간줄이다.
그래서 3번째 줄에서 우리는 나 그런 위험을 알고있어, Int 타입의 범위를 넘지 않는걸 확신하거나, 넘으면 넘은만큼은 버리기로 다짐했어. 라는 뜻으로 (int) 명시적 캐스팅을 해준 것.
아래는 최근 가장 헷갈렸던 원시타입 캐스팅 예제
import java.util.*;
import java.util.stream.*;
class Solution {
public Optional<int[]> findPoint(int[] l1, int[] l2){
long a = l1[0], b = l1[1], e = l1[2];
long c = l2[0], d = l2[1], f = l2[2];
long denom = (a*d - b*c);
if(denom == 0)
return Optional.empty();
long numerX = (b*f - e*d);
long numerY = (e*c - a*f);
if(numerX % denom == 0 && numerY % denom == 0){
return Optional.of(new int[]{(int)(numerX / denom), (int)(numerY / denom)});
}
return Optional.empty();
}
public String[] solution(int[][] line) {
List<int[]> points = new ArrayList<int[]>();
for(int i = 0 ; i < line.length; i++){
for(int j = i+1; j<line.length; j++){
Optional<int[]> point = findPoint(line[i], line[j]);
if(point.isPresent())
points.add(point.get());
}
}
// x 오름차순
points.sort((a1, a2) -> a1[0] - a2[0]);
int minX = points.get(0)[0];
int maxX = points.get(points.size()-1)[0];
// y 오름차순
points.sort((a1, a2) -> a1[1] - a2[1]);
int minY = points.get(0)[1];
int maxY = points.get(points.size()-1)[1];
char[][] result = new char[maxY-minY+1][maxX-minX+1];
for(int i = 0 ; i < result.length; i++)
Arrays.fill(result[i], '.');
for(int[] point : points){
result[point[1] - minY][point[0]-minX] = '*';
}
List<String> reversedAnswer = Arrays.stream(result)
.map(String::valueOf)
.collect(Collectors.toList());
Collections.reverse(reversedAnswer);
return reversedAnswer.stream()
.toArray(String[]::new);
}
}
가장 문제였던 부분은 여긴데,
public Optional<int[]> findPoint(int[] l1, int[] l2){
int a = l1[0], b = l1[1], e = l1[2];
int c = l2[0], d = l2[1], f = l2[2];
long denom = (long)(a*d - b*c); // <- 여기서
문제에서 코드에 ad - bc 계산을 모두 마친 후 long 타입으로 캐스팅 하면 될 것이라고 생각했는데,
내가 선언한 a~f 까지의 변수는 -100,000 ~ 100,000 사이의 값을 가지므로 애초에 a*d에서 integer overflow가 발생할 수 있었다.
따라서 오류가 발생하지 않으려면 아래처럼 작성해야한다.
long denom = ((long)a*d - (long)b*c);
(long) a*d 에서 문제가 발생하지 않았다는 것은 a*d 보다 (long) a, 즉 a의 long 형식으로의 캐스팅 연산이 먼저 일어났음을 의미한다. 이는 괄호 연산자가 * 연산보다(사실 가장) 우선순위가 높기 때문이다.
