래퍼클래스 - 기본형의 한계
자바는 객체 지향 언어이다. 그런데 자바에서 객체가 아닌 것이 있는데 바로 int, double 같은 기본형 타입이다.
기본형은 객체가 아니기 때문에 객체 지향 프로그래밍의 장점을 살릴 수 없다.
객체는 유용한 메서드를 제공할 수 있는데, 기본형은 객체가 아니므로 메서드를 제공할 수 없다.
추가로 객체 참조가 필요한 컬렉션, 제네릭을 사용할 수 없다.
그리고 null값을 가질 수 없다. 때로는 데이터가 없음이라는 상태를 나타내야 할 필요가 있는데, 기본형은
항상 값을 가진 상태이기 때문에 null을 표현할 수 없다.
package wrapper;
public class MyIntegerMethodMain0 {
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;
}
}
}
위 코드에선 value와 대상 값을 비교하는 compareTo() 메서드를 사용한다. 자기 자신인 value와 다른값을 비교하기 때문에 항상
자기 자신의 값인 value가 사용된다. 이런 경우 value가 만약 객체라면, 메서드를 만들어서 사용하는게 더 유용할 것이다.
기본형인 int를 객체로 만들 수 있는데 이게 래퍼 클래스이다.
package wrapper;
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는 int value라는 단순한 기본형 변수 하나를 가지고 있다. 그리고 이 변수를 사용하도록 다양한 메서드를 제공할 수 있게 됐다.
이 클래스는 불변 클래스이고 compareTo()메서드를 클래스 내부로 캡슐화 했다. 이 방법으로 int를 객체로 다룰 수 있게 된 것이다.
package wrapper;
public class MyIntegerMethodMain1 {
public static void main(String[] args) {
MyInteger myInteger = new MyInteger(10);
int i1 = myInteger.compareTo(5);
int i2 = myInteger.compareTo(10);
int i3 = myInteger.compareTo(20);
System.out.println("i1 = " + i1);
System.out.println("i2 = " + i2);
System.out.println("i3 = " + i3);
}
}
메서드를 편리하게 호출할 수 있도록 메인메서드가 변경되었다.
기본형은 항상 값을 가져야 한다. 하지만 때로는 null값을 줘야할 때가 있다.
package wrapper;
public class MyIntegerNullMain0 {
public static void main(String[] args) {
int[] intArr = {-1, 0, 1, 2, 3};
System.out.println(findValue(intArr, -1));
System.out.println(findValue(intArr, 0));
System.out.println(findValue(intArr, 1));
System.out.println(findValue(intArr, 100));
}
private static int findValue(int[] intArr, int target) {
for (int value : intArr) {
if (value == target) {
return value;
}
}
return -1;
}
}
findValue() 는 배열에 찾는 값이 있다면 해당 값을 반환하고, 찾는 값이 없으면 -1 를 리턴한다. findValue()는 결과로 int를 반환한다.
항상 기본형은 값이 있어야 한다. 어쩔 수 없이 리턴을 해야하기 때문에 -1를 반환하도록 작성 되었다.
100을 찾았을 때 -1가 리턴되는데, 이게 -1의 값을 찾아서 리턴을 한 건지, 해당하는 값이 없어서 -1 를 리턴을 한건지 확인하기 어렵다.
이럴 경우 null값을 리턴하면 되는데, 기본형이라 null을 리턴할 수 없는 것 이다.
package wrapper;
public class MyIntegerNullMain1 {
public static void main(String[] args) {
MyInteger[] intArr = {new MyInteger(-1), new MyInteger(0), new MyInteger(1),
new MyInteger(2), new MyInteger(3)};
System.out.println(findValue(intArr, -1));
System.out.println(findValue(intArr, 0));
System.out.println(findValue(intArr, 1));
System.out.println(findValue(intArr, 100));
}
private static MyInteger findValue(MyInteger[] intArr, int target) {
for (MyInteger myInteger : intArr) {
if (myInteger.getValue() == target) {
return myInteger;
}
}
return null;
}
}
래퍼 클래스를 사용해 null을 리턴할 수 있도록 하게 변경했다. 이렇게 해서 명확하게 값을 찾았는지, 못 찾았는지 리턴값을 보고
확인할 수 있게 되었다. (null을 다룰땐 NPE가 발생할 수 있으니 조심해야함)
자바 래퍼 래퍼클래스
자바는 기본형에 대응하는 래퍼 클래스를 기본으로 제공한다.
(Byte, Short, Integer, Long, Float, Double, Character, Boolean 등)
자바가 제공하는 기본 래퍼클래스는 불변 객체이고, equals로 비교해야한다.
package wrapper;
public class WrapperClassMain {
public static void main(String[] args) {
Integer newInteger = new Integer(10); // 미래에 삭제 예정, valueOf를 사용하는것을 권장
Integer integerObject = Integer.valueOf(10); // -128 ~ 127 자주 사용하는 숫자 값 재사용, 불변
Long longObj = Long.valueOf(100);
Double doubleObj = Double.valueOf(10.5);
System.out.println(newInteger);
System.out.println(integerObject);
System.out.println("내부 값 읽기");
int intValue = integerObject.intValue();
System.out.println(intValue);
long longValue = longObj.longValue();
System.out.println(longValue);
System.out.println("비교");
System.out.println("==: " + (newInteger == integerObject));
System.out.println("==: " + (newInteger.equals(integerObject)));
}
}
박싱 (Boxing)
기본형을 래퍼클래스로 변경하는 것을 Boxing이라 한다. new Integer(10)처럼 사용하면 안된다. 작동은 하지만 자바에서 제거될 수 있다. 대신에 Integer.valueOf()를 사용하면 된다. Integer.valueOf()는 내부에서 최적화 기능이 있다. 개발자들이 주로 사용하는 -128~127값을 미리 Integer 클래스에 미리 생성해두고 반환한다. (문자열 풀과 같은 느낌) 해당 값이 없다면 new Integer()를 호출한다.
언박싱(Unboxing)
래퍼 클래스에 들어있는 기본형 값을 다시 꺼내는 메서드이다.
비교는 equals() 사용
래퍼 클래스는 객체이기 때문에 == 비교를 하면 인스턴스의 참조값을 비교하게 된다. 래퍼 클래스는 내부의 값을 비교하도록 equals()를 재정의 해두었다. 따라서 값을 비교할 땐 equals()를 사용할 것
참고로 래퍼 클래스는 객체를 그대로 출력해도 내부에 있는 값을 문자로 출력하도록 toString() 메서드를 오버라이딩 했다.
오토 박싱 (Auto Boxing)
자바에서 int를 Integer로 변환하거나, Integer를 int로 변환하는 부분을 정리해보면, valueOf(), intValue()를 사용하면 된다.
package wrapper;
public class AutoBoxingMain1 {
public static void main(String[] args) {
int value = 8;
// Integer boxedValue = Integer.valueOf(value); // Integer.valueOf() 생략 가능
Integer boxedValue = value;
// Wrapper -> Primitive
// int unBoxedValue = boxedValue.intValue(); // intValue() 생략 가능
int unBoxedValue = boxedValue;
System.out.println(boxedValue);
System.out.println(unBoxedValue);
}
}
기본형을 래퍼 클래스로 변환하거나, 래퍼 클래스를 기본형으로 변환하는 일이 잦다보니, 불편함을 호소했다.
자바는 1.5 부터 오토 박싱, 오토 언박싱을 지원한다.
래퍼 클래스의 주요 메서드
valueOf(): 래퍼 타입을 반환. 숫자, 문자열 모두 지원
parseInt(): 문자열을 기본형으로 변환
compareTo(): 내 값과 인수로 넘어온 값을 비교. 내 값이 크면 1, 같으면 0, 작으면 -1을 반환
Integer.sum(), min(), max(): static 메서드이다. 간단한 덧셈, 작은 값, 큰 값 연산 수행
parseInt() vs valueOf()
원하는 타입에 맞는 메서드를 사용하면 된다.
valueOf("10")는 래퍼 타입을 반환
parseInt("10")은 기본형을 반환
pasgLong() 처럼 parse XXX()가 존재한다.
래퍼 클래스의 성능
래퍼 클래스는 객체이기 때문에 기본형과 다양한 기능을 제공한다. 다양한 기능을 제공하면 더 좋은건데, 기본형을
제공하는 이유는 무엇일까?
한가지 이유로는 성능의 차이가 있는데, 기본형 연산이 객체를 다루는 래퍼 클래스 보다 당연히 성능이 우위이다.
예를 들어 int 기본형은 4byte의 메모리를 사용하지만, 래퍼 클래스는 내부 필드의 기본형 값 뿐만 아니라 객체를
다루기 때문에 더 많은 메모리를 사용한다. (대략적으로 8에서 16byte정도)
그렇다면 기본형, 래퍼 클래스 어떤것을 사용하는것이 옳은가?
10억회를 반복하는 단순한 코드를 예로 들었을 때 성능의 차이는 약 5배의 차이가 났다.
래퍼 클래스를 사용한 10억 반복 연산속도는 1.5초, 기본형을 사용했을 땐 0.3초가 소요되었다.
일반적인 애플리케이션 관점에서 보면 래퍼 클래스를 사용한 코드를 기본형으로 최적화를 했다고 하더라도
느껴지는 체감은 거의 없을 것이다. 1.5초 나누기 10억을 어떻게 체감 할 것인가....
따라서 CPU 연산을 아주 많이 해야되는 특수한 경우엔 기본형을 사용해 최적화를 고려하는 것도 좋다.
일반적으로 코드를 유지보수 하기 쉬운 방향으로 코드를 작성하는 것이 낫다.
최신 컴퓨터의 사양이 발달함에 따라 이런 미미한 최적화로는 실질적으로 도움되지 않는 경우가 많기 때문이다.
성능 최적화는 단순함보다 복잡함을 요구하는 경우가 대부분이고, 백엔드 관점에서 본다면 네트워크 호출을
하나라도 더 줄이는게 성능 최적화에 도움이 되기 때문에 자바 메모리 내부에서 발생하는 연산을 줄이는 것 보다
다른 쪽에 시간을 투자하는게 옳은 방법이다.
권장되는 방법은 개발 이후에 성능 테스트를 하고, 문제가 되는 부분을 최적화 하는 것이다.
Class 클래스
자바에서 Class 클래스는 클래스의 정보 (메타데이터)를 다루는데 사용된다. 이를 통해 개발자는 실행중인
자바 애플리케이션 내에서 필요한 클래스와 속성과 메서드에 대한 정보를 조회하고 조작할 수 있다.
주요 기능
타입 정보 얻기: 클래스의 이름, 슈퍼클래스, 인터페이스, 접근 제한자 등 정보 조회 가능
리플렉션 : 클래스에 정의된 메서드, 필드, 생성자를 조회하고 이를 통해 객체 인스턴스를 생성 또는 메서드 호출가능
동적 로딩과 생성: class.forName() 메서드를 사용해 클래스를 동적으로 로드하고, newInstance() 메서드를 통해
새로운 인스턴스 생성 가능
어노테이션 처리: 클래스에 적용된 어노테이션을 조회하고 처리하는 기능을 제공한다.
(배우긴 배웠으나 현재는 위의 주요 기능을 알고, 이런 기능이 있다는 것만 인지하고 넘어가는게 좋다. 상위 개념인듯 함)
System 클래스
시스템과 관련된 기본 기능을 제공하는 클래스이다.
package lang.system;
import java.util.Arrays;
import java.util.Map;
public class SystemMain {
public static void main(String[] args) {
long currentTimeMillis = System.currentTimeMillis();
System.out.println("currentTimesMillis = " + currentTimeMillis);
// 현재 시간 (나노초)
long currentTimeNano = System.nanoTime();
System.out.println("currentTimeNano = " + currentTimeNano);
// 환경변수
System.out.println("getEnv = " + System.getenv());
// 시스템 속성
System.out.println("properties = " + System.getProperties());
System.out.println("Java version = " + System.clearProperty("java.version"));
// 배열 복사
char[] originalArray = {'h', 'e', 'l', 'l', 'o'};
char[] copiedArray = new char[5];
System.arraycopy(originalArray, 0, copiedArray, 0, originalArray.length);
System.out.println("copiedArray = " + copiedArray);
System.out.println("copiedArray = " + Arrays.toString(copiedArray));
// 프로그램 종료
System.exit(0);
System.out.println("hello");
}
}
여기서 그나마 유용한 기능은 배열 복사 기능인 듯 하다.
루프를 돌리지 않고 시스템에서 배열 복사를 하기 때문에 속도가 굉장히 빠르다는 장점이 있음
Math, Random 클래스
'Java' 카테고리의 다른 글
날짜와 시간 라이브러리(1) (0) | 2024.11.12 |
---|---|
열거형 - ENUM (2) | 2024.11.05 |
String 클래스 (1) | 2024.10.15 |
불변 객체 (0) | 2024.10.10 |
Object (1) | 2024.10.09 |