공부/Java

자바 래퍼 클래스(wrapper class)

Stair 2024. 9. 10. 20:57
반응형

기본형의 한계 1

자바는 객체 지향 언어이다. 그런데 바자 안에 객체가 아닌 것이 있다. 바로 int, double 같은 기본형(Primitive Type)이다. 기본형은 객체가 아니기 때문에 다음과 같은 한계가 있다.

1. 객체가 아님 : 기본형 데이터는 객체가 아니기 때문에, 객체 지향 프로그래밍의 장점을 살릴 수 없다. 예를 들어 객체는 유용한 메서드를 제공할 수 있는데, 기본형은 객체가 아니므로 메서드를 제공할 수 없다.

2. null 값을 가질 수 없음 : 기본형 데이터 타입은 null 값을 가질 수 없다. 때로는 데이터가 없음 이라는 상태를 나타내야 할 필요가 있는데, 기본형은 항상 값을 가지기 때문에 이런 표현을 할 수 없다.

 

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가 객체라면 value 객체 스스로 자기 자신의 값과 다른 값을 비교하는 메서드를 만드는 것이 더 유용할 것이다.

 

이런 문제를 해결하기 위해 int를 클래스로 만들 수 있다.

int는 클래스가 아니지만, 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()메서드를 캡슐화 한 것이다.

 

MyInteger 클래스를 통해 int를 내부에 품고 메서드를 통한 다양한 기능을 추가할 수 있게 되었다.

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()메서드를 통해 자기 자신을 통해 비교하는 상당히 객체지향적으로 짜여진 코드이다.

참고로 int는 기본형이기에 스스로 메서드를 가질 수 없다.

 

 

 

기본형의 한계 2

기본형과 null의 문제가 있다.

기본형은 항상 값을 가져야 한다. 하지만 때로는 데이터가 '없음'이라는 상태가 필요할 수 있다.

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

}

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

 

findValue()는 배열에 찾는 값이 있으면 해당 값을 반환하고, 찾는 값이 없으면 -1을 반환한다.

findValue()는 결과로 int 를 반환한다. int와 같은 기본형은 항상 값이 있어야 한다. 여기서도 값을 반환할 때 값을 찾지 못하면 숫자 중에 하나를 반환해야 하는데 보통 -1 또는 0을 사용한다.

-1
0
1
-1

그런데 실행값을 보면 

value가 target이랑 같을때(value가 -1이고 target도 -1일때) return 값이 -1이 나오는 것을 볼 수 있다.

코드만 놓고 보면 -1값을 찾아서 -1을 리턴한 것인지 값이 없어서 -1을 리턴한 것인지 명확하게 알 수 없다.

 

객체의 경우 데이터가 없다는 null이라는 명확한 값이 존재한다.

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

}

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

-1
0
1
null

 

앞서 만든 MyInteger 래퍼 클래스를 사용하였다.

실행결과를 보면 -1을 입력했을 시 -1을 반환하고

100을 입력했을시엔 null을 반환한다.

 

기본형의 경우는 항상 값이 존재해야 한다. 숫자의 경우 0,-1 같은 값이라도 항상 존재해야 한다. 반면에 객체인 참조형은 값이 없다는 null을 사용할 수 있다.(NullPointerException이 발생할수도 있기에 주의해서 사용하자)

 

자바는 기본형에 대응하는 래퍼 클래스를 기본으로 제공한다

참조형처럼 앞글자가 대문자이고, 불변객체이다.

또한 객체이기야 == 비교가 아닌 equals로 비교하여야 한다.

 

 

래퍼 클래스를 사용하는 간단한 예제이다.

public static void main(String[] args) {
    Integer newInteger = new Integer(10); //미래에 삭제 예정, 대신에 valueOf()를 사용
    Integer integerObj = Integer.valueOf(10);
    //Integer newInteger = Integer.valueOf(10);
    Long longObj = Long.valueOf(100);
    Double doubleObj = Double.valueOf(10.5);


    System.out.println("newInteger = " + newInteger.toString());
    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);

    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
비교
==: false
equals: true

 

 

기본형을 래퍼 클래스로 변경하는 것을 마치 박스에 물건을 넣은 것 같다고 하여 박싱(Boxing)이라 한다.

new Integer(10)처럼 직접 사용은 권장하지 않는다. 향후 자바에서 제거될 예정이기 때문이다.

대신에 Integer.valueOf(10)을 사용하자.

추가로 Integer.valueOf()에는 성는 최적화 기능이 있다. -128~127 범위의 클래스를 미리 생성하고 해당 범위의 값을 조회하면 미리 생성된 Integer 객체를 반환한다. 해당 범위의 값이 없으면 new Integer()를 호출한다.

 

 

 

오토 박싱, 오토 언박싱

자바에서 int를 Integer로 변환하거나, Integer를 int로 변환하는 부분을 정리해보자.

다음과 같이 valueOf(), intValue() 메서드를 사용하면 된다.

public static void main(String[] args) {
    int value = 7;
    Integer boxedValue = Integer.valueOf(value);

    int unboxedValue = boxedValue.intValue();

    System.out.println("boxedValue = " + boxedValue);
    System.out.println("unboxedValue = " + unboxedValue);

}

 

그러나 현업에서는 기본형을 래퍼클래스로 변환하거나 래퍼클래스를 기본형으로 변환하는 일이 자주 발생했다. 번거로움이 있어 번거로움을 해결하기 위해 오토 박싱(Auto-boxing),오토 언박싱(Auto-unboxing)을 지원하게 되었다.

public static void main(String[] args) {
    int value = 7;
    Integer boxedValue = value;//오토 박싱

    int unboxedValue = boxedValue;//오토 언박싱

    System.out.println("boxedValue = " + boxedValue);
    System.out.println("unboxedValue = " + unboxedValue);

}

 

오토 박싱과 오토 언박싱은 컴파일러가 개발자 대신 valueOf, xxxValue()등의 코드를 추가해주는 기능이다.

덕분에 기본형과 래퍼형을 서로 편리하게 변환할 수 있다.

 

 

 

주요 메서드와 성능

레퍼 클래스가 제공하는 주요 메서드들이 몇개 있다.

public static void main(String[] args) {
    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));
    System.out.println("sum: " + Integer.min(10,20));
    System.out.println("sum: " + Integer.max(10,20));


}

 

valueOf() : 래퍼 타입을 반환한다. 숫자, 문자열 모두 지원한다.

parseInt() : 문자열을 기본형으로 변환한다.

comepareTo() : 내 값과 인수로 넘어온 값을 비교한다. 내 값이 크면 1, 같으면 0, 내 값이 작으면 -1을 반환한다.

sum, min, max : static 메서드이다. 연산을 수행한다.

 

parseInt() vs valueOf()

원하는 타입에 맞는 메서드를 사용하면 된다.

valueOf("10")은  래퍼 타입을 반환한다.

parseInt("10")은 기본형을 반환한다.

 

 

래퍼클래스는 객체이기 때문에 기본형보다 다양한 기능을 제공한다.

그렇다면 더 좋은 래퍼 클래스만 제공하면 되지 왜 기본형을 제공할까?

public static void main(String[] args) {
    int iterations = 1_000_000_000;//반복 횟수 설정
    long startTime, endTime;

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

    System.out.println("sumPrimitive = " + sumPrimitive);
    System.out.println("long 실행 시간: " + (endTime-startTime));


    //래퍼 클래스 long
    Long sumWrapper = 0L;
    startTime = System.currentTimeMillis();
    for(int i = 0; i < iterations; i++){
        sumWrapper += i;
    }
    endTime = System.currentTimeMillis();

    System.out.println("sumWrapper = " + sumWrapper);
    System.out.println("long 실행 시간: " + (endTime-startTime));

}

 


sumPrimitive = 499999999500000000
long 실행 시간: 338
sumWrapper = 499999999500000000
long 실행 시간: 4412

 

 

기본 자료형을 사용했을때는 0.3초가 걸렸지만 래퍼클래스를 사용하니 4초가 넘게 걸렸다.

기본형은 메모리에서 단순히 그 크기만큼의 공간을 차지한다. 하지만 래퍼클래스는 객체이기에 기본형 값 뿐만 아니라 자바에서 객체 자체를 다루는데 필요한 객체 메타 데이터를 포함하므로 더 많은 메모리를 사용한다.

 

하지만 요즘 컴퓨터는 성능이 좋기에 기본형이든 래퍼클래스든 매우 빠른 연산이 수행된다.

CPU 연산을 아주 많이 수행하는 특수한 경우거나, 수만~수십만 이상 연속해서 연산을 수행해야 하는 경우라면 기본형을 사용하여 최적화를 고려하자.

그렇지 않다면 코드를 유지보수하기 더 나은것을 선택하면 된다.

 

 

 

Class 클래스

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

 

Class 클래스의 주요 기능은 다음과 같다.

타입 정보 얻기 : 클래스의 이름, 슈퍼클래스, 인터페이스, 접근 제한자 등과 같은 정보를 조회할 수 있다.

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

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

애노테이션 처리 : 클래스에 적용된 애노테이션(annotation)을 조회하고 처리하는 기능을 제공한다.

    public static void main(String[] args) throws Exception{
        //String Class 조회
        Class clazz = String.class; //1. 클래스에서 조회
//        Class clazz1 = new String().getClass(); //2. 인스턴스에서 조회
//        Class.forName("java.lang.String"); //3. 문자열로 조회

        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("declaredField = " + declaredField);
        }

        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("declaredMethod = " + declaredMethod);
        }

        //상위 클래스 정보 출력
        System.out.println("SuperClass: " + clazz.getSuperclass().getName());

        //인터페이스 정보 출력
        Class[] interfaces = clazz.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println("anInterface = " + anInterface);
        }


    }

 

String 클래스가 가진 다양한 정보를 Class를 통해서 확인할 수 있다.

 

class vs clazz  class는 자바의 예약어이다. 따라서 패키지명, 변수명으로 사용할 수 없다.

이런 이유로 자바 개발자들은 class 대신 clazz라는 이름을 관행으로 사용한다. clazz는 class와 유사하게 들리고, 이 단어가 class를 의미한다는 것을 쉽게 알 수 있다.

 

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

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

 

Class 클래스의 주요 기능

getDeclaredFields() : 클래스의 모든 필드를 조회한다.

getDeclaredMethods() : 클래스의 모든 메서드를 조회한다.

getSuperclass() : 클래스의 부모 클래스를 조회한다.

getInterfaces() : 클래스의 인터페이스들을 조회한다.

 

 

 

Class 클래스에는 클래스의 모든 정보가 들어있다. 이 정보를 기반으로 인스턴스를 생성하거나, 메서드를 호출하고, 필드의 값도 변경할 수 있다.

public class Hello {

    public String hello(){
        return "hello!";
    }
}
    public static void main(String[] args) throws Exception {
        Class helloClass = Hello.class;
//        Class helloClass = Class.forName("lang.clazz.Hello");

        Hello hello = (Hello) helloClass.getDeclaredConstructor().newInstance();
        String result = hello.hello();
        System.out.println("result = " + result);
    }

 

 

 

 

 

 

System 클래스

System 클래스는 시스템과 관련된 기본 기능들을 제공한다.

public static void main(String[] args) {
    //현재 시간(밀리초)를 가져온다
    long currentTimeMillis = System.currentTimeMillis();
    System.out.println("currentTimeMillis = " + currentTimeMillis);

    //현재 시간(나노초)를 가져온다
    long nanoTime = System.nanoTime();
    System.out.println("nanoTime = " + nanoTime);

    //환경 변수를 가져온다.
    System.out.println("System.getenv()" + System.getenv());

    //시스템 속성을 읽는다.
    System.out.println("properties"+System.getProperties());
    System.out.println("Java version" + System.getProperty("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 = " + Arrays.toString(copiedArray));

    //프로그램 종료
    System.exit(0);
    System.out.println("hello");
}

 

번외

표준 입력, 출력, 에러 스트림 : System.in, System.out, System.err은 각각 표준 입력, 표준 출력 표준 오류 스트림을 나타낸다.

 

나머지는 위의 코드를 돌려보며 이해하면 될 것 같다.

 

 

 

Math, Random 클래스
matho는 수 많은 수학 문제를 해결해주는 클래스이다. 너무 많은 기능을 제공하기 때문에 이런것이 있구나 하는 정도면 충분하다.

public static void main(String[] args) {
    // 기본 연산 메서드
    System.out.println("max(10,20): " + Math.max(10,20)); // 최대값
    System.out.println("min(10,20): " + Math.min(10,20)); // 최소값
    System.out.println("abs(-10): " + Math.abs(-10)); // 절대값

    // 반올림 및 정밀도 메서드
    System.out.println("Ceil(2.1): " + Math.ceil(2.1));// 올림
    System.out.println("floor(2.1): " + Math.floor(2.1));// 내림
    System.out.println("round(2.1): " + Math.round(2.1));// 반올림

    //기타 유용한 메서드
    System.out.println("sqrt(4): " + Math.sqrt(4)); //제곱근
    System.out.println("random(): " + Math.random()); // 0.0~1.0 사이의 double 값
}
public static void main(String[] args) {
    Random random = new Random();

    int randomInt = random.nextInt();
    System.out.println("randomInt = " + randomInt);

    double randomDouble = random.nextDouble();
    System.out.println("randomDouble = " + randomDouble);

    boolean randomBoolean = random.nextBoolean();
    System.out.println("randomBoolean = " + randomBoolean);

    //범위 조회
    int randomRange1 = random.nextInt(10); // 0~9까지
    System.out.println("randomRange1 = " + randomRange1);

    int randomRange2 = random.nextInt(10) + 1;
    System.out.println("randomRange2 = " + randomRange2);

}

 

 

 

씨드 - seed

seed가 같으면 Random의 결과가 같게 된다.

Random random = new Random(1); //seed

 

seed값을 직접 사용하면 결과 값이 항상 같기 때문에 결과가 달라지는 랜덤 값을 구할 수 없다. 하지만 결과가 고정되기 때문에 테스트 코드 같은 곳에서 같은 결과를 검증할 수 있다.

 

 

반응형

'공부 > Java' 카테고리의 다른 글

자바 날짜와 시간  (1) 2024.09.12
자바 열거형-ENUM  (3) 2024.09.11
자바 String 클래스  (1) 2024.09.10
자바 불변 객체  (0) 2024.09.09
자바 Object 클래스  (1) 2024.09.08