본문 바로가기

항해 99/Java

Java - Wrapper, Class, System, Math, Random

래퍼 클래스(Wrapper Class)

기본 데이터 타입(primitive data type)을 객체(object)로 다루기 위해 제공되는 클래스를 말함

자바의 모든 기본 데이터 타입에는 해당하는 Wrapper 클래스가 있으며, 이는 java.lang 패키지에 포함되어 있다.

 

Wrapper Class

  • boolean Boolean
  • char → Character
  • byte Byte
  • short Short
  • int Integer
  • long Long
  • float Float
  • double Double

기본형(Primitive Type)의 한계

  • 객체가 아님: 기본형 데이터는 객체가 아니기 때문에, 객체 지향 프로그래밍의 장점을 살릴 수 없다(객체는 유용한 메서드를 제공할 수 있지만 기본형은 안 됨).
    • 객체 참조가 필요한 컬렉션 프레임워크나 제네릭도 사용할 수 없다.
  • null값을 가질 수 없음: 기본형 데이터 타입은 null 값을 가질 수 없다. 때로는 데이터가 없음이라는 상태를 나타내야 할 필요가 있는데, 기본형은 항상 값을 가지기 때문에 이런 표현을 할 수 없다.

래퍼 클래스 사용

래퍼 클래스 생성 - 박싱(Boxing)

  • 기본 데이터 타입을 해당하는 Wrapper 클래스 객체로 변환하는 것
    • 박스에 물건을 넣은 것 같다고 해서 박싱(Boxing)이라 한다.
  • Integer.valueOf(10) 처럼 valueOf()를 사용해서 변환한다.

기본 데이터 타입 변환 - 언박싱(Unboxing)

  • Wrapper 클래스 객체를 기본 데이터 타입으로 변환하는 것
    • 박스에 들어있는 물건을 꺼내는 것 같다고 해서 언박싱(Unboxing)이라 한다.
  • IntegerObj.intValue() 처럼 사용해서 변환한다.

래퍼 클래스 비교 - equals() 사용

  • 래퍼 클래스는 객체이기 때문에 == 비교(동일성 비교)를 하면 인스턴스의 참조값을 비교한다.
  • 래퍼 클래스는 내부의 값을 비교하도록 equals()(동등성 비교)를 재정의 해두었다. 따라서 값을 비교할 때는 equals()를 사용해야 한다.

 

예시 코드

package lang.wrapper;

public class WrapperClassMain {

    public static void main(String[] args) {
        Integer newInteger = new Integer(10); // 미래에 삭제 예정, 대신 valueOf()를 사용
        Integer integerObj = Integer.valueOf(10); // -128 ~ 127 자주 사용하는 숫자 값 재사용, 불변
        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("longValue = " + longValue);

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

 

Autoboxing Unboxing

자바 1.5부터  Autoboxing과 Auto-Unboxing이 도입되어, 기본 데이터 타입과 Wrapper 클래스 간의 변환이 자동으로 이루어 진다.

package lang.wrapper;

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

 

Wrapper Class - 주요 메서드

  • valueOf(): 래퍼 타입을 반환한다. 숫자, 문자열을 모두 지원한다.
  • parseInt(): 문자열을 기본형으로 변환한다.
    • 각 타입에 맞는 parseXxx()가 존재한다.
  • compareTo(): 내 값과 인수로 넘어온 값을 비교한다. 내 값이 크면 1, 같으면 0, 내 값이 작으면 -1을 반환한다.
  • Integer.sum(), Integer.max(), Integer.min(): static 메서드, 간단한 연산을 수행한다.
package lang.wrapper;

public class WrapperUtilsMain {

    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("min: " + Integer.min(10, 20));
        System.out.println("max: " + Integer.max(10, 20));
    }
}

 

Wrapper 클래스 성능

래퍼 클래스는 객체이기 때문에 기본형보다 다양한 기능을 제공하지만, 기본형보다 처리 속도가 느리다.

package lang.wrapper;

public class WrapperVsPrimitive {

    public static void main(String[] args) {
        int iterations = 1_000_000_000; // 반복 횟수 설정, 10억
        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) + "ms");

        // 래퍼 클래스 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) + "ms");
    }
}
실행 결과
sumPrimitive = 499999999500000000
기본 자료형 long 실행 시간: 318ms
sumWrapper = 499999999500000000
래퍼 클래스 Long 실행 시간: 1454ms

 

위 예시 코드처럼 기본형 연산이 래퍼 클래스보다 대략 5배 정도 빠르다(계산 결과는 시스템마다 차이가 있다).

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

 

기본형 vs 래퍼 클래스

일반적인 애플리케이션을 만드는 관점에서 보면 이런 부분을 최적화해도 사막의 모래알 하나 정도의 차이가 날 뿐이다.

CPU 연산을 아주 많이 수행하는 특수한 경우나 수만 ~ 수십만 이상 연속해서 연산을 수행해야 하는 경우에는 기본형을 사용해 최적화를 고려할 수 있다.

그렇지 않은 일반적인 경우라면 코드를 유지보수하기 더 나은 것을 선택하면 된다.

 

유지보수 vs 최적화

유지보수 vs 최적화를 고려해야 하는 상황에서는 유지보수하기 좋은 코드를 먼저 고민해야 한다. 최신 컴퓨터는 매우 빠르기 때문에 메모리 상에서 발생하는 연산을 몇 번 줄인다고해도 실질적인 도움이 되지 않는 경우가 많다.

  • 성능 최적화는 대부분 단순함 보다는 복잡함을 요구하고, 더 많은 코드들을 추가로 만들어야 한다. 최적화를 위해 유지보수해야 하는 코드가 더 늘어나는 것인데, 최적화를 했어도 전체 애플리케이션 성능 관점에서 보면 불필요한 최적화를 할 가능성이 있다.
  • 웹 애플리케이션의 경우 메모리 상에서 발생하는 연산보다 네트워크 호출을 한 번을 줄이는 것이 더 효과적인 경우가 많다.
  • 개발 이후 성능 테스트를 하고 정말 문제가 되는 부분을 찾아서 최적화하는 것이 좋다.

 

Class 클래스

자바에서 Class 클래스는 클래스의 정보(메타 데이터)를 다루는데 사용됨.

Class 클래스를 통해 자바 애플리케이션 내에서 필요한 클래스의 속성과 메서드에 대한 정보를 조회하고 조작할 수 있다.

 

Class 클래스의 주요 기능

  • 타입 정보 얻기: 클래스의 이름, 슈퍼클래스, 인터페이스, 접근 제한자 등과 같은 정보를 조회할 수 있다.
  • 리플렉션: 클래스에 정의된 메서드, 필드, 생성자 등을 조회하고, 이들을 통해 객체 인스턴스를 생성하거나 메서드를 호출하는 등의 작업을 할 수 있다.
  • 동적 로딩과 생성: Class.forName() 메서드를 사용하여 클래스를 동적으로 로드하고, newInstance() 메서드를 통해 새로운 인스턴스를 생성할 수 있다.
  • 애노테이션 처리: 클래스에 적용된 애노테이션(annotation)을 조회하고 처리하는 기능을 제공한다.

예시 코드

package lang.clazz;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ClassMetaMain {
    public static void main(String[] args) throws Exception {
        //Class 조회
        Class clazz = String.class; // 1. 클래스에서 조회
        //Class clazz1 = new String().getClass(); // 2. 인스턴스에서 조회
        //Class clazz2 = 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());
        }

    }
}

 

Clazz?

class는 자바의 예약어이기 때문에 패키지명, 변수명으로 사용할 수 없다, 따라서 자바 개발자들은 class 대신 clazz라는 이름을 관행으로 사용한다.

 

Class 클래스 주요 메서드

  • getDeclaredFields(): 클래스의 모든 필드를 조회한다.
  • getDeclaredMethods(): 클래스의 모든 메서드를 조회한다.
  • getSuperclass(): 클래스의 부모 클래스를 조회한다.
  • getInterfaces(): 클래스의 인터페이스들을 조회한다.

 

System 클래스

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

 

주요 기능

  • 표준 입력, 출력, 오류 스트림: System.in, System.out, System.err
  • 시간 측정: System.currentTimeMillis(), System.nanoTime()은 현재 시간을 밀리초 또는 나노초 단위로 제공한다.
  • 환경 변수: System.getenv()로 OS에서 설정한 환경 변수의 값을 얻을 수 있다.
  • 시스템 속성: System.getProperties()를 사용해 현재 시스템 속성을 얻거나 System.getProperty(String key)로 특정 속성을 얻을 수 있다(시스템 속성: 자바에서 사용하는 설정 값)
  • 시스템 종료: System.exit(int status)는 프로그램을 종료하고, OS에 프로그램 종료의 상태 코드를 전달한다.
    • 상태 코드 0 : 정상 종료
    • 상태 코드 0 이 아님 : 오류나 예외적인 종료
  • 배열 고속 복사: System.arraycopy는 시스템 레벨에서 최적화된 메모리 복사 연산을 사용한다. 직접 반복문을 사용해 배열을 복사할 때보다 수 배 이상 빠른 성능을 제공한다.
package lang.system;

import java.util.Arrays;

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[] 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("Arrays.toString = " + Arrays.toString(copiedArray));

        // 프로그램 종료
        System.exit(0); // 사용 지양
    }
}

 

Math, Random 클래스

Math는 수 많은 수학 문제를 해결해주는 클래스이고, Random은 랜덤한 값을 생성하는 클래스이다(Math.Random을 사용해도 되지만 Random 클래스를 사용하면 더욱 다양한 랜덤값을 구할 수 있다).

 

Math 클래스는 많은 기능을 제공하기 때문에 사용할 때 검색하거나 API 문서를 통해 필요한 기능을 찾는 것이 좋다.

 

Math 기본 메서드 - 예시 코드

package lang.math;

public class MathMain {

    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.5): " + Math.round(2.5)); // 반올림

        // 기타 유용한 메서드
        System.out.println("sqrt(4): " + Math.sqrt(4)); // 제곱근
        System.out.println("random(): " + Math.random()); // 0.0 ~ 1.0 사이의 double 값
    }
}

 

Random 클래스 예시 코드

package lang.math;

import java.util.Random;

public class RandomMain {

    public static void main(String[] args) {
        Random random = new Random();
        //Random random = new Random(1); // seed가 같으면 Random의 결과가 같다.

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

        double randomDouble = random.nextDouble(); // 0.0d ~ 1.0d
        System.out.println("randomDouble = " + randomDouble);

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

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

        int randomRange2 = random.nextInt(10) + 1; // 1 ~ 10까지 출력
        System.out.println("1 ~ 10: " + randomRange2);
    }
}

 

Seed - 씨드

랜덤은 내부에서 씨드(Seed) 값을 사용해서 랜덤 값을 구하는데,  이 씨드 값이 같으면 항상 같은 결과가 출력된다.

Random random = new Random(1); //seed가 같으면 Random의 결과가 같다.
  • new Random(): 생성자를 비워두면 내부에서 System.nanoTime()에 여러가지 복잡한 알고리즘을 섞어서 씨드값을 생성한다. 따라서 반복 실행해도 결과가 항상 달라진다.
  • new Random(int seed): 생성자에 씨드 값을 직접 전달할 수 있다. 씨드 값이 같으면 여러 번 반복 실행해도 실행 결과가 같다(결과가 고정되기 때문에 테스트 코드 같은 곳에서 같은 결과를 검증할 수 있다).

'항해 99 > Java' 카테고리의 다른 글

Java - 날짜와 시간  (0) 2024.07.26
Java - ENUM  (0) 2024.07.18
String 클래스  (0) 2024.06.05
Java - 불변 객체  (0) 2024.05.31
Java - Object  (0) 2024.05.29