java 에서 타입은 크게 primitive type 과 reference type 이 있다.
primitive type 은 쉽게 말해 정수, 실수, 문자, 논리 리터럴 등의 실제 데이터 값을 저장하는 타입이고,
reference type 은 객체의 번지를 참조 (주소를 저장) 하는 타입으로 메모리 번지 값을 통해 객체를 참조하는 타입이다.
1. Primitive Type (원시 타입) - 변수에 값 자체를 저장
primitive type 은 반드시 사용하기 전에 선언되어야 한다.
비객체 타입이기 때문에 null 값을 가질 수 없다.
-> 기본형 타입에 null 을 넣고 싶다면 Wrapper 클래스를 활용해야 한다.
실제 값을 저장하는 공간으로, stack 메모리에 저장된다.
컴파일 시점에 데이터의 표현 범위를 벗어나면 컴파일 에러가 발생한다.
ex) boolean, byte, short, int, long, float, double, char
타입 | 할당되는 메모리 크기 |
기본값 | 데이터의 표현 범위 | |
논리형 | boolean | 1 byte | false | true, false ● java 가 데이터를 다루는 최소 범위가 1 byte 이기 때문에 낭비적이지만 1 byte 를 사용한다. |
정수형 | byte | 1 byte | 0 | -128 ~ 127 |
short | 2 byte | 0 | -32,768 ~ 32,767 | |
int (기본) |
4 byte | 0 | -2,147,483,648 ~ 2,147,483,647 | |
long | 8 byte | 0L | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 | |
실수형 | float | 4 byte | 0.0F | (3.4 X 10-38) ~ (3.4 X 1038) 의 근사값 |
double (기본) |
8 byte | 0.0 | (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값 | |
문자형 | char | 2 byte (유니코드) |
'\u0000' | 0 ~ 65,535 ● java 의 경우 Unicode 를 사용, 동양의 글자의 경우 2 byte 필요하기 때문에 2 byte 를 사용한다. |
1.1. char type 추가 설명
java 에서 유일하게 제공되는 unsigned 형태이다.
unsigned : 음수 없이 0 부터 시작하여 양수 값만 가지는 데이터 형태
● 통상의 경우
2 byte = 16진수 0x00 = 0000 0000 0000 0000
-> 맨 앞의 1 bit 로 음수, 양수를 나타낸다
● char 형의 경우
맨 앞의 비트를 음수, 양수 형식으로 사용하지 않는다
ex) char 형이 1111 1111 1111 1111 의 bit 를 가지고 있을 때 : 10진수의 값은 65525
ex) short 형이 1111 1111 1111 1111 의 bit 를 가지고 있을 때 : 10진수의 값은 -1
● char a = 'A'; char b = 'B' 일 때 if (a > b) 가 가능한 이유는 char 형은 Unicode 정수 형태로 저장되기 때문이다
ex) char a = 'A' 의 경우 a 변수에는 'A' 의 정수 값인 65 가 들어간다.
1.2. 정수형 데이터 타입 추가 설명
JVM 의 피연산자 스택이 피연산자를 4 byte 단위로 저장하기 때문에 int 보다 작은 자료형의 값을 계산시 int 형으로 형변환되어 연산이 수행된다.
정수형 데이터를 사용하게 되면 JVM 에서 기본적으로 int 형 데이터 타입의 데이터로 인식해주게 된다.
int 형 데이터 타입의 범위를 넘어서는 long 타입의 정수를 사용하고자 하는 경우에는 정수 데이터 맨 뒤쪽에 접미사 'l' or 'L' 을 붙여줘야 한다.
1.3. 실수형 데이터 타입 추가 설명
실수형 데이터 타입에서는 double 형 데이터 타입이 기본 데이터 타입니다.
double 형 데이터 타입의 범위를 넘어서는 float 형 데이터 타입의 실수형 데이터를 사용하고자 하는 경우에도 마찬가지로 실수 데이터 맨 뒤쪽에 접미사 'f' 나 'F' 를 붙여줘야 한다.
2. Reference Type (참조 타입) - 메모리상에 객체가 있는 위치를 저장
primitive type 을 제외한 타입들이 모두 reference type 이다.
-> 기본적으로 java.lang.Object 를 상속받으면 reference type 이 된다.
빈 객체를 의미하는 null 이 존재한다.
값이 저장되어 있는 곳의 주소값을 저장하는 공간으로 heap 메모리에 저장된다.
런타임 에러가 발생한다.
-> 배열을 null 로 받으면 Null Point Exception 발생
ex) array, enumeration, class, interface
타입 | 예시 | 기본값 | 할당되는 메모리 크기 |
array (배열) |
int[] arr = new int[5]; | null | 4 byte (객체의 주소값) |
enumeration (열거) |
null | ||
class (클래스) |
String str = "test"; Student dongha = new Student(); |
null | |
interface (인터페이스) |
null |
2.1. Class Type 추가 설명
Class type 은 기본형과 다르게 객체를 참조하는 형태이다.
2.2. Interface Type 추가 설명
Interface 를 만들게 되면 새로운 reference data type 을 만드는 것과 같다.
-> 참고로 기존에 있는 자료형의 이름도 만들 수 있다 ex) Long
Interface 도 자료형이기 때문에 자료형으로써 자신을 구현한 객체의 주소를 가질 수 있다.
-> 하지만 Interface 에 정의된 메소드만 사용할 수 있다.
2.3. Array Type 추가 설명
배열은 기본형, 참조형으로 만들 수 있다.
자료형에 대해 [ ] 를 선언함으로써 배열을 지정할 수 있다.
-> 참고로 배열형 변수 또한 배열의 주소를 가지고 있는 것이기 때문에, 클래스형의 특징과 일치한다.
같은 객체의 주소를 바라보게 만들면 동일한 배열을 가리키게 된다.
2.4. 참조변수 연산
java 에서는 primitive type 간 산술연산 및 논리연산이 가능하며, reference type 은 산술연산 및 논리연산을 할 수 없다.
예외적으로 == 연산자를 통해 가르키고 있는 해시코드의 값을 비교할 수 있다.
-> 이 때는 참조하고 있는 레퍼런스가 같다면 true 를 반환한다.
레퍼런스간 값 비교가 필요할 경우 Object.equals 메소드를 오버라이딩하여 원하는 값끼리 비교할 수 있다.
ex) String.equals(String str2)
-> 참조 해시코드가 다르더라도 저장된 문자열 값이 같아면 true 반환
대소 비교가 필요하다면 Comparable Interface 를 구현하여 compareTo(object O) 메소드를 재정의하여 레퍼런스간 비교가 가능하다.
3. primitive type 과 reference type 차이
3.1. null 포함 가능 여부
primitive type 은 null 을 담을 수 없지만, reference type 은 가능하다.
// 불가능
int i = null;
// 가능
Integer integer = null;
3.2. 제너릭 타입에서 사용 여부
primitive type 은 제너릭 타입에서 사용할 수 없지만, reference type 은 가능하다.
// 불가능
List<i> list;
// 가능
List<Integer> list;
4. primitive type 의 장점
4.1. 접근 속도
primitive type 은 Stack 메모리에 값이 존재한다
반면에 reference type 은 하나의 인스턴스이기 때문에 Stack 메모리에는 참조값만 있고, 실제값은 Heap 메모리에 존재한다.
그리고 값을 필요로 할 때마다 Unboxing 과정을 거쳐야 하니, primitive type 과 비교해서 접근 속도가 느려지게 된다.
예외적으로 엄청 큰 숫자를 복사해야 한다면, 참조값만 넘길 수 있는 reference type 이 좋을수도 있다.
4.2. 메모리 양
primitive type 이 사용하는 메모리 |
reference type 이 사용하는 메모리 |
boolean - 8 bits | Boolean - 128 bits |
byte - 8 bits | Byte - 128 bits |
short, char - 16 bits | Short, Character - 128 bits |
int, float - 32 bits | Integer, Float - 128 bits |
long, double - 64 bits | Long, Double - 128 bits |
primitive type 보다 reference type 이 사용하는 메모리 양이 압도적으로 높다.
따라서 메모리 사용적으로도 primitive type 이 reference type 보다 효율적으로 사용할 수 있다.
5. Stack, Heap
5.1. 정적 메모리 Stack 영역
Stack 영역에는 기본타입 변수가 할당되고, 변수의 실제 값들이 저장된다.
참조타입 변수들은 이 Stack 영역에서 Heap 영역에 생성된 객체의 주소 값을 저장한다.
객체 안의 메소드의 작업이 종료되면 할당되었던 메모리 공간은 반환되어 비워진다.
5.2. 동적 메모리 Heap 영역
Heap 영역에는 객체와 배열이 생성된다.
참조타입들이 이 객체들의 주소를 Stack 영역에 저장한다.
기본타입 변수들과는 다르게 크기가 정해져 있지 않다.
프로그램 실행시 메모리에 동적으로 할당된다.
참조하는 변수가 없으면 java 의 Garbage Collector 가 제거한다.
5.3. Garbage Collector
메모리의 Heap 영역에 할당된 더 이상 사용되지 않는 객체를 제거하는 역할.
이렇게 객체를 제거하며 메모리가 관리된다.
6. primitive type <-> reference type 변환
Boxing | primitive type -> Wrapper 클래스로 변환 |
Unboxing | Wrapper 클래스 -> primitive type 으로 변환 |
public static void main(String[] args) {
Integer a = new Integer(3);
int b = 3;
// 이것은 Boxing
Integer c = (Integer) b;
// 이것은 Unboxing
int d = (int) a;
// 자동으로 된다!!
int e = a;
Integer f = b;
}
7. valueOf() 와 parseInt() 의 차이
Integer.valueOf(String) | Integer 클래스를 리턴하기 때문에 산술 연산을 할 수 없다. |
Integer.parseInt(String) | int 형을 리턴하기 때문에 산술 연산을 할 수 있다. |
// to int i from Integer ii
int i = ii.intValue();
// to Integer ii from int i
Integer ii = new Integer(i);