기본형의 한계와 래퍼 클래스
기본형의 한계
자바는 객체 지향 언어이지만 int, double 같은 기본형(Primitive Type) 은 객체가 아니라 객체 지향의 장점을 온전히 사용할 수 없다.
기본형의 한계를 이해하기 위해, 두 값을 비교해서 다음과 같은 결과를 출력하는 간단한 코드를 작성해보자
public static void main(String[] args) {
int value = 10;
int i1 = compareTo(value, 5);
int i2 = compareTo(value, 10);
int i3 = compareTo(value, 20);
System.out.println("i1 = " + i1);
System.out.println("i2 = " + i2);
System.out.println("i3 = " + i3);
}
public static int compareTo(int value, int target) {
if (value < target) {
return -1;
} else if (value > target) {
return 1;
} else {
return 0;
}
}
//실행결과
i1 = 1
i2 = 0
i3 = -1
주요 한계점
- 메서드 없음: 기본형은 메서드를 가질 수 없음 (객체와 달리
value.compareTo()같은 메서드 호출 불가) - 컬렉션 제약: 기본형은 제네릭, List 등에서 직접 사용할 수 없음
- null 사용 불가: 기본형은 항상 값을 가져야 하므로 “값 없음” 상태 표현 불가
직접 만든 래퍼 클래스
기본형 int를 감싸는 MyInteger 클래스 정의
public class MyInteger {
private final int value;
public MyInteger(int value) { this.value = value; }
public int getValue() { return value; }
public int compareTo(int target) {
if (value < target) return -1;
else if (value > target) return 1;
else return 0;
}
@Override
public String toString() {
return String.valueOf(value);
}
}
- 메서드를 객체에 캡슐화하여 직접 호출 가능
- 비교, 출력 등 기능을 메서드로 제공
- 불변 객체 설계 가능
MyInteger myInteger = new MyInteger(10);
System.out.println(myInteger.compareTo(5)); // 1
기본형은 null을 사용 불가
// 기본형의 문제점: -1은 실제 값인지, 없는 값인지 모호함
private static int findValue(int[] intArr, int target) {
for (int value : intArr) {
if (value == target) return value;
}
return -1;
}
→ -1은 값이기도 하고 “없음”이기도 함 → 명확하지 않음
객체는 null을 사용 가능!
// 래퍼 클래스를 이용해 '값 없음'을 null로 명확히 구분 가능
private static MyInteger findValue(MyInteger[] intArr, int target) {
for (MyInteger myInteger : intArr) {
if (myInteger.getValue() == target) return myInteger;
}
return null;
}
//실행결과
-1
0
1
null
-
기본형은 객체가 아니기 때문에 다음과 같은 제약이 있음
→ 메서드 사용 불가, null 표현 불가, 컬렉션/제네릭 사용 불가
-
직접 만든 래퍼 클래스를 사용하면 객체처럼 다룰 수 있음
-
자바는
Integer,Double같은 표준 래퍼 클래스를 제공함
자바 래퍼 클래스
래퍼 클래스란?
- 기본형을 객체로 감싸서 더 풍부한 기능을 제공하는 클래스
- 자바는 기본형에 대응하는 표준 래퍼 클래스를 기본 제공함
| 기본형 | 래퍼 클래스 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
Integer newInteger = new Integer(10); // 미래에 삭제 예정, 대신에 valueOf() 사
Integer integerObj = Integer.valueOf(10); // 추천 방식
Long longObj = Long.valueOf(100);
Double doubleObj = Double.valueOf(10.5);
System.out.println("newInteger = " + newInteger);
System.out.println("integerObj = " + integerObj);
System.out.println("longObj = " + longObj);
System.out.println("doubleObj = " + doubleObj);
System.out.println("내부 값 읽기");
int intValue = integerObj.intValue();
System.out.println("intValue = " + intValue);
long longValue = longObj.longValue();
System.out.println("longObj = " + longValue);
System.out.println("비교");
System.out.println("==: " + (newInteger == integerObj));
System.out.println("equals: " + newInteger.equals(integerObj));
//실행 결과
newInteger = 10
integerObj = 10
longObj = 100
doubleObj = 10.5
내부 값 읽기
intValue = 10
longObj = 100
비교
==: false
equals: true
==는 주소 비교 → 항상 equals()로 비교toString()은 내부 값을 문자열로 출력하도록 이미 재정의되어 있음
박싱(Boxing) & 언박싱(Unboxing)
-
박싱: 기본형 → 래퍼 클래스
예)
Integer.valueOf(10) -
언박싱: 래퍼 클래스 → 기본형
예)
integerObj.intValue()
오토 박싱 / 오토 언박싱
int value = 7;
Integer boxed = value; // 오토 박싱
int unboxed = boxed; // 오토 언박싱
→ 컴파일러가 Integer.valueOf(), intValue() 호출 코드를 자동으로 추가함
Integer i1 = Integer.valueOf(10);//숫자, 래퍼 객체 반환
Integer i2 = Integer.valueOf("10");//문자열, 래퍼 객체 반환
int intValue = Integer.parseInt("10");//문자열 전용, 기본형 반환
//비교
int compareResult = i1.compareTo(20);
System.out.println("compareResult = " + compareResult);
//산술 연산
System.out.println("sum: " + Integer.sum(10, 20)); //30
System.out.println("min: " + Integer.min(10, 20)); //10
System.out.println("max: " + Integer.max(10, 20)); //20
//실행결과
compareResult = -1
sum: 30
min: 10
max: 20
valueOf()→ 래퍼 타입 반환 (숫자/문자열 모두)parseInt()→ 문자열 → 기본형compareTo()→ 비교 결과: -1 / 0 / 1
성능 비교
// 기본형 long
long sumPrimitive = 0;
for (int i = 0; i < 1_000_000_000; i++) {
sumPrimitive += i;
}
// 래퍼 클래스 Long (오토 박싱 발생)
Long sumWrapper = 0L;
for (int i = 0; i < 1_000_000_000; i++) {
sumWrapper += i;
}
실행 시간 예시 (M2 맥 기준)
long: 318msLong: 1454ms → 약 5배 차이
왜 차이가 날까?
- 기본형: 4~8byte만 사용
- 래퍼 클래스: 메타데이터 등 포함 → 추가 메모리 소모 (8~16byte 이상)
- 오토 박싱 반복 → 객체 생성 비용 증가
언제 어떤 걸 써야 하나?
| 상황 | 추천 방식 |
|---|---|
| 성능 민감한 반복 연산 | 기본형 (int, long 등) |
| 유지보수가 중요한 일반 로직 | 래퍼 클래스 |
| 컬렉션, 제네릭 사용 필요 시 | 래퍼 클래스 |
| 값이 “없음(null)“을 표현 | 래퍼 클래스 |
최근에는 컴퓨터 성능이 워낙 좋아졌기 때문에, 성능보다는 유지보수성과 가독성 을 우선으로 코드를 작성하는 경우가 많다.
최적화는 실제로 성능 문제가 발생했을 때, 그 핵심 병목 구간만 타겟팅해서 수행하는 것이 더 효과적이다.
자바 유틸리티 클래스 정리
Class 클래스
클래스의 정보(메타데이터)를 다루는데 사용한다. Class 클래스를 통해 개발자는 실행중인 자바 어플리케이션 내부의 필요한 클래스의 속성과 메서드에 대한 정보를 조회하고 조작할 수 있다.
주요 기능
- 클래스 이름, 필드, 메서드, 부모 클래스, 인터페이스 조회
- 런타임에 객체 생성, 메서드 호출 가능 (리플렉션)
- 애노테이션 조회 및 처리
Class 객체 얻는 방법 3가지
Class clazz = String.class; //클래스에서 조회
Class clazz = new String().getClass(); //인스턴스에서 조회
Class clazz = Class.forName("java.lang.String"); //문자열로 조회
주요 메서드
Field[] fields = clazz.getDeclaredFields(); //클래스의 모든 필드 조회
Method[] methods = clazz.getDeclaredMethods(); //클래스의 모든 메서드 조회
Class<?> superclass = clazz.getSuperclass(); //클래스의 부모 클래스 조회
Class<?>[] interfaces = clazz.getInterfaces(); //클래스의 인터페이스 조회
//Class 조회
Class clazz = String.class; // 1.클래스에서 조회
//Class clazz = new String().getClass();// 2.인스턴스에서 조회
//Class clazz = Class.forName("java.lang.String"); // 3.문자열로 조회
// 모든 필드 출력
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("Field: " + field.getType() + " " +
field.getName());
}
// 모든 메서드 출력
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Method: " + method);
}
// 상위 클래스 정보 출력
System.out.println("Superclass: " + clazz.getSuperclass().getName());
// 인터페이스 정보 출력
Class[] interfaces = clazz.getInterfaces();
for (Class i : interfaces) {
System.out.println("Interface: " + i.getName());
}
-
출력 결과
Field: class [C value Field: int hash Field: long serialVersionUID Field: class [Ljava.io.ObjectStreamField; serialPersistentFields Field: interface java.util.Comparator CASE_INSENSITIVE_ORDER Method: public boolean java.lang.String.equals(java.lang.Object) Method: public java.lang.String java.lang.String.toString() Method: public int java.lang.String.hashCode() Method: public int java.lang.String.compareTo(java.lang.Object) Method: public int java.lang.String.compareTo(java.lang.String) Method: public int java.lang.String.indexOf(java.lang.String,int) Method: static int java.lang.String.indexOf(char[],int,int,java.lang.String,int) Method: static int java.lang.String.indexOf(char[],int,int,char[],int,int,int) Method: public int java.lang.String.indexOf(int) Method: public int java.lang.String.indexOf(java.lang.String) Method: public int java.lang.String.indexOf(int,int) Method: public static java.lang.String java.lang.String.valueOf(char) Method: public static java.lang.String java.lang.String.valueOf(java.lang.Object) Method: public static java.lang.String java.lang.String.valueOf(boolean) Method: public static java.lang.String java.lang.String.valueOf(char[],int,int) Method: public static java.lang.String java.lang.String.valueOf(char[]) Method: public static java.lang.String java.lang.String.valueOf(double) Method: public static java.lang.String java.lang.String.valueOf(float) Method: public static java.lang.String java.lang.String.valueOf(long) Method: public static java.lang.String java.lang.String.valueOf(int) Method: private static void java.lang.String.checkBounds(byte[],int,int) Method: public int java.lang.String.length() Method: public boolean java.lang.String.isEmpty() Method: public char java.lang.String.charAt(int) Method: public int java.lang.String.codePointAt(int) Method: public int java.lang.String.codePointBefore(int) Method: public int java.lang.String.codePointCount(int,int) Method: public int java.lang.String.offsetByCodePoints(int,int) Method: public void java.lang.String.getChars(int,int,char[],int) Method: void java.lang.String.getChars(char[],int) Method: public byte[] java.lang.String.getBytes() Method: public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException Method: public void java.lang.String.getBytes(int,int,byte[],int) Method: public byte[] java.lang.String.getBytes(java.nio.charset.Charset) Method: public boolean java.lang.String.contentEquals(java.lang.StringBuffer) Method: public boolean java.lang.String.contentEquals(java.lang.CharSequence) Method: private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder) Method: public boolean java.lang.String.equalsIgnoreCase(java.lang.String) Method: public int java.lang.String.compareToIgnoreCase(java.lang.String) Method: public boolean java.lang.String.regionMatches(int,java.lang.String,int,int) Method: public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int) Method: public boolean java.lang.String.startsWith(java.lang.String) Method: public boolean java.lang.String.startsWith(java.lang.String,int) Method: public boolean java.lang.String.endsWith(java.lang.String) Method: private int java.lang.String.indexOfSupplementary(int,int) Method: public int java.lang.String.lastIndexOf(int,int) Method: static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int) Method: static int java.lang.String.lastIndexOf(char[],int,int,java.lang.String,int) Method: public int java.lang.String.lastIndexOf(java.lang.String,int) Method: public int java.lang.String.lastIndexOf(int) Method: public int java.lang.String.lastIndexOf(java.lang.String) Method: private int java.lang.String.lastIndexOfSupplementary(int,int) Method: public java.lang.String java.lang.String.substring(int) Method: public java.lang.String java.lang.String.substring(int,int) Method: public java.lang.CharSequence java.lang.String.subSequence(int,int) Method: public java.lang.String java.lang.String.concat(java.lang.String) Method: public java.lang.String java.lang.String.replace(char,char) Method: public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence) Method: public boolean java.lang.String.matches(java.lang.String) Method: public boolean java.lang.String.contains(java.lang.CharSequence) Method: public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String) Method: public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String) Method: public java.lang.String[] java.lang.String.split(java.lang.String,int) Method: public java.lang.String[] java.lang.String.split(java.lang.String) Method: public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[]) Method: public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable) Method: public java.lang.String java.lang.String.toLowerCase(java.util.Locale) Method: public java.lang.String java.lang.String.toLowerCase() Method: public java.lang.String java.lang.String.toUpperCase() Method: public java.lang.String java.lang.String.toUpperCase(java.util.Locale) Method: public java.lang.String java.lang.String.trim() Method: public char[] java.lang.String.toCharArray() Method: public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]) Method: public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]) Method: public static java.lang.String java.lang.String.copyValueOf(char[],int,int) Method: public static java.lang.String java.lang.String.copyValueOf(char[]) Method: public native java.lang.String java.lang.String.intern() Superclass: java.lang.Object Interface: java.io.Serializable Interface: java.lang.Comparable Interface: java.lang.CharSequence
System 클래스 - 시스템 제어 및 정보 조회
주요 기능
- 시간 조회:
System.currentTimeMillis(),System.nanoTime() - 환경 변수:
System.getenv() - 시스템 속성:
System.getProperties(),System.getProperty(key) - 배열 고속 복사:
System.arraycopy(src, srcPos, dest, destPos, length) - 프로그램 종료:
System.exit(status)
System.out.println(System.currentTimeMillis());
System.arraycopy(original, 0, copied, 0, original.length);
Math 클래스
자주 쓰는 메서드
Math.max(a, b),Math.min(a, b),Math.abs(x)Math.ceil(x),Math.floor(x),Math.round(x)Math.sqrt(x),Math.pow(a, b),Math.random()
System.out.println(Math.round(2.5)); // 3
System.out.println(Math.random()); // 0.0 ~ 1.0
Random 클래스
Random random = new Random(); // 매번 다른 결과
Random fixed = new Random(1); // 항상 같은 결과 (seed 고정)
int i = random.nextInt(); // 전체 범위
int i2 = random.nextInt(10); // 0 ~ 9
int i3 = random.nextInt(10) + 1; // 1 ~ 10
double d = random.nextDouble(); // 0.0 ~ 1.0
boolean b = random.nextBoolean(); // true/false
- Seed 고정 시 항상 같은 결과 → 테스트에 유용
- Math.random()도 내부적으로 Random 클래스 사용
자바의 유틸리티 클래스들은 복잡한 시스템 작업을 간단한 코드로 가능하게 해준다. 필요한 기능이 있다면, 먼저 JDK의 유틸 클래스부터 찾아보는 습관을 들이자.
출처: [김영한의 실전 자바 - 중급 1편](https://www.inflearn.com/course/김영한의-실전-자바-중급-1)