래퍼, Class

황상익·2024년 5월 13일

Inflearn JAVA

목록 보기
27/61

래퍼 클래스 - 기본형의 한계1

기본형의 한계

자바는 객체지향 언어, 자바 안에 객체가 아닌 것이 있음. int double 같은 primitive type
기본형은 객체가 아니기 때문에 한계가 있음

  • 객체가 아님 :
    기본형 데이터는 객체가 아니기 때문에 객체 지향 프로그래밍의 장점을 살릴 수 X.
    -> 객체 참조가 필요한 컬랙션 프레임워크를 사용 X, 제네릭도 사용 X

  • null 값을 가질 수 X :
    기본형 데이터 타입은 null 값을 가질 수 X. 데이터가 없음 이라는 상태를 나타내야 할 필요
    -> 기본형은 항상 값을 갖기 떄문에 표현 X

public class MyIntegerMethodMain0 {
    public static void main(String[] args) {
        int val = 10;
        int i1 = compareTo(val, 5);
        int i2 = compareTo(val, 10);
        int i3 = compareTo(val, 15);

        System.out.println("i1 = " + i1);
        System.out.println("i2 = " + i2);
        System.out.println("i3 = " + i3);
    }

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

val 비교 대상 값을 compareTo 라는 외부 메서드 사용해서 비교
자기 자신 val 다른 연산하는 것이기 때문에 항상 자기 자신의 값인 val 사용.
이런 경우 val 객체라면 val 객체 스스로 자기 자신의 값과 다른 값을 비교하는 메서드를 만드는 것 유용

직접 만든 래퍼 클래스

int를 클래스로 형성.
int 값을 갖고 클래스를 만들면 된다. 이 코드는 마치 int를 클래스로 감싸서 만드는 것 처럼 보인다. 특정 기본형 Wrap 만드는 클래스를 래퍼 클래스

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를 내부에 품고, 메서드를 통해 다양한 기능을 추가. 덕분에 데이터 조각에 불과한 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(15);
        System.out.println("i1 = " + i1);
        System.out.println("i2 = " + i2);
        System.out.println("i3 = " + i3);
    }
}

래퍼 클래스 - 기본형 한계 2

기본형과 null

기본형은 항상 값을 가져야 한다.

public class MyIntegerNullMain0 {
    public static void main(String[] args) {
        int[] intArr = {-1, 0, 1, 2, 3};
        System.out.println(findVal(intArr, -1));
        System.out.println(findVal(intArr, 0));
        System.out.println(findVal(intArr, 1));
        System.out.println(findVal(intArr, 100));

    }

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

값이 -1일때 -1을 반환, 배열에 없는 값을 반환해도 -1. 구분이 안된다.

public class MyIntegerNullMain1 {
    public static void main(String[] args) {
        MyInteger[] intArr = {new MyInteger(-1), new MyInteger(0), new MyInteger(1)};
        System.out.println(findVal(intArr, -1));
        System.out.println(findVal(intArr, 0));
        System.out.println(findVal(intArr, 1));
        System.out.println(findVal(intArr, 100));
    }

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

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

래퍼 클래스 - 자바 래퍼 클래스

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

  • byte Byte
  • short Short
  • int Integer
  • long Long
  • float Float
  • double Double
  • char Character
  • boolean Boolean
public class WrapperClassMain {
    public static void main(String[] args) {
        Integer newInteger = new Integer(10); // 미래에 삭제 예정, valueOf를 사용해라
        Integer objInteger = Integer.valueOf(10);// -128 ~ 127 자주 사용하는 숫자 값 (풀을 사용)

        Long objLong = Long.valueOf(100);
        Double objDouble = Double.valueOf(10.5);

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

        System.out.println("objLong = " + objLong);
        System.out.println("objDouble = " + objDouble);

        System.out.println("내부 값 읽기");
        int intVal = objInteger.intValue();
        System.out.println("intVal = " + intVal);

        long longVal = objLong.longValue();
        System.out.println("longVal = " + longVal);

        System.out.println("비교");
        System.out.println("== " + (newInteger == objInteger));
        System.out.println("== " + (newInteger.equals(objInteger)));


    }
}

래퍼 클래스 생성 - Boxing
기본형을 래퍼 클래스로 변경하는 것은 마치 박스에 물건을 넣은 것과 같다.
new Integer(10) 은 직접 사용하면 안된다. 작동은 하지만, 향후 자바에서 제거될 예정이다.
대신에 Intger.valueOf(10)을 사용

추가로 Integer.valueOf에는 성능 최적화 기능. 주로 사용하는 Integer 클래스를 미리 생성, 해당 범위를 조회 -> 미리 생성된 Integer 객체를 반환. 해당 값 범위 있다면 new Integer를 호출

  • 문자열 푸과 비슷하게 자주 사용 되는 숫자를 미리 생성, 재사용

intValue - unBoxing
래퍼 클래스에 들어있는 기본형 값을 다시 꺼내는 매서드
박스 안에 들어 있는 물건을 꺼내는 것과 같다고 해서 unboxing

비교는 equals 사용
래퍼 클래스는 객체이기 때문에 == 비교를 하면 인스턴스의 참조값 비교
래퍼 클래스는 내부의 값을 비교하도록 equals를 재정의

래퍼 클래스 - AutoBoxing

자바에서 int를 Integer로 변환, Integer를 int 변환하는 부분을 정리

public class AutoboxingMain1 {
    public static void main(String[] args) {
        //primitive -> wrapper
        int val = 7;
        Integer boxedVal = Integer.valueOf(val);

        //wrapper -> primitive
        int unboxedValue = boxedVal.intValue();

        System.out.println("boxedVal = " + boxedVal);
        System.out.println("unboxedValue = " + unboxedValue);
    }
}
  • 박싱 : valueOf()
  • 언박싱 : xxxValue() (ex : intValue(), doubleValue())
public class AutoboxingMain2 {
    public static void main(String[] args) {
        //primitive -> wrapper
        int val = 7;
        Integer boxedVal = val; //오토 박싱

        //wrapper -> primitive
        int unboxedValue = boxedVal; //오토 언박싱

        System.out.println("boxedVal = " + boxedVal);
        System.out.println("unboxedValue = " + unboxedValue);
    }
}

오토 박싱과 언박싱은 컴파일러가 개발자 대신 valueOf등의 코드를 자동으로 추가
기본형과 래퍼형을 서로 편리하게 변환

래퍼 클래스 - 주요 메서드와 성능

public class WrapperUtilsMain {
    public static void main(String[] args) {
        Integer i1 = Integer.valueOf(10);
        Integer i2 = Integer.valueOf("10");// 문자열, 래퍼 객체 변환
        int intVal = Integer.parseInt("10"); // 문자열 전용, 기본형 반환

        int compareRst = i1.compareTo(20);
        System.out.println("compareRst = " + compareRst);

        System.out.println("Integer.sum(10, 20) = " + Integer.sum(10, 20));
        System.out.println("Integer.min(10, 20) = " + Integer.min(10, 20));
        System.out.println("Integer.max(10, 20) = " + Integer.max(10, 20));


    }
}

valueOf() : 래퍼 타입을 반환한다. 숫자, 문자열을 모두 지원한다.
parseInt() : 문자열을 기본형으로 변환한다.
compareTo() : 내 값과 인수로 넘어온 값을 비교한다. 내 값이 크면 1 , 같으면 0 , 내 값이 작으면 -1 을 반환 한다.
Integer.sum() , Integer.min() , Integer.max() : static 메서드이다. 간단한 덧셈, 작은 값, 큰 값 연산을 수행한다.

parseInt() vs valueOf()
원하는 타입에 맞는 메서드를 사용하면 된다.
valueOf("10") 는 래퍼 타입을 반환한다.
parseInt("10") 는 기본형을 반환한다.
Long.parseLong() 처럼 각 타입에 parseXxx() 가 존재한다.

래퍼 클래스와 성능

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

public class WrapperPrimitive {
    public static void main(String[] args) {
        int iterations = 1000000000;
        long startTime, endTime;

        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("실행시간 = " + (endTime - startTime));


        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("실행시간 = " + (endTime - startTime));
    }
}

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

기본형, 래퍼 클래스 무엇을 사용

일반적인 애플리케이션을 만드는 관점에서 보면 이런 부분을 최적화해도 사막의 모래알 하나 정도의 차이가 날 뿐 이다.
CPU 연산을 아주 많이 수행하는 특수한 경우이거나, 수만~ 수십만 이상 연속해서 연산을 수행해야 하는 경우라면 기본형을 사용해서 최적화를 고려하자.
그렇지 않은 일반적인 경우라면 코드를 유지보수하기 더 나은 것을 선택하면 된다.

유지보수 vs 최적화

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

코드 변경 없이 성능 최적화를 하면 가장 좋겠지만, 성능 최적화는 대부분 단순함 보다는 복잡함을 요구하고, 더 많은 코드들을 추가로 만들어야 한다. 최적화를 위해 유지보수 해야 하는 코드가 더 늘어나는 것이다. 그런데 진짜문제는 최적화를 한다고 했지만 전체 애플리케이션의 성능 관점에서 보면 불필요한 최적화를 할 가능성이 있다.
특히 웹 애플리케이션의 경우 메모리 안에서 발생하는 연산 하나보다 네트워크 호출 한 번이 많게는 수십만배 더오래 걸린다. 자바 메모리 내부에서 발생하는 연산을 수천번에서 한 번으로 줄이는 것 보다, 네트워크 호출 한 번을 더 줄이는 것이 더 효과적인 경우가 많다.
권장하는 방법은 개발 이후에 성능 테스트를 해보고 정말 문제가 되는 부분을 찾아서 최적화 하는 것이다.

Class 클래스

자바에서 class는 클래스의 정보를 다루는데 사용, Class를 통해 개발자는 실행중인 자바 애플리케이션 내에서 필요한 클래스의 속성과 메서드에 대한 정보를 조회

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

  • 타입 정보 얻기 : 클래스 이름, 슈퍼 클래스, 인터페이스, 접근 제어자 등과 같은 정보를 조회
  • 리플렉션 : 클래스에 정의된 메서드, 필드, 생성자 조회, 객체 인스턴스를 생성하거나 메서드를 호출 하는 작업 진행
  • 동적 로딩과 생성 : Class.forName() 메서드를 사용해서 클래스를 동적으로 로드, newInstance 메서드를 통해 새로운 인스턴스를 새엇ㅇ
  • 애노테이션 처리 : 클래스에 적용된 애노테이션을 조회 처리
public class ClassMetaMain {
    public static void main(String[] args) throws Exception {
        //클래스 조회
        Class clazz = String.class; //클래스에서 조회
        Class clazz1 = new String().getClass(); //인스턴스 조회
        Class clazz2 = Class.forName("java.lang.String");


        //필드 조회
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println("field = " + field.getType() + " " + field.getName());
        }

        //모든 메서드 출력
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("declaredMethod = " + declaredMethod);
        }

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

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

Class 클래스의 주요 기능

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

클래스 생성하기

class 클래스에는 클래스의 모든 정보 포함. 인스턴스를 생성, 메서드 호출, 필드값 변경 가능

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

        Hello hello = (Hello) helloClass.getDeclaredConstructor().newInstance();
        String rst = hello.Hello();

        System.out.println("rst = " + rst);
    }
}

리플렉션
Class를 사용하면 클래스의 메타 정보를 기반으로 클래스에 정의된 메서드 필드 생성자 등을 조회, 객체 인스턴스를 생성하거나 메서드를 호출하는 작업을 할 수 있음. 이런 작업을 리플렉션

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("System.getenv() = " + System.getenv());

        //시스탬 속성을 읽는다.
        System.out.println("System.getProperties() = " + System.getProperties());
        //자바가 사용하는 운영체제

        System.out.println("System.getProperty(javaVersion) = " + System.getProperty("javaVersion"));
        //"javaVersion" -> javaVersion을 읽을 수 있다.

        //배열을 고속으로 복사
        char[] originalArray = new char[]{'h', 'e', 'l', 'l', 'o'};
        char[] copy = new char[5];
        //0부터 copy를 해서 0번 부터 복사해서 넣는다.
        //통채로 hardware에서 읽어서 빠름
        System.arraycopy(originalArray, 0, copy, 0, originalArray.length);

        System.out.println("copy = " + copy);
        //toString을 사용해서 이쁘게 넣어줌
        System.out.println(" = " + Arrays.toString(copy));

        System.exit(0);
    }
}

표준 입력, 출력, 오류 스트림 : 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 는 시스템 레벨에서 최적화된 메모리 복사 연산을 사용한다. 직접 반복문을 사용해서 배열을 복사할 때 보다 수 배 이상 빠른 성능을 제공한다

Math.Random 클래스

Math 클래스
1. 기본 연산 메서드
abs(x) : 절대값
max(a, b) : 최대값
min(a, b) : 최소값

  1. 지수 및 로그 연산 메서드
    exp(x) : e^x 계산
    log(x) : 자연 로그
    log10(x) : 로그 10
    pow(a, b) : a의 b 제곱

  2. 반올림 및 정밀도 메서드
    ceil(x) : 올림
    floor(x) : 내림
    rint(x) : 가장 가까운 정수로 반올림
    round(x) : 반올림

  3. 삼각 함수 메서드
    sin(x) : 사인
    cos(x) : 코사인
    tan(x) : 탄젠트

  4. 기타 유용한 메서드
    sqrt(x) : 제곱근
    cbrt(x) : 세제곱근
    random() : 0.0과 1.0 사이의 무작위 값 생성

public class MathMain {
    public static void main(String[] args) {
        //기본 연산 메서드
        System.out.println(Math.max(10, 20));
        System.out.println(Math.min(10, 20));
        System.out.println(Math.abs(-20));

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

        //기타 유용한 메서드
        System.out.println(Math.sqrt(4)); //제곱근
        System.out.println(Math.random());
    }
}

Random 클래스

랜덤의 경우 Math.random을 사용, Random 클래스를 사용하면 더욱 다양한 랜덤값을 구할 수 잇다.

public class RandomMain {
    public static void main(String[] args) {
       // Random random = new Random();
        Random random = new Random(1);

        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 randomRange = random.nextInt(10); // 0 ~ 9 범위
        System.out.println("randomRange = " + randomRange);

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

random.nextInt() : 랜덤 int 값을 반환한다.
nextDouble() : 0.0d ~ 1.0d 사이의 랜덤 double 값을 반환한다.
nextBoolean() : 랜덤 boolean 값을 반환한다.
nextInt(int bound) : 0 ~ bound 미만의 숫자를 랜덤으로 반환한다. 예를 들어서 3을 입력하면 0, 1, 2 를 반환한다

씨드
랜덤은 내부에서 씨드값을 사용해서 랜덤 값을 구한다. 그런데 이 씨드 값이 같으면 항상 같은 값 출력

//Random random = new Random();
Random random = new Random(1); //seed가 같으면 Random의 결과가 같다.

new Random : 생성자를 비워두면 내부에서 System.nanoTime()에 여러가지 복합 알고리즘을 섞어서 씨드값 생성
new Random(int seed) : 생성자에 씨드 값을 직접 전달할 수 있다. 씨드 값이 같으면 여러번 반복해도 실행 값 같음. 씨드 값을 직접 사용하면, 결과 값이 항상 같음. 결과가 달라지는 랜덤 값을 구할 수 없다. 하지만 결과가 고정되기 때문에 테스트 코드 같은 곳에서 검증.

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글