래퍼, Class 클래스

2025. 3. 27. 15:39Java

◎ 기본형의 한계

-> 자바의 int, double과 같은 기본형에는 몇가지 한계가 있다.

- 객체가 아님 : 기본형 데이터는 객체가 아니기 떄문에 객체와 관련된 메서드 등을 이용할 수 없다. 

- null 값을 가질 수 없음 : 기본형은 null 값을 가질 수 없다. 데이터가 없는 상태를 나타내야 할 때는 기본형 데이터로는 나타낼 수 없다.

-> 다음은 기본형 데이터를 이용한 두 정수의 대소 관계를 비교하는 코드이다.

package lang.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);
    }

    private static int compareTo(int value, int target) {
        if (value < target) {
            return -1;
        } else if (value > target) {
            return 1;
        } else {
            return 0;
        }
    }
}

-> 결과

 

-> value라는 int형 값을 compareTo()라는 외부 메서드를 이용해 두 정수를 비교한다. 이 경우 자기 자신인 value와 다른 값을 연산하는 것이기 때문에 항상 자기 자신의 값인 value가 사용된다. value가 객체였다면 value객체 스스로 자기 자신의 값과 다른 값을 비교한는 메서드를 만드는 것이 더 효율적이다.

 

-> 다음은 int값을 가지고 클래스를 만들어 클래스에서 제공하는 메서드를 이용해 두 정수의 대소관계를 비교하는 코드이다.

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를 MyInteger 클래스를 통해 객체로 다룰 수 있다.

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);
    }
}

-> 결과

-> myInteger.compareTo()는 자기 자신의 값을 외부의 값과 비교한다. 

-> MyInteger는 객체이므로 해당 객체가 가진 메서드를 편리하게 호출할 수 있다. 단순히 int형만 사용했을 경우 int가 기본형이기 때문에 메서드를 스스로 갖지 못했던 문제를 해결할 수 있다.

 

-> 기본형은 항상 값을 가져야 한다. 따라서 null(데이터 없음)인 상태가 될 수 없다. 다음은 기본형만을 사용하여 int 배열에 있는 값을 찾는 코드이다.

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, 17));

    }

    private static int findValue(int[] intArr, int target) {
        for (int value : intArr) {
            if (value == target) {
                return value;
            }
        }
        return -1;
    }
}

-> 특정 값이 intArr 이라는 int형 배열에 존재하는지 검사하여 있으면 해당 값을 리턴하고, 아니면 -1을 리턴하도록 했다.

-> 결과

-> intArr에 -1이 있는지 검증하였고, 해당 배열에는 -1이 있기 때문에 찾고자 하는 값 -1을 리턴하여 출력했다. 또한 17이라는 값은 intArr에 없기 때문에 -1을 리턴하여 출력했다. 이렇게 되면 출력 결과만 봤을 때 해당 값이 배열에 존재하지 않아 -1이 출력된건지 찾고자 하는 값이 -1이라 -1을 출력한 것인지 알 수 없다. 이렇듯 기본형은 반드시 어떠한 값이 들어가 있어야 하기 때문에 이런 문제가 발생한다.

-> 이번에는 이전에 만들었던 MyInteger 객체를 사용해 해당 객체의 배열을 만들어 배열 내 값을 찾는 코드를 만들었다.

public class MyIntegerNullMain1 {
    public static void main(String[] args) {
        MyInteger[] intArr = {new MyInteger(-1), new MyInteger(0), new MyInteger(1)};

        System.out.println(findValue(intArr, -1));
        System.out.println(findValue(intArr, 0));
        System.out.println(findValue(intArr, 1));
        System.out.println(findValue(intArr, 17));

    }

    private static MyInteger findValue(MyInteger[] intArr, int target) {
        for (MyInteger myInteger : intArr) {
            if (myInteger.getValue() == target) {
                return myInteger;
            }
        }
        return null;
    }
}

-> MyInteger를 생성하여 MyInteger의 int형 필드 value에 값을 넣고, 특정 값을 찾을 때 해당 객체의 getValue()메서드를 이용해 value 필드의 값을 꺼내와서 특정 값과 비교하였고, 그 값이 있으면 해당 값을 출력하고 없으면 null을 출력했다.

-> 결과

-> 이렇게 객체를 이용하여 비교하면 어떤 값이 배열에 존재하지 않는지 명확하게 알 수 있다.

 

◎ 래퍼 클래스 

-> 위에서 설명한 래퍼 클래스는 자바의 기본형을 객체로 감싸고, 객체에 메서드를 정의하여 편리하게 사용하도록 했다.

-> 래퍼 클래스는 기본형의 객체 버전이다.

-> 자바는 각각의 기본형에 대응하는 래퍼 클래스를 제공한다.

- byte -> Byte

- short -> Short

- int -> Integer

- long -> Long

- float -> Float

- double -> Double

- char -> Character

- boolean -> Boolean

-> 자바가 제공하는 기본 래퍼 클래스는 불변하고, equals(객체라서 new 키워드로 생성하면 서로 다른 참조 값을 가진 인스턴스가 생성되기 때문)로 비교해야 한다는 특징을 가지고 있다.

 

public class WrapperClassMain {
    public static void main(String[] args) {
        Integer newInteger = new Integer(10); // 미래에 삭제 예정. 가급정 valueOf()를 사용할 것
        Integer integerObj = Integer.valueOf(10);

        System.out.println("newInteger = " + newInteger);
        System.out.println("integerObj = " + integerObj);

        Long longObj = Long.valueOf(100);
        Double doubleObj = Double.valueOf(10.7);

        System.out.println("longObj = " + longObj);
        System.out.println("doubleObj = " + doubleObj);

        System.out.println();

        System.out.println("내부 값 읽기");
        int iv = integerObj.intValue();
        long lv = longObj.longValue();
        double dv = doubleObj.doubleValue();

        System.out.println("iv = " + iv);
        System.out.println("lv = " + lv);
        System.out.println("dv = " + dv);

        System.out.println();

        System.out.println("비교");
        System.out.println("== : " + (newInteger == integerObj));
        System.out.println("equals : " + (newInteger.equals(integerObj)));
    }
}

-> 결과

-> Integer.valueOf(10)를 사용하면 Integer 객체를 생성한다.

-> 이렇게 기본형 값을 객체를 생성하여 넣는 것을 박싱(boxing)이라 한다.

-> Integer.valueOf()에는 -128~127 범위의 Integer 클래스를 미리 생성하여 해당 범위 값을 조회하면 미리 생성된 Integer 객체를 반환한다.(최적화 기능임) 즉, -128~127 범위 내의 값을 넣고 Integer 객체들을 생성하고 == 비교를 하면 true가 나온다. 

-> 반대로 intValue()를 통해 Integer에 있는 int형 값을 꺼내는 것을 언박싱(unboxing)이라 한다.

 

◎ 오토 박싱/언박싱

-> 기본형을 래퍼 클래스로 변환할 때 valueOf()를 래퍼 클래스를 기본형으로 가져올 때는 xxxValue()를 사용하여 각각 박싱과 언박싱을 했다.

-> 자바에서는 기본형을 래퍼 클래스로 변환하거나 래퍼 클래스를 기본형으로 변환할 때 자동으로 변환하도록 해준다.

public class AutoBoxingMain2 {
    public static void main(String[] args) {
        // primitive -> wrapper
        int value = 7;
        Integer boxedValue = value; // auto boxing

        // wrapper -> primitive
        int unBoxedValue = boxedValue; // auto unboxing

        System.out.println("boxedValue = " + boxedValue);
        System.out.println("unBoxedValue = " + unBoxedValue);
    }
}

 

-> 위 처럼 따로 변환 메서드를 사용하지 않아도 알아서 박싱과 언박싱을 한다.

-> 실제로는 컴파일러가 아래와 같이 valueOf()와 xxxValue()를 추가하여 박싱과 언박싱을 한다.

Integer boxedValue = value; // auto boxing
Integer boxedValue = Integer.valueOf(value); // 컴파일러가 해당 코드를 자동으로 추가

int unboxedValue = boxedValue; // auto unboxing
int unboxedValue = boxedValue.intValue(); // 컴파일러가 해당 코드를 자동으로 추가

 

◎ 래퍼 클래스 성능

-> 다음은 기본형과 래퍼 클래스의 성능 차이를 비교하는 코드이다.

public class WrapperVsPrimitive {
    public static void main(String[] args) {
        int iterations = 1000000000; // 반복 횟수
        long starTime, endTime;

        // 기본형 long
        long sumPrimitive = 0;
        starTime = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            sumPrimitive += i;
        }

        endTime = System.currentTimeMillis();
        System.out.println("sumPrimitive : " + sumPrimitive);
        System.out.println("기본 자료형 long 실행 시간 : " + (endTime - starTime) + "ms");
    }
}

 -> 먼저 기본형 long을 10억번 더하는 연산이다.

-> 결과

public class WrapperVsPrimitive {
    public static void main(String[] args) {
        int iterations = 1000000000; // 반복 횟수
        long starTime, endTime;

        // 래퍼 클래스 Long 사용
        Long sumWrapper = 0L;
        starTime = System.currentTimeMillis();
        for (int i = 0; i < iterations; i++) {
            sumWrapper += i; // 오토 박싱 발생
        }

        endTime = System.currentTimeMillis();
        System.out.println("sumWrapper = " + sumWrapper);
        System.out.println("래퍼 클래스 Long 실행 시간 : " + (endTime - starTime) + "ms");

-> 다음은 래퍼 클래스 Long을 이용한 코드이다.

-> 결과

-> 연산 결과 기본형이 더 빠른 것을 알 수 있다.

-> 기본형은 메모리에서 단순히 해당 기본형 크기만큼만 차지한다. int형이면 4byte의 메모리를 사용한다. 반면 래퍼 클래스는 내부에 필드로 가지고 있는 기본형의 값과 자바에서 객체를 다루는데 필요한 객체의 메타데이터를 포함하고 있기 때문에 더 많은 메모리를 차지한다.

 

◎ Class 클래스

-> 클래스의 정보(메타 데이터)를 다루른데 사용한다. Class를 통해 개발자는 실행 중인 자바 애플리케이션 내에 필요한 클래스 속성과 메서드에 대한 정보를 조회 및 조작을 할 수 있다.

-> 다음은 Class 클래스의 주요 기능이다.

- 타입 정보 얻기 : 클래스 이름, 슈퍼 클래스, 인터페이스 등과 같은 정보 조회

- 리플렉션 : 클래스에 정의된 메서드, 필드, 생성자 등을 조회하고 이를 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 등의 작업 가능

- 동적 로딩과 생성 : Class.forName() 메서드를 이용해 클래스를 동적으로 로드하고, newInstance()를 통해 새로운 인스턴스를 생성한다.

- 애너테이션 처리 : 클래스에 적용된 애너테이션을 조회하고 처리하는 기능 제공

 

-> 다음은 String 클래스에 대한 메타 데이터를 조회하는 코드이다.

public class ClassMetaMain {
    public static void main(String[] args) throws Exception {
        // 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("i = " + i.getName());
        }

    }
}

-> Class 클래스는 다음과 같이 3가지 방법으로 조회할 수 있다.

        Class clazz = String.class; // 1. 클래스에서 조회
        Class clazz = new String().getClass(); // 2. 인스턴스에서 조회
        Class clazz = Class.forName("java.lang.String"); // 3. 문자열로 조회

 

System 클래스

-> System과 관련된 기본 기능을 제공하는 클래스

public class SystemMain {
    public static void main(String[] args) {

        // 현재 시간(밀리초)
        long currentTimeMillis = System.currentTimeMillis();
        System.out.println("currentTimeMillis = " + 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.getProperty("java.version"));

        // 배열 고속 복사
        char[] originalArr = {'h', 'e', 'l', 'l', 'o'};
        char[] copiedArr = new char[5];
        System.arraycopy(originalArr, 0, copiedArr, 0, originalArr.length);

        // 배열 출력
        System.out.println("copiedArr = " + copiedArr);
        System.out.println("Arrays.toString = " + Arrays.toString(copiedArr));

        // 프로그램 종료
        System.exit(0);
        
    }
}

 

 

 

★ 참고 및 출처

스프링 DB 1편 - 데이터 접근 핵심 원리 

'Java' 카테고리의 다른 글

중첩 클래스 ,내부 클래스1  (0) 2025.04.07
열거형 - ENUM  (0) 2025.04.01
String 클래스  (0) 2024.11.18
불변객체  (0) 2024.10.31
Object 클래스  (0) 2024.10.29