Java 변수와 타입 | 기본 타입, 참조 타입, 형변환
이 글의 핵심
Java 기본·참조 타입과 형변환, String·배열 사용 시 흔한 함정을 짚습니다. 리터럴 접미사와 동등 비교(== vs equals)까지 실무 관점으로 정리했습니다.
들어가며
Java는 정적 타입 언어(변수마다 타입을 미리 적어 두고, 소스를 바이트코드로 바꿀 때 검사하는 방식)이므로, 변수에는 컴파일러가 검사할 수 있는 타입(명찰)을 붙입니다. 기본형과 참조형을 구분해 두면 이후 컬렉션·객체와 연결하기 쉽습니다.
1. 기본 타입 (Primitive Types)
정수 타입
// byte: -128 ~ 127
byte b = 127;
// short: -32,768 ~ 32,767
short s = 32767;
// int: -2,147,483,648 ~ 2,147,483,647 (기본)
int i = 2147483647;
// long: -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
long l = 9223372036854775807L; // L 접미사 필수
// 언더스코어 사용 가능 (가독성)
int million = 1_000_000;
long billion = 1_000_000_000L;
정수 타입 선택 가이드: 일반 비즈니스 로직에서는 int가 기본입니다. 파일 크기·타임스탬프처럼 범위가 큰 값은 long, 메모리가 극도로 제한된 바이너리 포맷이나 대량 배열에서는 byte/short를 고려합니다. L 접미사를 빼먹으면 리터럴이 int로 처리되어 범위를 넘을 때 컴파일 에러가 납니다.
실수 타입
// float: 32비트 부동소수점
float f = 3.14f; // f 접미사 필수
// double: 64비트 부동소수점 (기본)
double d = 3.14159;
double scientific = 1.23e-4; // 0.000123
부동소수점 주의: float와 double은 이진 부동소수점이라 0.1 + 0.2 같은 식이 기대와 다르게 보일 수 있습니다. 그래도 지리·그래픽·통계 전처리 등에서는 여전히 널리 쓰이며, 정확한 십진 연산이 필요하면 BigDecimal로 전환하는 것이 안전합니다.
문자 타입
char c = 'A';
char korean = '가';
char unicode = '\u0041'; // 'A'
// char는 16비트 유니코드
불리언 타입
boolean flag = true;
boolean isActive = false;
// 조건식 결과
boolean isAdult = age >= 18;
boolean과 널: 기본 타입 boolean은 null을 가질 수 없지만, 래퍼 Boolean은 null이 가능해 NPE(NullPointerException) 위험이 생깁니다. API 설계 시 “세 가지 상태(참/거짓/미정)”가 필요하면 Optional<Boolean>·별도 enum을 검토하세요.
2. 참조 타입 (Reference Types)
String
// 리터럴 (String Pool)
String name1 = "홍길동";
String name2 = "홍길동";
System.out.println(name1 == name2); // true (같은 객체)
// new 키워드
String name3 = new String("홍길동");
System.out.println(name1 == name3); // false (다른 객체)
System.out.println(name1.equals(name3)); // true (값 비교)
== 와 equals: 리터럴로 만든 동일 문자열은 풀에서 재사용될 수 있어 ==가 true로 나올 수 있지만, **일반적인 비교는 항상 equals**가 안전합니다. 특히 사용자 입력·DB에서 읽은 문자열은 ==에 의존하면 버그로 이어지기 쉽습니다.
배열
// 선언 및 초기화
int[] numbers = {1, 2, 3, 4, 5};
int[] arr = new int[5];
// 접근
System.out.println(numbers[0]); // 1
numbers[0] = 10;
// 길이
System.out.println(numbers.length); // 5
// 다차원 배열
int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
System.out.println(matrix[0][1]); // 2
배열 실무 노트: Java 배열은 크기가 고정이라, 동적 증가가 잦은 경우 ArrayList 등 컬렉션이 낫습니다. 다차원 배열은 “배열의 배열”이라 행마다 길이가 달라도 되지만, 직사각형이 아닌 구조는 루프 작성 시 주의가 필요합니다.
3. 형변환 (Type Casting)
자동 형변환 (Widening)
// 작은 타입 → 큰 타입 (자동)
byte b = 10;
int i = b; // OK
long l = i; // OK
float f = l; // OK
double d = f; // OK
// 순서: byte → short → int → long → float → double
명시적 형변환 (Narrowing)
// 큰 타입 → 작은 타입 (명시적)
double d = 3.14;
int i = (int) d; // 3 (소수점 버림)
long l = 1000L;
int i2 = (int) l;
// 주의: 데이터 손실 가능
int big = 130;
byte small = (byte) big; // -126 (오버플로우)
4. 래퍼 클래스 (Wrapper Classes)
기본 타입 vs 래퍼 클래스
| 기본 타입 | 래퍼 클래스 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
오토박싱/언박싱
Java 5부터 기본 타입과 래퍼 클래스 간 자동 변환이 지원됩니다:
// 오토박싱 (Auto-boxing): 기본 타입 → 래퍼 클래스
Integer obj = 10;
// 내부 동작: Integer obj = Integer.valueOf(10);
// 기본 타입 int를 자동으로 Integer 객체로 변환
// 언박싱 (Unboxing): 래퍼 클래스 → 기본 타입
int primitive = obj;
// 내부 동작: int primitive = obj.intValue();
// Integer 객체를 자동으로 int로 변환
// 자동 변환 예시
Integer a = 10; // 오토박싱
Integer b = 20; // 오토박싱
Integer sum = a + b; // 언박싱 → 연산 → 오토박싱
// 내부 동작:
// 1. a.intValue() + b.intValue() (언박싱)
// 2. 10 + 20 = 30 (int 연산)
// 3. Integer.valueOf(30) (오토박싱)
// 컬렉션에서 자동 변환
List<Integer> numbers = new ArrayList<>();
numbers.add(10); // 오토박싱: int → Integer
int first = numbers.get(0); // 언박싱: Integer → int
// 주의: null 언박싱 시 NullPointerException
Integer nullValue = null;
// int x = nullValue; // NullPointerException!
// null을 기본 타입으로 변환할 수 없음
// 안전한 처리
Integer value = getValue(); // null 가능성
int result = (value != null) ? value : 0; // null 체크
오토박싱 성능 주의:
// ❌ 나쁜 예: 반복문에서 오토박싱
Integer sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i; // 매 반복마다 언박싱 + 박싱 발생
}
// 1000번의 불필요한 객체 생성
// ✅ 좋은 예: 기본 타입 사용
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i; // 기본 타입 연산 (빠름)
}
Integer result = sum; // 마지막에 한 번만 박싱
래퍼 클래스 메서드
// 문자열 → 숫자
int num = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
// 숫자 → 문자열
String str = Integer.toString(123);
String str2 = String.valueOf(123);
// 비교
Integer a = 100;
Integer b = 100;
System.out.println(a.equals(b)); // true
// 최대/최소값
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
5. var (Java 10+)
// 타입 추론
var name = "홍길동"; // String
var age = 25; // int
var price = 19.99; // double
var list = new ArrayList<String>();
// 주의: 초기화 필수
// var x; // 에러!
6. 실전 예제
예제 1: 타입 변환
public class TypeConversion {
public static void main(String[] args) {
// 문자열 → 숫자
String input = "123";
int num = Integer.parseInt(input);
// 안전한 변환
try {
int result = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("변환 실패");
}
// 숫자 → 문자열
int value = 123;
String str = String.valueOf(value);
String str2 = Integer.toString(value);
}
}
예제 2: 배열 처리
public class ArrayExample {
public static void main(String[] args) {
int[] scores = {85, 92, 78, 95, 88};
// 합계
int sum = 0;
for (int score : scores) {
sum += score;
}
// 평균
double average = (double) sum / scores.length;
System.out.println("평균: " + average);
// 최대값
int max = scores[0];
for (int score : scores) {
if (score > max) {
max = score;
}
}
System.out.println("최대값: " + max);
}
}
정리
핵심 요약
- 기본 타입: byte, short, int, long, float, double, char, boolean
- 참조 타입: String, 배열, 객체
- 형변환: 자동 (Widening), 명시적 (Narrowing)
- 래퍼 클래스: Integer, Double 등
- 오토박싱: 자동 변환
다음 단계
- Java 클래스와 객체
- Java 컬렉션
- Java Stream API
관련 글
- C++ 변수와 자료형 | int, double, string 완벽 정리 [초보자용]
- Kotlin 변수와 타입 | val, var, 기본 타입 완벽 정리
- Swift 변수와 타입 | var, let, 옵셔널
- C++ numeric_limits |
- C++ 템플릿 |