회고록 블로그

[공부 필기] Java 기본 공부하기 (28) 본문

2. 프로그래밍 언어 공부/Java

[공부 필기] Java 기본 공부하기 (28)

김간장 2021. 12. 9. 23:10

공부 중인 강의 : 윤성우 선생님, 윤성우의 열혈 Java 프로그래밍 강의.

링크 : https://cafe.naver.com/cstudyjava

 

윤성우의 프로그래밍 스터디그룹 [C/... : 네이버 카페

윤성우의 스터디 공간입니다. C와 JAVA를 공부하시는 분들은 모두 들어오세요. ^^

cafe.naver.com

 

※ 강의 청강 중 필요한 내용만 필기함

※ 틀린 필기가 있을 수 있음..

 


1. Wrapper Class (래퍼 클래스)

- Wrap(감싸다)

- Wrapper Class : 기본 자료형의 값(3, 1.25, 'a' 등)을 인스턴스로 감싸는 것

   → 값(3, 1.25, 'a' 등)을 가지고 있는 인스턴스를 만든다고 생각해도 됨

 

- 왜 값을 감싸는 걸까

   → 제네릭에 가면 공부하게 되는데, 여기에서 간단하게 설명하면 이러함

   → 메소드가 하나 있다고 가정

        * 인자로 [인스턴스]를 전달하는 메소드라고 가정 (정확하게는 인스턴스의 참조값을 전달할 것임)

        * 만약 인자로 전달되는 값이 인스턴스가 아니라 "정수 3, 실수 1.25" 등 이라면?

           인자로 전달될 수가 없음

        * 인자로 [인스턴스]를 전달할 수 있기 때문

        * 그렇다면 이 값들을 '인스턴스화' 시켜야함

 

- 즉, 인스턴스를 전달해야하는데, 부득이하게 기본 자료형의 값을 전달해야하는 상황이라면 Wrapper Class를 사용함

 

- 코드 예시

   → "Hello"라는 문자열(String형)을 인자로 전달받아 출력해주는 메소드 printData

[그림1]

   → 매개변수가 Object형이기 때문에 인스턴스를 받을 수 있음

   → 정수, 실수를 받으려면 인스턴스화 시켜야함 (그림2 참고)

[그림2]

   → 이러면 Integer 인스턴스가 생성되고 그 안에 '3'이라는 값이 저장됨

 

- 이렇게 기본 자료형의 값을 인스턴스로 감싸는 목적의 클래스가 Wrapper Class

 

 

# 주의사항

사실 JDK 버전 9 부터 Integer, Double 클래스를 사용할 때, 생성자는 사용하지 않는다고 한다.

[그림3] Integer 클래스 생성자 설명 (출처 : JDK 9 API Documentation) 

1> Integer 생성자 (매개변수가 int value 일 때)

: Integer num = new Integer(3); 보다는 Integer num = Integer.valueOf(3); 이 훨씬 공간이나 시간적으로 퍼포먼스가 좋다고 한다.

 

2> Integer 생성자 (매개변수가 String s 일 때)

: String형 값(= "3")을 정수형으로 변환한다고 가정할 때

: Integer num = new Integer("3"); 보다는 Integer num = Integer.parseInt("3");이나 Integer.valueOf("3"); 을 사용하라고 한다.

 

또한 String형 값을 인자로 Integer 인스턴스를 생성할 때 사용하는 메소드 "valueOf"와 "parseInt",

이 둘의 차이점을 찾기 위해서 API Documentation 문서를 찾아봤다.

[그림4]

- valueOf : String형 값이 Integer 객체로 반환됨

                    (전달된 문자열의 아스키코드 값이 반환되는게 아니라, 우리가 눈으로 보는 '그 숫자' 그대로 반환됨)

                    인자는 부호 있는 10진수로 해석됨 

[그림5]

- parseInt : 부호가 있는 10진수를 인자로 전달받음

                       입력받은 문자열은 10진수가 되는데, 만약 첫번째 문자에 '-'가 있으면 음의 값(음수)으로 나타내고

                       '+'가 있으면 양의 값(양수)로 나타냄

                       결과로 나온 값은 정수(숫자)로 반환됨 (객체로 반환 아님)

 

그렇다고 한다.

##

 

 

- 래퍼 클래스의 종류는 아래와 같음

   → 기본 자료형 boolean의 래퍼 클래스 : Boolean

   → 기본 자료형 char의 래퍼 클래스 : Character

   → 기본 자료형 byte의 래퍼 클래스 : Byte

   → 기본 자료형 short의 래퍼 클래스 : Short

   → 기본 자료형 int의 래퍼 클래스 : Integer

   → 기본 자료형 long의 래퍼 클래스 : Long

   → 기본 자료형 float의 래퍼 클래스 : Float

   → 기본 자료형 double의 래퍼 클래스 : Double

 

- Float 객체와 Double 객체

   → 생성자 사용을 권장하지 않지만 어쨌든 Float 클래스에는 생성자가 세 가지 있음 (그림6 참고)

   → 매개변수가 double형인 생성자, 매개변수가 float형인 생성자, 매개변수가 String형인 생성자

[그림6]

   → 매개변수가 double형인 생성자가, float형인 생성자가 각각 존재하는 이유는 "전달되는 값이 double로 해석될 수 있기 때문"

        * 만약 인자로 "1.1"을 전달한다고 가정

        * "1.1" 뒤에 'F'를 붙인다면 float형으로 인식되겠지만, 기본적으로 리터럴은 컴파일러에 의해 double형으로 인식이 됨

        * 큰 자료형(double)에서 작은 자료형(float)으로 바뀔 땐 강제 형변환이 필요하기 때문에

           매개변수가 double형인 생성자가 존재하는 것

   → Double 클래스의 생성자는 두 가지 뿐인데, 그 이유 또한 형변환 때문

        * 만약 인자로 "1.1F"가 전달되었다고 하더라도, 작은 자료형(float)은 큰 자료형(double)으로 자동 형변환 가능하기 때문에

           매개변수가 float형인 생성자가 별도로 필요 없는 것

[그림7]

 

- 래퍼 클래스는 두 가지의 기능을 함

   → 첫번째는 Boxing (상자에 담는 것)

        * 기본 자료형의 값을 인스턴스에 담는 것을 말함

// Integer numObj = new Integer(3);과 같은 의미
Integer numObj = Integer.valueOf(3);

   → 두번째는 Unboxing (상자에서 꺼내는 것)

        * 기본 자료형의 값을 인스턴스에서 꺼내는 것을 말함

int number = numObj.intValue();

 

- 한가지 유의할 점은 래퍼 클래스는 Immutable object라는 것

   → 인스턴스를 한번 생성했으면 그 상태를 변경할 수 없음

   → 즉, 값(3, 1.1, 'c' 등)을 넣어서 인스턴스를 만들었다면 그 값을 변경할 수 없음

         * 따라서 값을 변경하고 싶다면 새로운 인스턴스를 생성해야함

- 아래와 같이 num1의 값을 수정하는 코드를 작성할 수는 있지만,

   이건 num1의 값이 [변경]된게 아니라 [새로운 인스턴스]를 가리키게 된 것

   → 만약 num1의 값이 변경되었다면 num2도 변경되었어야함

[그림8]

 

2. 오토 박싱(auto-boxing)과 오토 언박싱(auto-unboxing)

- 오토 박싱/언박싱은 [자동으로] 값이 인스턴스에 넣어지고 꺼내지는 것을 의미함

 

- 인스턴스가 와야할 위치에 값이 오면 오토 박싱이 진행되고

   값이 와야할 위치에 인스턴스가 오면 언박싱이 진행됨

 

- 언제 오토 박싱이 진행될까

   → 대입 연산자를 기준으로 했을 때, 왼쪽에는 참조변수가 오고 오른쪽에는 리터럴이 올 때

         * 참조변수가 온다는 것은 오른쪽의 피연산자에 [인스턴스]가 와야한다는 의미

   → 예시

// Integer num = new Integer(3); 또는 Integer num = Integer.valueOf(3);
// 위와 동일

Integer num = 3;

 

- 언제 오토 언박싱이 진행될까

   → 대입 연산자를 기준으로 했을 때, 왼쪽에는 기본 자료형+변수명이 오고 오른쪽에는 인스턴스가 올 때

         * 원래 언박싱은.. 인스턴스의 intValue 메소드를 이용해서 값(정수)을 리턴 받은 후 리턴 받은 값을 변수에 저장하는 형태였음

   → 예시

// Integer num = Integer.valueOf(3);
// int number = num.intValue();
// 위의 코드와 동일

int number = num;

 

 

# 참고사항

Integer num = Integer.valueOf(3); 과 Integer num = 3;의 차이점?

→ Integer 객체를 대입하느냐, 리터럴을 대입하느냐

 

위에서 정리했듯이 박싱이냐 오토 박싱이냐의 차이이다.

리터럴을 넣어도 컴파일러는 자동으로 Integer.valueOf(3); 로 바꿔준다.

##

 

 

- 오토 박싱과 오토 언박싱이 적용된 놀라운 예시

[그림9]

   → 이 코드가 실행된다는건 굉장히 놀라운 일

   → 사실 '++' 연산자(증감 연산자)는 정수나 실수 등 자료형의 변수가 와야함

   → 하지만 [그림9]에서는 인스턴스 참조변수가 피연산자로 왔음 (심지어 에러 없이 결과가 출력됨)

   → 이는 컴파일러가 오토 박싱과 오토 언박싱을 진행해서 코드를 해석해주었기 때문

...
numObj++; // numObj = new Integer(numObj.intValue() + 1); 과 동일
...

   → 사실 numObj++; 는 아래와 같이 코드가 작성되어야 함

        (1) numObj.intValue()를 처리해서 numObj의 값(정수)을 반환받음 

              * numObj.intValue() // 언박싱

        (2) 반환 받은 값에 1을 더함 (예시에서는 정수 4가 됨)

              * numObj.intValue() + 1

        (3) 정수 4를 인자로 전달하는 Integer 인스턴스를 생성함

              * new Intger(numObj.intValue() +1)

        (4) 참조변수 numObj가 새로 생성된 Intger 인스턴스를 가리키도록 함

              * numObj = new Intger(numObj.intValue() +1) // 박싱

 

- 물론 '++' 연산자(증감 연산자) 뿐만 아니라 복합 대입 연산자(+=, -= 등)도 컴파일러에 의해 오토 박싱과 오토 언박싱이 진행됨

 

 

3. Number 클래스

- 모든 래퍼 클래스의 부모 클래스

- java.lang.Number 클래스에는 추상 메소드들이 있음

[그림10]
[그림11]

- 앞에서 배웠던 내용에 의하면 추상 메소드는 클래스에서 구현(정의)해줘야 한다고 했음

   → 즉, Number 클래스를 상속하는 하위 클래스에서 추상 메소드를 구현해줘야함

   → 구현해주지 않으면 인스턴스 생성이 불가능함

 

- 따라서 래퍼 클래스(Integer, Double 등)에는 Number의 추상 메소드(intValue, longValue, floatValue, doubleValue 등)가 모두 구현되어 있다는 얘기

   → Integer 인스턴스에서 intValue만 사용할 수 있는게 아니라 doubleValue, longValue 등도 사용 가능하다는 이야기

   → 그럼, 프로그래머가 원한다면 Integer 인스턴스에 저장된 값(정수)을 double형으로도 반환할 수도 있다는 것

[그림12]

 

4. BigInteger 클래스 & BigDecimal 클래스

- long형 보다 더 큰 정수를 표현해야할 때 사용하는 클래스 : BigInteger

 

- 아주 작은 오차도 허용하지 않는 연산을 지원하기 위해 디자인된 클래스 : BigDecimal

   → float과 double형은 정밀도의 차이가 있다고 했었고

   → 실수 연산은 오차가 발생할 수 밖에 없다고 했었는데

   → BigDecimal 클래스는 작은 오차도 허용하지 않는 연산(우주공학, 수학 등)을 위해 마련되었음

   → 다만, 실수형(float, double)보다 연산 속도가 늦음

         * 따라서, 정말 작은 오차도 허용할 수 없는 상황일 때 사용하는걸 권장함

         * 실수 연산 시 작은 오차라도 발생하면 굉장히 큰 문제가 발생기는 상황(우주산업 등) 등이 아니라면

            일반적으로 double 타입의 정밀도를 사용해도됨

 

- java.math.BigInteger 클래스

   → Immutable(불변의) 객체임

   → BigInteger 인스턴스 생성 방법

         * 인자를 큰따옴표로 묶어 문자열로 표현한 이유는 '숫자(정수)'로 표현할 수 없기 때문임

         * 만약 큰따옴표를 지우고 컴파일을 하면,

            컴파일러는 "int형(리터럴을 저장하는 기본 단위)으로 표현할 수 있는 범위"를 넘어섰기 때문에 에러를 발생시킴

BigInteger big = new BigInteger("10000000000000000000000000000000");

 

- java.lang.BigDecimal 클래스

   → BigDecimal 인스턴스 생성 방법

         * BigDecimal은 double형으로 표현할 수 있는 범위를 넘어선 것도 아닌데 왜 큰따옴표로 묶었나..

         * 그 이유는 컴파일러에 의해 실수 값이 저장될 때 오차가 포함되기 때문임

         * 만약 큰따옴표를 없애고 인자로 1.1 이라는 실수 값을 전달하면,

            컴파일러는 이 리터럴을 double형으로 받아들임

            그리고 메모리에 double형의 공간만큼 할당해서 1.1이라는 값을 저장해놓음

         * 문제는 double형으로 표현하는 순간 오차가 존재하게 됨

           (실수를 저장할 때 부동소수점 방식으로 표현 후 저장하기 때문)

         * 그렇게 되면 BigDecimal 인스턴스를 생성할 때 [오차가 포함된 실수 값]을 인자로 전달해주게 됨

         * 따라서 오차를 저장하지 않기 위해서 문자열로 저장을 하는 것

BigDecimal bd = new BigDecimal("1.1");

 

Comments