...
래퍼 클래스 (Wrapper Class)
이전 강의글에서 우리는 자바의 자료형은 기본 타입(primitive type)과 참조 타입(reference type) 으로 나누어지고 각 특징에 대해 알아보았다.
프로그래밍을 하다 보면 기본 타입의 데이터를 객체로 표현해야 하는 경우가 종종 생기게 된다.
예를 들어 메소드의 인수로 객체 타입만이 요구되면, 기본 타입의 데이터를 그대로 사용할수 없기 때문에 어떠한 변환 작업이 필요해 진다.
또한 멀티스레드 환경에서 동기화 데이터를 사용해야 할 경우 이를 객체화 해야 할 필요성이 생긴다.
이럴 때에 기본 타입(primitive type)을 객체로 다루기 위해서 사용하는 클래스들을 래퍼 클래스(wrapper class)라고 한다.
자바는 모든 기본타입(primitive type)은 값을 갖는 객체를 생성할 수 있다.
이런 객체를 포장 객체라고도 하는데, 기본 타입의 값을 내부에 두고 포장하는 것처럼 보이기 때문이다.
포장된 물건을 바꿀수 없듯이, 래퍼 클래스로 감싸고 있는 기본 타입 값은 외부에서 변경할 수 없다.
만약 값을 변경하고 싶다면 새로운 포장 객체를 만들어야 한다.
래퍼 클래스는 모두 java.lang 패키지에 포함되어 제공된다.
그래서 별다른 패키지 불러오기 없이 곧바로 소스 단에서 사용이 가능하다.
래퍼 클래스를 이용하면 각 타입에 해당하는 데이터를 파라미터로 전달받아 해당 값을 가지는 객체로 만들어준다.
Integer num1 = new Integer(5); // 기본형 타입 정수를 래퍼 클래스로 감싸 객체화
Integer num1 = 5; // 이런식으로도 표현 가능
Double num2 = new Double(1.11); // 기본형 타입 실수를 래퍼 클래스로 감싸 객체화
Double num2 = 1.11;
자바의 8개의 기본 타입에 대한 래퍼 클래스는 다음과 같다.
기본 타입 | 래퍼 클래스 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
대부분의 래퍼 클래스는 기본 타입의 첫 번째 단어를 대문자로 바꿔준 이름으로 되어있지만, 이 중에서 Integer 와 Character 클래스만이 자신의 기본 타입과 이름이 약간 다르다는 점은 유의하자.
박싱(Boxing) & 언박싱(UnBoxing)
위에서 래퍼 클래스는 값을 포장하여 객체로 만드는 것이라고 했다.
값을 포장하여 객체로 만드는 것 까지는 좋지만, 만일 값을 더하거나 등 변환시켜야 할 필요가 생길 경우 포장을 다시 뜯을 필요가 있다.
이러한 행위를 전문적인 용어로 말하자면, 박싱(Boxing) 과 언박싱(UnBoxing) 이라고 한다.
- Boxing : 기본 타입의 데이터 → 래퍼 클래스의 인스턴스로 변환
- UnBoxing : 래퍼 클래스의 인스턴스에 저장된 값 → 기본 타입의 데이터로 변환
래퍼 클래스는 산술 연살을 위해 정의된 클래스가 아니다.
생성된 인스턴스의 값만을 참조할수 있기 때문에 따라서 래퍼 클래스 인스턴스에 저장된 값을 직접 변경이 불가능하다.
그래서 래퍼 클래스를 언박싱 한 뒤에 값을 변경한 뒤 박싱해야 하는 중간 단계를 거칠 필요가 있다.
// 박싱
Integer num = new Integer(20); // Integer 래퍼 클래스 num 에 21 의 값을 저장
// 언박싱 (intValue)
int n = num.intValue(); // 래퍼 클래스 num 의 값을 꺼내 가져온다.
// 재 포장(박싱)
n = n + 100; // 120
num = new Integer(n);
각 Wrapper 타입의 클래스의 언박싱 메소드들 다음과 같다.
메소드 | 반환값 | 설명 |
booleanValue() | boolean | 기본형 데이터를 문자열로 바꾼 뒤에 반환 |
byteValue() | byte | 객체의 값을 byte 값으로 변환하여 반환 |
doubleValue() | double | 객체의 값을 double 값으로 변환하여 반환 |
floatValue() | float | 객체의 값을 float 값으로 변환하여 반환 |
intValue() | int | 객체의 값을 int 값으로 변환하여 반환 |
longValue() | long | 객체의 값을 long 값으로 변환하여 반환 |
shortValue() | short | 객체의 값을 short 값으로 변환하여 반환 |
자동 박싱(AutoBoxing) & 자동 언박싱(AutoUnBoxing)
JDK 1.5 부터는 박싱과 언박싱이 필요한 상황에 자바 컴파일러가 자동으로 처리해주기 시작했다.
이러한 자동화된 박싱과 언박싱을 오토박싱 (AutoBoxing) 과 오토언박싱 (AutoUnBoxing) 이라고 부른다.
기본타입 값을 직접 박싱, 언박싱하지 않아도 래퍼 클래스 변수에 대입만 하면 자동으로 박싱과 언박싱이 된다.
/* 기존 박싱 & 언박싱 */
Integer num = new Integer(17); // 박싱
int n = num.intValue(); // 언박싱
/* 오토 박싱 & 언박싱 */
Integer num = 17; // new Integer() 생략
int n = num; // intValue() 생략
이처럼 오토 박싱을 이용하면 new 키워드를 사용하지 않고도 자동으로 인스턴스를 생성할 수 있으며, 언박싱 메소드를 사용하지 않고도, 오토 언박싱을 이용하여 인스턴스에 저장된 값을 바로 참조할 수 있게 된다.
래퍼 클래스 연산
오토 박싱 & 언박싱 기능을 이용해 다음과 같이 래퍼 객체를 직접 연산이 가능해지게 된다.
원래는 래퍼 클래스는 직접 연산이 불가능 하지만 컴파일러가 스스로 판단해 자동으로 언박싱하여 연산 하기 때문에 다음 구문이 허용되는 것이다.
Integer num1 = new Integer(7); // 박싱
Integer num2 = new Integer(3); // 박싱
int int1 = num1.intValue(); // 언박싱
int int2 = num2.intValue(); // 언박싱
// 박싱된 객체를 오토 언박싱하여 연산하고 다시 박싱하여 저장
Integer result1 = num1 + num2; // 10
Integer result2 = int1 - int2; // 4
int result3 = num1 * int2; // 21
[ NullPointException 에러 ]
래퍼 객체를 다룰때 가장 많이 보게 될 에러 예외 메세지 일 것이다.
null 값을 가지는 wrapper 클래스 객체와 기본형(primitive type) 값을 연산할 경우, NullPointException이 발생하게 된다.
이는 피연산자에 기본형 int 값이 존재하여 wrapper 클래스 객체를 auto unboxing 하는 중에 발생하는 에러이다.
래퍼 클래스 동등 비교
오토 언박싱을 통해 객체 값을 더하고 빼고는 문제는 없지만, 객체 값을 비교를 할때는 조심해야 한다.
인스턴스에 저장된 값에 대한 동등 여부 판단은 동등 연산자 == 으로는 값을 비교하는게 아닌 객체의 주소값을 비교해서 의도적이지 않은 작동이 일어나기 때문이다.
Integer num1 = new Integer(100);
Integer num2 = new Integer(100);
num1 == num2; // 참조형과 참조형 비교 false
따라서 래퍼 클래스의 객체 값 비교는 포장 내부의 값을 얻어 비교해야 되기 때문에, 직접 언박싱해서 비교하던가, equals() 메소드를 통해 바로 비교가 가능하다.
Integer num1 = new Integer(10);
Integer num2 = new Integer(20);
Integer num3 = new Integer(10);
System.out.println(num1 == num3); // false
System.out.println(num1.equals(num3)); // true
// 동등 비교 외의 연산은 문제 없다.
System.out.println(num1 < num2); // true
System.out.println(num1 + num2); // 30
대신 래퍼 클래스와 기본 자료형과의 비교는 자동으로 오토박싱과 언박싱을 해주기 때문에 == 연산과 equals 연산 모두 가능하다.
Integer num = new Integer(10); // 래퍼 클래스1
Integer num2 = new Integer(10); // 래퍼 클래스2
int i = 10; // 기본타입
// 래퍼클래스 == 기본타입
System.out.println(num == i); // true
// 래퍼클래스.equals(기본타입)
System.out.println(num.equals(i)); // true
// 래퍼클래스 == 래퍼클래스
System.out.println(num == num2); // false (invalid)
// 래퍼클래스.equals(래퍼클래스)
System.out.println(num.equals(num2)); // true
자료형 변환 메소드
객체를 포장하는 기능 외에도, 래퍼 클래스는 자체 지원하는 parse타입() 메소드를 이용하여 데이터 타입을 형 변환 할때도 유용히 쓰인다.
String str = "10";
String str2 = "10.5";
String str3 = "true";
byte b = Byte.parseByte(str);
int i = Integer.parseInt(str);
short s = Short.parseShort(str);
long l = Long.parseLong(str);
float f = Float.parseFloat(str2);
double d = Double.parseDouble(str2);
boolean bool = Boolean.parseBoolean(str3);
System.out.println("문자열 byte값 변환 : "+b);
System.out.println("문자열 int값 변환 : "+i);
System.out.println("문자열 short값 변환 : "+s);
System.out.println("문자열 long값 변환 : "+l);
System.out.println("문자열 float값 변환 : "+f);
System.out.println("문자열 double값 변환 : "+d);
System.out.println("문자열 boolean값 변환 : "+bool);
Boxing & Unboxing 성능 고려
기능적 편의성을 위하여 오토 박싱 / 언박싱을 제공하지만, 다른 타입간의 형 변환은 어플리케이션의 성능에 영향을 미치게 된다. 비록 사소한 차이 일지라도 어플리케이션의 성능 측면에서 봤을때 반드시 필요한 상황이 아니라면 지양 해야 하는 것이 옳다.
이해를 돕기위해 100만 건의 데이터를 다루는 예제를 들어보겠다.
Auto Boxing을 포함한 연산
public static void main(String[] args) {
long t = System.currentTimeMillis(); // 현재 시간(밀리초)를 저장
Long sum = 0L; // 래퍼 객체로 오토 박싱으로 정수 값을 저장
// 백만번 도는 동안 더하기 연산
for (long i = 0; i < 1000000; i++) {
sum += i;
}
System.out.println("processing time: " + (System.currentTimeMillis() - t) + " ms") ;
}
processing time: 34 ms
primitive 타입간 연산
public static void main(String[] args) {
long t = System.currentTimeMillis();
long sum = 0L; // 기본형 정수 타입인 long 자료형에 정수 저장
for (long i = 0; i < 1000000; i++) {
sum += i;
}
System.out.println("processing time: " + (System.currentTimeMillis() - t) + " ms") ;
}
processing time: 5 ms
위와 같이 총 100만번의 sum 연산을 통해 두 코드의 결과를 비교해 보면, 거의 5배의 결과 차이를 보여준다.
만일 실제로 대용량 트래픽이 발생하는 서비스를 이용할 경우 100만건을 넘어 1000만건, 1억건의 처리가 필요할지도 모르고, 단지 똑같은 자료값을 저장하지만 어떤 타입 방식으로 선언했느냐에 따라 어플리케이션 성능 차이는 점점 벌어지게 될 것이다.
따라서 작성한 코드에 불필요한 auto casting이 반복적으로 이루어지고 있는지 확인하는 것은 대용량 서비스를 개발하는데 있어서 꼼꼼히 파악해야하는 요소이다.
# 참고자료
http://www.tcpschool.com/java/java_api_wrapper
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.