Skip to content

Commit

Permalink
docs: 이펙티브 자바 item 61 정리본 (#116)
Browse files Browse the repository at this point in the history
  • Loading branch information
kim0527 authored Aug 23, 2024
1 parent e857137 commit 1b563b7
Showing 1 changed file with 195 additions and 0 deletions.
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. 리플렉션을 통해 메서드를 호출할 때


## 📚 정리

박싱 타입은 기본 타입보다 메모리를 많이 차지 하기 때문에

**만약 꼭 참조값이 필요한 경우가 아니라면, 기본 값 타입을 쓰자.**

오토박싱, 오토언박싱으로 편의성을 제공해주지만, 해당 기술에 의존시 심각한 성능 문제를 일으킬 수 있다.

**즉 꼭 써야 하지하는 경우를 제외한다면 기본타입을 사용하자.**


0 comments on commit 1b563b7

Please sign in to comment.