Java/클린 코딩 (with OOP)

5. JAVA 기본 타입 vs 참조 타입 (with 래퍼클래스를 사용해야할 때) ➡️ Integer(Wrapper Class) 보다 int(기본 타입)

민돌v 2022. 6. 14. 22:51

 

 

저는 래퍼클래스가, Null을 처리할 수 있기 때문에, 더 범용성이 좋고, 유용하다고 생각했습니다..

하지만, 온보딩 과제를 진행할때, 매개변수를 사용할 때 래퍼클래스보단, 기본타입을 이용하는게 좋다는 피드백을 받았고,

이번 포스트이에는, 그 이유에대해서 생각해본 것을 정리해볼까 합니다..!

 

 

 

[목차]

  1.  JAVA 자료형 종류와 차이점 (기본타입 자료형 vs 참조타입 자료형)
  2.  Wrapper Class란
  3.  Wrapper Class 특징
  4.  String의 생성과 비교 방식
  5.  Wrapper Class를 사용해야할 때

 


 

 

 

📌 Java의 자료형 2가지

 

1. 기본 타입 자료형 (Primitive Type)

  • int, boolean, double, float 등등

2. 참조 타입 자료형 (Refernce Type)

  • Integer, Boolean, String, Double 등등

 

자바에서 자료형의 타입은, 위와 같이 2가지가 존재합니다.

두 자료형의 가장 큰 차이점은, 아래와 같다고 생각합니다.

  1. 값을 저장하는가(기본타입), 주소를 참조하는가(참조타입)
  2. jvm내부에서 메모리가 어디 영역에 저장되는가 (stack - 기본형이 저장됨, heap - 객체들이 저장됨)

 

JVM 메모리 구조

 

자바에서 객체들이 생성될 때는, 그 객체가 힙영역에 생성되어, 생성된 주소를 참조하게됩니다.

//생성되는 순간, heap 영역에 Car객체가 생성되고
// myCar 는 그 주소값을 참조합니다.

Car myCar = new Car()

 


 

📌  Wrapper Class

래퍼클래스도 마찬가지 입니다. 생성할 때 기본타입을 객체로 감싸서 생성주기 때문에 힙영역에 생성되고 그 주소값을 참조하는, 참조타입 자료형 입니다.

//래퍼클래스 또한 객체의 형식으로 기본타입을 감싸서 생성 -> 참조타입

Integer a = new Integer(1);

 

한마디로 정리하자면,

래퍼클래스란(Wrapper Class), 기본타입(primitive Type) 데이터를 객체로 다뤄야할 때, 참조 타입으로 다루기위해 객체로 감싸 다루는 것을, 래퍼클래스라고 합니다.

 

Object 객체 아래에 존재

 

 

📌  Wrapper Class 특징

✨1. "==" 비교 연산 불가

  • == 비교 연산자는 비교하는 2 값들이 같은지를 비교합니다.
  • 기본타입에서는 값을 비교하고
  • 참조타입에서는 참조된 주소를 비교해, 같은 객체인지를 확인합니다.
  • 따라서, 두 객체의 값은 같더라도, 참조되는 주소가 다를 수 있기 때문에 == 연산이 의도와 다를 수 있습니다.

 

Integer a = new Integer(10);
Integer b = new Integer(10);

 

 

참조타입의 값을 비교하기 위해서는, equals 함수를 이용해야하며, 혹은 hascode() 함수를 사용해서 값을 비교할 수 있기도 합니다.

Integer 내부 구현체

 

2. 래퍼클래스는 불변 객체 (Immutable)

래퍼클래스는, 불변 객체여서 한번 할당된 객체는 값이 변하지 않습니다.

우리가 수정이나 값을 변경할 때는, 객체의 값을 변경하는게 아닌, 새로운 값의 객체를 new 생성해서 반환받는 것 입니다.

 


 

📌  String 생성 과 비교

String 또한, 래퍼클래스입니다. 즉 참조형 데이터 타입인데

String은 특이하게 "==" 연산으로 비교를 하곤 합니다. 왜 String만 다를까요?

String은 참조 타입인데 == 연산이 가능

 

그 이유는 스트링의 생성과 연관이 있습니다.

String intern() 메소드

 

 

📌 String 생성 방식

String의 생성 방식은 리터럴 방식과, 객체 생성 방식이 있습니다.

 

1. 리터럴 방식

고정 데이터 값을 생성하는 방식으로 위처럼 String 변수에 값을 대입해주는 것을 말합니다.

String은 참조 데이터 타입이지만, 그 객체의 주소가  jvm heap 메모리 영역 내부에, 별도의  String Context Pool 영역을 가집니다.

String이 리터럴 방식으로 생성될 때, String 내부에서 Intern() 메소드가 호출됩니다.

Intern() 메소드는 그 값이 String pool 에 존재하면, 새로운 객체를 생성하지 않고, String pool에 미리 존재하는,

문자열 객체의 주소값을 할당해 줍니다.

 

따라서, String이 리터럴 방식으로 생성되면, 래퍼 클래스임에도 불구하고, 같은 주소값을 할당받게 되는 것입니다.

 

 


 

 

2. 객체 생성 방식 (new 연산자)

하지만 리터럴 방식이 아닌 new 연산자로 새로운 객체를 할당한다면, 

참조 타입 객체 생성과 똑같습니다.

값이 같더라도 다른 객체를 생성해주기 때문에, 주소값이 달라 "=="비교를 할 수 없습니다.

 

 


 

📌  Wrapper Class 를 사용해야할 때

본론입니다.

 

그렇다면, 언제 Wrapper Class를 사용해야 할까요

물론 정답은 없지만, 저는 "Null 값을 다뤄주어야할 때" 라고 생각합니다.

Wrapper Class로 자료형을 다루면 기본 타입보다 2가지 정도를 더 할 수 있습니다

 

 

1) Null 값을 받을 수 있다.

2) toString() 메소드로, 문자열로 보다 쉽게 변환할 수 있다.

 


 

✨ Wrapper Class 의 Null 다루기

래퍼 클래스를 사용하면, 데이터 타입을 객체로 감싸주어(Boxing) 안에서 데이터를 관리할 수 있고, 그렇기 때문에 null을 데이터 값으로 받을 수 있습니다.


✨ Wrapper Class 의 Null 은 불친절하다.

null 을 데이터 값으로 받을 수 있다는 것, 사용자로부터 들어온 값이 Null인지, 제대로 들어왔는지 바로바로 파악할 수 없다는 것입니다.

즉, Null을 한번 더 체크해주어야하는, 불편함이 있습니다.


✨ Wrapper Class가 사용될 때

따라서, 래퍼클래스는, 어쩔 수 없이 Null을 다루어야할 때만 사용하는 게 좋습니다.

 

1) DB에 값이 저장될 때

  • DB에는 값이 null로 들어갈 수 있기 때문에, 유연하게 값을 받을 필요가 있습니다. 
  • 디비 테이블에 Null이 들어갔는지 아닌지, 판단할 수 없을 때, Wrapper Class를 사용하면 유용합니다.

 ➡️ 하지만, null을 허용하지 않는 컬럼일 경우는, 똑같이 기본 데이터타입으로 지정

 

(예외)

2) request로도 null이 들어올 때

  • 이때 또한 Requeset 들어오는 값이 Null일 수 있기에 WrapperClass로 받아 줄 수있긴 합니다.
  • 하지만, request같은 경우는 -> @validate, @NotNull같은 거로, 사전에 차단하는게 좋을지도...?

 

 

 

 

따라서, Null을 바로바로 파악할 수 있는 (NullPointException을 떨궈버리는) 기본 타입을 잘 사용하자!!