2 분 소요

서버와의 통신을 위해서 프로그램을 만들 일이 있어서 이것저것 기초작업을 시작해보다가 의문이 있어서 자료형에 대해 찾아보았다.

C++는 char, short, int, int64 등의 자료형과 여기에 대한 unsigned형들, C#에서는 byte, int16, int32, int64 등의 자료형들을 사용해왔다. 이클립스에서 코드를 작성하다 보니 unsigned형에 대한 코드 힌트가 나오지 않았다.

2026년 수정 안내 원래 이 글에는 “모든 자료형이 부호를 가지기 때문에 unsigned가 따로 없다”고 적었는데 틀렸다. char가 바로 부호 없는 타입이다. 바로 앞 줄에 “char는 2바이트”라고 써놓고 반례를 놓쳤다. 정확한 내용으로 정리한다.

Java의 기본 자료형

타입 크기 범위 부호
byte 1바이트 -128 ~ 127 있음
short 2바이트 -32,768 ~ 32,767 있음
int 4바이트 -2^31 ~ 2^31-1 있음
long 8바이트 -2^63 ~ 2^63-1 있음
char 2바이트 0 ~ 65,535 없음
float 4바이트 IEEE 754 -
double 8바이트 IEEE 754 -
boolean 명시 안 됨 true / false -

char가 유일한 예외다

char는 문자를 담기 위한 타입이라 음수가 필요 없다. 그래서 0부터 65535까지의 부호 없는 16비트 정수다. Java에서 유일한 unsigned 타입이다.

크기가 2바이트인 이유는 Java가 문자열을 UTF-16으로 다루기 때문이다. 정확히 말하면 char 하나는 문자 하나가 아니라 UTF-16 코드 유닛 하나다. 이모지나 일부 한자처럼 BMP를 벗어나는 문자는 char 두 개(서로게이트 페어)로 표현된다.

String emoji = "😀";
System.out.println(emoji.length());              // 2  ← char 개수
System.out.println(emoji.codePointCount(0, 2));  // 1  ← 실제 문자 개수

unsigned가 필요할 때

서버와 바이너리 프로토콜로 통신할 때 이게 문제가 된다. C++ 서버가 unsigned char로 0~255를 보내는데 Java의 byte는 -128~127이라, 128 이상의 값이 음수로 읽힌다.

해결은 더 큰 타입으로 받으면서 비트 마스크를 씌우는 것이다.

byte b = -1;              // C++에서 보낸 unsigned char 255
int  unsigned = b & 0xFF; // 255
short s = -1;                    // unsigned short 65535
int   unsigned = s & 0xFFFF;     // 65535

int i = -1;                      // unsigned int 4294967295
long unsigned = i & 0xFFFFFFFFL; // 4294967295

long을 unsigned로 다뤄야 한다면 담을 더 큰 기본 타입이 없으므로 BigInteger를 쓰거나, 아래 헬퍼 메서드를 쓴다.

Java 8부터 unsigned 헬퍼가 추가되었다

Java 8(2014)부터는 부호 없는 연산을 위한 정적 메서드들이 표준에 들어왔다. 값 자체는 여전히 부호 있는 타입에 담기지만, 연산과 출력을 부호 없는 것처럼 처리해준다.

int  x = -1;

Integer.toUnsignedString(x);   // "4294967295"
Integer.toUnsignedLong(x);     // 4294967295L
Integer.divideUnsigned(x, 2);  // 2147483647
Integer.compareUnsigned(x, 1); // 양수 (x가 더 큼)

Byte.toUnsignedInt((byte) -1);   // 255
Short.toUnsignedInt((short) -1); // 65535
Long.toUnsignedString(-1L);      // "18446744073709551615"

바이너리 프로토콜을 다룬다면 ByteBuffer도 함께 알아두면 좋다. 엔디안 지정이 가능하다.

ByteBuffer buf = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN);
int value = buf.getShort() & 0xFFFF;   // unsigned short로 읽기

참고자료

댓글 남기기