본문 바로가기

프로그래밍/Java

java.lang -1 WrapperClass

반응형

해당 포스트를 작성하는 목적

자바의신vol2 의 20장 공부와 더불어, 시스템클래스에 대해서 알아보기 위해 작성한다.

20장에서는 java.lang패키지와 함께 wrapper클래스, System클래스를 다루고있다.

이번 포스트에서는 java.lang패키지에 대한 개략적인 이해와 Wrapper클래스에 대해서 알아보고자 한다.

java.lang 패키지는 특.별.하.다

자바의 패키지 중에서

유일하게 import를 하지 않아도 사용할 수 있는 패키지

이다.

그만큼 자바동작에 있어 꼭 필요한 여러 기능을 제공한다.

몇가지 알고있어야할 가끔나타나는 에러들

OutOfMemoryError(OOME)

OOME의 경우 메모리가 부족하여 발생하는 에러이다. 가상머신에서 메모리를 관리하지만, Static변수가 너무 많다던지 등의 잘못된 프로그램 작성이나, 설정등에 의해서 발생할 수 있다.

StackOverflowError

호출된 메소드의 깊이가 너무 깊을 때 발생한다. Stack이라는 영역에서 어떤 메소드가 어떤 메소드를 호출했는지 관리하는데. 재귀함수등을 잘못작성한다면 발생할 수 있는 오류이다.

숫자를 처리하는 클래스들 -Wrapper클래스

자바에서는 간단한 계산을 할때에는 대부분 기본자료형을 사용한다. 이 기본자료형은 힙 영역에 저장되지 않고, 스탭영역에 저장되어 관리되며 주소값이 아닌 '값'자체가 저장된다. 그러나, 이런

기본 자료형도 객체로 처리해야할 때

가 있다. 이에 따라 각 기본 자료형들을 참조자료형으로 만든 Wrapper클래스가 존재한다. Character, Boolean을 제외한 나머지 클래스들을 Wrapper클래스 라고 부르며, 모두 Number라는 abstract클래스를 extends한다. 또 String처럼 기본 자료형처럼 리터럴 생성이 가능하다. 왜냐하면

자바컴파일러에서 자동으로 형변환

을 해주기 때문이다.

Wrapper클래스를 굳이?.. 왜 써야할까?

  • 매개 변수를 참조 자료형으로만 받는 메소드를 처리하기 위해서
  • 제네릭과 같이 기본 자료형을 사용하지 않는 기능을 사용하기 위해서
  • MIN_VALUE,MAX_VALUE와 같이 클래스에 선언된 상수 값을 사용하기 위해서
  • 문자열을 숫자로, 숫자를 문자열로 쉽게 변환하고, 2,8,10,16진수 변환을 쉽게 처리하기 위해서

로 볼수있다. (String과는 다르게 Immutable한 장점을 위해서보단, 각종 연산시의 편의성을 위해서인것 같다.)

parse()와 valueOf()

Character 클래스를 제외한 나머지 Wrapper클래스 에서는 parse타입명(),valueOf()라는 메소드를 공통으로 제공한다. 코드를 통해 해당 메소드들의 역할을 이해해보자

package etc;
public class JavaLangNumber {
    public static void main(String[] args){
    	JavaLangNumber j = new JavaLangNumber();
    	j.numberTypeCheck();
    }
    public void numberTypeCheck(){
        String strValue1 = "3";
        String strValue2 = "5";
				// parse()메소드는 기본형으로 리턴해준다.
        byte byte1 = Byte.parseByte(strValue1);
        byte byte2 = Byte.parseByte(strValue2);
        System.out.println(byte1 + byte2);
				//valueOf()는 참조형 객체값으로 반환한다.
        Integer intVal = Integer.valueOf(strValue1);
        Integer intVal2 = Integer.valueOf(strValue2);
        System.out.println(intVal+intVal2+"7");
    }
}
/*
8
87
*/

첫번째 결과가 8로 나온것은 이해가 될것이다. byte끼리 더한값을 출력한 것이니...

그러나 두번째 결과에서 87이 나온 이유는 무엇일까? 분명+연산이 가능한 참조자료형은 String뿐이었다. 그 이유는 Wrapper클래스들 역시 필요시 기본 자료형처럼 사용할 수있기 때문이다.

즉 Integer가 아니라 int형으로 형변환이되어 String과 연산이 됬던것이다.

이렇게 Wrapper클래스와 기본 자료형의 형변환을 하는것을 박싱/언박싱 이라 한다. JDK1.5이상 부터는 컴파일러가 자동으로 형변환을 해주는 Auto Boxing/Unboxing을 지원한다.

int i = 10; Integer it = i;//오토 언박싱 
int i2 = it; //오토 박싱

이런 오토박싱/언박싱의 등장으로 Wrapper클래스의 불변성 원칙이 깨져 final키워드가 Wrapper클래스가 추가 되기도 하였다.

Wrapper클래스의 비교연산

Wrapper클래스의 비교연산은 어떻게 수행할까?? 아래의 코드 결과를 비교해 보자.

Integer a = 127;
Integer b = 127;
System.out.println(a==b);

Integer c = 128;
Integer d = 128;
System.out.println(c==d);
  • 실행결과
    true false

Integer a =127; 코드는 컴파일러에 의해 오토박싱 된다. 컴파일러가

Integer a = Integer.valueOf(127); 로 수정해주는 것이다.

아래의 코드도 마찬가지이다. Integer c =128; 코드는 컴파일러에 의해 자동 형변환 되면서

Integer c = Integer.valueOf(128); 로 바뀐다.

그런데 무엇이 다른걸까? 다시 다음의 코드를 보자.

Integer e = new Integer(127);
Integer f = new Integer(127);
System.out.println(e==d);

/*
false
*/

리터럴 생성과 new 연산자 생성이 다르다. 리터럴 생성시에는 컴파일러가 자동으로 오토박싱을 해주어 Integer.valueOf()메소드를 호출해 주었다. valueOf()메소드를 확인해 보자.

/**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

매개변수로 넘긴 int i의 값이 IntegerCache.low 와 IntegerCache.high 값에 있으면 새로운 Integer객체를 생성하지 않고, IntegerCache.cache의 배열값중 하나를 반환해준다.

IntegerCache의 소스코드를 확인해 보자.

/**
     * Cache to support the object identity semantics of autoboxing for values between
     * -128 and 127 (inclusive) as required by JLS.
     *
     * The cache is initialized on first usage.  The size of the cache
     * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
     * During VM initialization, java.lang.Integer.IntegerCache.high property
     * may be set and saved in the private system properties in the
     * sun.misc.VM class.
     */

    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

코드를 확인해보면, -128~127에 해당하는 Integer객체를 생성, static final 키워드로, 상수화 해두는것을 알수 있다. 즉 메소드영역에 존재하며, 만일 Integer.valueOf()의 매개변수가 -128~127사이의 값이 온다면 새로운 객체 생성이 아닌, 미리 상수화 된 Integer객체를 반환하는 것이다. 즉 String의 StringPool과 비슷하게, 캐시된 영역의 Integer 객체를 반환하므로, 같은 객체를 가리키게 되어 전자의 경우 true가, 이 범위를 벗어난 c,d의 경우에는 새로운 Integer객체가 생성되어 false를 가르킨 것이다.

💡
이런 cache는 왜 하는걸까?? 정해진 범위 -128~127은 굉장히 많이 사용되는 숫자이다. 그러므로 미리 캐시를 해두어 메모리의 효율성을 늘리는 것이다.

이는 Integer뿐 아니라, Byte,Short,Long등에도 모두 있다. 단, 캐시의 범위를 조절할 수있는것은 Integer가 유일하다.

포스트 작성을 통해 알게된점

Wrapper클래스의 parse() 메소드와 valueOf() 메소드에 대해서 좀더 자세히 알게되었다. 실제로 업무에서 쓸때는 그저 컴파일 오류가 나니까 parse로 바꾸거나 valueOf로 썼던것 같은데.... 뿐만아니라 굳이 기본자료형들을 Wrapper클래스로 만들어 참조 자료형으로 쓰게되는 이유도 알게 되었다.

메소드의 매개 변수를 아예 Object등으로 참조자료형을 받는경우가 있을 수 있고, 또 클래스에 선언해둔 상수값들과, 메소드를 이용해 보다 편한 연산을 위해서 쓰게 되는것을 알게 되었다.

그리고 Wrapper클래스의 비교연산중 알게된것은 캐시의 존재이다. String을 공부할때 StringPool처럼 다른 Wrapper클래스도 비슷한 무언가 있지않을까? 생각했었는데, valueOf()메소드를 통해 알수있게 되었다.

Reference

자바의신2

[Java] Integer.valueOf(127) == Integer.valueOf(127) 는 참일까요?

 

반응형

'프로그래밍 > Java' 카테고리의 다른 글

java.lang -2 System클래스와 로깅  (0) 2021.05.20
상속과 컴포지션  (0) 2021.05.20
JAVA의 역사와 JVM-2  (0) 2021.05.18
GC  (0) 2021.05.18
JAVA의 역사와 JVM-1  (0) 2021.05.18