자바와 기본형
자바는 객체 지향 언어로 객체가 자바의 대부분을 차지한다. 일부 객체가 아닌 것들이 존재하는데 바로 int, long, char와 같은 기본형이다. 기본형은 객체가 아니라 다음과 같은 한계점이 있다.
- 메서드를 제공할 수 없음
- null 값을 제공할 수 없음. 프로그래밍을 하다보면 필연적으로 값이 없다는 상태를 나타내는 null 값을 사용해야 할 때가 있는데 기본형에서는 이를 표현할 수가 없다
기본형의 한계를 이해하기
public class PrimitiveLimit {
public static void main(String[] args) {
int value = 10;
int i1 = compareTo(value, 5);
int i2 = compareTo(value, 10);
int i3 = compareTo(value, 20);
System.out.println("i1 = " + i1);
System.out.println("i2 = " + i2);
System.out.println("i3 = " + i3);
}
public static int compareTo(int value, int target) {
if (value < target) {
return -1;
} else if (value > target) {
return 1;
} else{
return 0;
}
}
}
만약 기본형 타입을 비교해서 값을 반환하고 싶다면, 다음과 같은 메서드를 매번 만들어야 한다
기본형 자체로는 메서드를 제공할 수 없기 때문이다.
public class PrimitiveNullMain {
public static void main(String[] args) {
int[] intArr = {-1, 0, 1, 2, 3};
System.out.println(findValue(intArr, -1));
System.out.println(findValue(intArr, 0));
System.out.println(findValue(intArr, 4));
}
private static int findValue(int[] intArr, int target) {
for (int value : intArr) {
if (value == target) {
return value;
}
}
return -1;
}
}
만약 int 배열에서 target 값을 찾아서 반환하는 메서드를 정의한다고 할 때, 배열에 target 값이 없을 때 반환값을 어떤 값으로 지정해야하는지가 매우 민감한 사항이 될 수 있다
이는 기본형에서는 null 값을 표현할 수 없기 때문이다
그렇다면 객체를 활용하면 이 두 문제를 모두 해결할 수 있다
래퍼 클래스 만들기
기본형 int를 담고 있는 클래스를 선언해보자
public class MyInteger {
private final int value;
public MyInteger(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public int compareTo(int target) {
if (value < target) {
return -1;
} else if (value > target) {
return 1;
} else {
return 0;
}
}
@Override
public String toString() {
return String.valueOf(value);
}
}
MyInteger를 활용하게 되면 메서드를 제공해주지 않아서 매번 새로운 메서드를 정의해서 사용해야한다는 불편함과 null 값을 표현하지 못해서 생기는 불편함을 모두 해결할 수 있다.
이렇게 기본형을 클래스로 감싼 것을 래퍼 클래스라고 부른다
자바 래퍼 클래스
지금까지 래퍼 클래스가 생겨난 이유와 간단한 설명에 대해서 알아보았다. 쉽게 말해서 래퍼 클래스란 기본형의 객체 버전이라고 볼 수 있다.
자바는 기본형에 대응하는 래퍼 클래스를 기본으로 제공해준다
- int -> Integer
- char -> Character
- long -> Long
- double -> Double
- 등 모든 기본형에 대한 래퍼 클래스 제공
래퍼 클래스의 특징
- 불변 객체이다
- 객체이므로 equals로 비교해야 한다
불변 객체인 이유?
- Integer의 경우 문자열과 유사하게 Integer 풀을 활용한다. 자주 사용하는 범위의 숫자 ( -128 ~ 127 )에 대한 Integer 객체를 미리 생성해서 풀에 넣어두고, 이 값을 갖는 Integer가 생성되면 해당 인스턴스의 참조값을 반환해서 성능 최적화를 진행한다
- 따라서 Integer 객체의 경우 공유 참조가 일어나게 되므로 한 변수에서 값을 바꾸게 되면 모든 공유 참조중인 변수에 영향을 미치게 되므로 불변 클래스로 선언해서 이 같은 사이드 이펙트를 막은 것이다
- 이는 모든 래퍼 클래스에 동일하게 적용된다
- 따라서 MyInteger의 value 처럼 래퍼 클래스이 기본형 값은 모두 private final로 지정되고, 그 후 수정할 수 없다
래퍼 클래스의 사용법
// 래퍼 클래스에 값 저장
Integer integer1 = new Integer(10); // deprecated : 삭제 예정
Integer integer2 = Integer.valueOf(10); // 대신 사용
Long longObj = Long.valueOf(10);
Double doubleObj = Double.valueOf(10.5);
// 래퍼 클래스에 저장된 값 꺼내기
int num1 = integer1.intValue();
int num2 = integer2.intValue();
long num3 = longObj.longValue();
double num4 = doubleObj.doubleValue();
- 위와 같이 래퍼 클래스를 통해서 객체를 생성하는 것을 박싱이라고 한다
- 아래와 같이 래퍼 클래스에 저장된 값을 꺼내는 것을 언박싱이라고 한다
- 래퍼 클래스는 객체이기 때문에 동등성을 비교하기 위해서는 equals()를 사용해야 한다
오토 박싱
방금 위에서 박싱, 언박싱하는 과정을 래퍼 클래스의 valueOf() 메서드와 xxxValue() 메서드를 통해서 할 수 있었다.
그런데 개발자들이 래퍼 클래스를 너무 많이 사용하기 시작하면서 위 메서드를 통한 변환이 엄청나게 일어나게 되자 자바에서 자동 형변환을 지원해주게 되었다.
// 래퍼 클래스에 값 저장
Integer integer1 = 10;
Long longObj = 10L;
Double doubleObj = 10.9;
// 래퍼 클래스에 저장된 값 꺼내기
int num1 = integer1;
long num1 = longObj;
double num2 = doubleObj;
다음과 같이 valueOf() 메서드와, xxxValue() 메서드를 사용하지 않아도 그대로 기본형 -> 래퍼 클래스, 래퍼 클래스 -> 기본형의 변환이 자동으로 이루어진다.
래퍼 클래스 메서드
- valueOf () : 래퍼 타입을 반환한다.
- parseXxx() : 제공된 것을 Xxx의 기본형으로 변환한다. ( parseInt () 에 문자열을 집어넣으면 기본형 int로 반환 )
- compareTo() : 현재 내 값과 인수로 넘어온 값을 비교한다
성능
그렇다면 래퍼 클래스는 기본형 값도 저장할 수 있고, 메서드도 제공해주면서 null 값도 표현할 수 있는 무적인데 그럼 기본형을 사용할 필요가 없는거 아닌가?
-> 가장 먼저 성능적으로 차이가 난다. 기본형은 데이터 그 자체만 갖고 있지만 래퍼 클래스의 경우 메서드부터 필드까지 다양한 정보를 갖고 있기 때문에 연산 과정에서 성능 차이가 나게 된다
물론 기본형의 연산과 래퍼 클래스의 성능 연산이 정말 드라마틱하게 많이 차이나는 것은 아니므로 현재 시점에서 유지보수하기 좋은 타입을 활용하는 것이 좋다
'TIL > Java' 카테고리의 다른 글
[Java] 날짜와 시간 데이터 다루기 (0) | 2025.02.11 |
---|---|
[Java] 열거형 - enum (0) | 2025.02.09 |
[Java] String 클래스 (0) | 2025.02.07 |
[Java] 불변 객체 (0) | 2025.02.06 |
[Java] equals() (0) | 2025.02.05 |