-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
195 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
## 기본 타입 🆚 박싱 타입 | ||
|
||
기본 타입과 박싱 타입의 차이점에 대해 알아보자. | ||
|
||
### 1. 박싱 타입은 식별성이란 속성을 갖는다. | ||
|
||
```java | ||
public static void main(String[] args) { | ||
Integer a = new Integer(99); | ||
Integer b = new Integer(99); | ||
|
||
System.out.println(a.equals(b)); // true | ||
System.out.println(a == b); // false | ||
} | ||
``` | ||
|
||
- 기본 타입은 값만 가지는 반면, 박싱 타입은 값과 식별성이라는 속성을 더 가진다. | ||
|
||
- 즉, 객체, 인터페이스 와 같이 **메모리 주소를 할당받는다.** | ||
|
||
- 그래서 위 코드를 보면, `a.equals(b)` 의 경우 true가 나오지만, `a==b`의 경우 false가 나온다. | ||
|
||
- 다른 주소 값을 할당받았고, 산술연산에서는 주소값을 기준으로 동등비교했기 때문이다. | ||
|
||
> 💡 추가 정보 | ||
> | ||
> 위 코드에서 처럼 `new 박싱 타입(숫자)`로 정의하는 것은 Deprecated 되었고, 위와 같이 정의하는 경우도 거의 없다. | ||
### 2. 박싱 타입은 항상 유효하지 않다. | ||
|
||
- 1번에서의 연장선 얘기이다. | ||
|
||
- 박싱 타입은 참조 타입이기 때문에 주소값을 할당받고, 이 말은 즉 **null을 가질 수 있다는 것을 의미한다.** | ||
|
||
- 반면 기본 타입의 경우 값만 가지기 때문에 항상 유효한 값을 가진다. ( 초기화 하지 않아도 0으로 저장되기 때문 ) | ||
|
||
### 3. 박싱 타입은 더 많은 메모리를 차지한다. | ||
|
||
- 메모리 주소를 할당되고, 이 값은 스택 혹은 힙 메모리에 저장된다. | ||
|
||
- 즉 박싱 타입을 사용하는 경우, 메모리를 값 타입에 비해 더 사용하게 되며 시간 또한 더 오래 걸린다. | ||
|
||
## 박싱 타입 사용시 발생할 수 있는 문제 예시 | ||
|
||
위와 같은 차이점으로 인해 **박싱 타입을 사용했을때 발생하는 문제 예시를** 살펴보자. | ||
|
||
### 예시 1 : 비교 연산자 | ||
|
||
```java | ||
public static void main(String[] args) { | ||
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1); | ||
System.out.println(naturalOrder.compare(new Integer(99), new Integer(99))); | ||
} | ||
``` | ||
|
||
> 출력 결과 : 1 | ||
> | ||
> 같다라는 의미로 0이 출력되어야 하는데, 첫번째 것이 크다는 1이 출력되었다. | ||
```java | ||
public static void main(String[] args) { | ||
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1); | ||
System.out.println(naturalOrder.compare(new Integer(98), new Integer(99))); // 98을 넣으면 출력 결과는 -1이 나온다. | ||
} | ||
``` | ||
|
||
> 출력 결과 : -1 | ||
- 원인을 분석해보면, 첫번째 비교 (`i<j`)에서 오토박싱된 값들은 기본 타입으로 변환된 후 비교된다. | ||
|
||
- 위 예시를 보면 98과 99를 비교했을때는 올바른 값인 -1이 출력된다. 즉 `i<j`는 올바르게 동작한다. | ||
|
||
- 결국 문제점은 바로 `i==j`에서 발생한다. | ||
|
||
- 해당 부분에서는 값 비교가 아닌 식별성으로 비교하기 때문에 다른 참조값으로 인해 false가 나와 1이 출력된 것이다. | ||
|
||
> 해결 방법 | ||
```java | ||
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> { | ||
int i = iBoxed, j = jBoxed; // 오토 박싱 | ||
return i < j ? -1 : (i==j ? 0 : 1); | ||
}; | ||
``` | ||
|
||
- 기본 타입 정수로 저장을 한 후 , 모든 비교를 해당 정수로만 비교하도록 하면 문제를 해결할 수 있다. | ||
|
||
### 예시 2 : NullPointerException | ||
|
||
```java | ||
public class Unbelievable { | ||
static Integer i; | ||
|
||
public static void main(String[] args){ | ||
if (i == 42) | ||
System.out.println("믿을 수 없군!"); | ||
} | ||
} | ||
``` | ||
|
||
> 실행 결과 : NullPointerException | ||
- 실행 결과, 아무것도 출력되지 않는 것이 아닌 `NullPointerException`이 발생했다. | ||
|
||
- 기본적으로 기본 타입과 박싱 타입의 혼합연산에서는 박싱 타입이 기본타입으로 변환된다. | ||
|
||
- 위에서 `Integer i`에 초기화 한 값이 없이 실행했기 때문에 Null이 들어가 있고, 이를 박싱하는 과정에서 `NullPointerException`이 발생한 것이다. | ||
|
||
> 해결 방법 | ||
```java | ||
public class Unbelievable { | ||
static int i; // int로 변경하면 NullPointerException이 발생하지 않는다. | ||
|
||
public static void main(String[] args){ | ||
if (i == 42) | ||
System.out.println("믿을 수 없군!"); | ||
} | ||
} | ||
``` | ||
|
||
### 예시 3 : 속도 | ||
|
||
```java | ||
public static void main(String[] args) { | ||
Long sum = 0L; | ||
long i = 0; | ||
long beforeTime = System.currentTimeMillis(); | ||
while(i <= Integer.MAX_VALUE) { | ||
i++; | ||
sum += i; | ||
} | ||
long afterTime = System.currentTimeMillis(); | ||
System.out.println(sum); | ||
System.out.println("실행 시간(ms): " + (afterTime - beforeTime)); | ||
} | ||
``` | ||
|
||
> 출력 결과 : | ||
> | ||
> 2305843010287435776 | ||
> | ||
> 실행 시간(ms): 4582 | ||
- 코드를 살펴보면 sum은 박싱 타입인 `Long`으로, i는 기본 타입인 `long`으로 정의되어있다. | ||
|
||
- 그래서 while문에서 sum에 i가 더해질 떄마다 박싱과 언박싱이 계속해서 일어나게 된다. | ||
|
||
```java | ||
public static void main(String[] args) { | ||
long sum = 0; // Long -> long 변경 | ||
long i = 0; | ||
long beforeTime = System.currentTimeMillis(); | ||
while(i <= Integer.MAX_VALUE) { | ||
i++; | ||
sum += i; | ||
} | ||
long afterTime = System.currentTimeMillis(); | ||
System.out.println(sum); | ||
System.out.println("실행 시간(ms): " + (afterTime - beforeTime)); | ||
} | ||
``` | ||
|
||
> 출력 결과 : | ||
> | ||
> 2305843010287435776 | ||
> | ||
> 실행 시간(ms): 1859 | ||
- `Long sum`을 `long sum`으로 변경하여 실행해본 결과, 실행 시간의 큰 차이를 보이고 있다. | ||
|
||
- 즉 박싱타입을 잘못 사용하면, 위와 같이 심각한 성능 문제를 일으킬 수 있다. | ||
|
||
## 🙋♂️ 그렇다면 우리는 박싱 타입은 도대체 언제 사용하는 것인가? | ||
|
||
물론 박싱타입을 사용해야 하는 경우 또한 존재한다. | ||
|
||
1. 컬렉션의 원소, 키, 값을 정의할 때 | ||
|
||
2. 매개변수화 타입을 정의할 때 | ||
|
||
3. 리플렉션을 통해 메서드를 호출할 때 | ||
|
||
|
||
## 📚 정리 | ||
|
||
박싱 타입은 기본 타입보다 메모리를 많이 차지 하기 때문에 | ||
|
||
**만약 꼭 참조값이 필요한 경우가 아니라면, 기본 값 타입을 쓰자.** | ||
|
||
오토박싱, 오토언박싱으로 편의성을 제공해주지만, 해당 기술에 의존시 심각한 성능 문제를 일으킬 수 있다. | ||
|
||
**즉 꼭 써야 하지하는 경우를 제외한다면 기본타입을 사용하자.** | ||
|
||
|