Chapter 02 - 변수와 타입

김태원·2023년 1월 9일
0

정수 타입

자바는 정수, 실수, 논리값을 저장할 수 있는 기본(primitive) 타입 8개를 다음과 같이 제공한다.

값의 분류기본타입
정수byte, char, short, int, long
실수float, double
논리(true/false)boolean

정수 타입은 총 5개로, 다음과 같이 메모리 할당 크기와 저장되는 값의 범위를 가지고 있다.

타입메모리크기저장되는 값의 허용 범위
byte1byte-128 ~ 127
short2byte-32,768 ~ 32,767
char2byte0 ~ 65,535(유니코드)
int4byte-2,147,483,648 ~ 2,147,483,647
long8byte-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

C언어와 비교해보자.

타입메모리크기저장되는 값의 허용 범위
char1byte-128 ~ 127
short2byte-32,768 ~ 32,767
int4byte-2,147,483,648 ~ 2,147,483,647
long4byte-2,147,483,648 ~ 2,147,483,647
long long8byte-9,223,372,036,854,775,808 ~9,223,372,036,854,775,807

C언어와 비교해보면 short, int는 동일하며 자바의 long은 C언어의 long long과 동일하다.

또한 자바와 C언어 모두 문자를 저장하기 위한 char 자료형이 존재한다는 공통점이 있다.
여기서 주목할 점은 두 언어에서 char 자료형의 값의 허용 범위(크기)가 다르다.

두 언어 모두 문자를 숫자로 변환해서 저장하는데, 변환하는 방식에서의 차이가 존재한다.
C언어에서는 char 자료형에 저장할 문자를 ASCII 코드로 저장하는 반면에 자바에서는 유니코드로 저장한다.

따라서 동일한 목적의 자료형임에도 불구하고 값의 허용 범위(크기)가 다른 것이다.


리터럴의 정의

코드에서 프로그래머가 직접 입력한 값을 리터럴(Literal)이라고 한다.

자바에서 정수 리터럴은 int, 실수 리터럴은 double로 간주한다.

자바에서 변수에 대입할 정수 리터럴은 진수에 따라 작성하는 방법이 다르다.

2진수 : 0b 또는 0B로 시작하고 0과 1로 작성

int x = 0b1011; // 10진수 값 = 11

8진수 : 0으로 시작하고 0~7 숫자로 작성

int x = 013; // 10진수 값 = 11

10진수 : 소수점이 없는 0~9 숫자로 작성

int x = 11;

16진수 : 0x 또는 0X로 시작하고 숫자나 A~F 또는 a~f로 작성

int x = 0xB3; // 10진수 값 = 179

문자 리터럴

하나의 문자를 작은 따옴표(')로 감싼 것을 문자 리터럴이라고 한다.

문자 리터럴은 유니코드로 변환되어 저장되는데, 유니코드는 세계 각국의 문자를 0~65535 숫자로 매핑한 국제 표준 규약이다. 자바는 이러한 유니코드를 저장할 수 있도록 char 타입을 제공한다.

char var1 = 'A'; // 'A' 문자와 매핑되는 숫자 65로 대입
char var2 = '가'; // '가' 문자와 매핑되는 숫자 44032로 대입

주의할 점은 char 타입의 변수에 어떤 문자도 대입하지 않고 단순히 초기화를 할 목적으로 다음과 같이 작은 따옴표(') 두개를 연달아 붙인 빈 문자열을 대입하면 컴파일 에러가 발생한다.

char c = ''; // 컴파일 에러 발생

이 경우에는 다음과 같이 공백(유니코드 32) 하나를 포함해서 초기화해야한다.

char c = ' '; // 공백 하나를 포함해서 초기화

실수 타입

실수 타입에는 float과 double이 있으며 다음과 같이 메모리 할당 크기와 저장되는 값의 범위를 가지고 있다.

타입메모리크기저장되는 값의 허용 범위(양수 기준)유효 소수 이하 자리
float4byte1.4 x 10-45 ~ 3.4 x 10387자리
double8byte4.9 x 10-324 ~ 1.8 x 1030815자리

자바는 IEEE 754 표준에 근거해서 float 타입과 double 타입의 값을 부동 소수점(floating-point) 방식으로 메모리에 저장한다.

위 링크에서 설명하는 것처럼 부동 소수점 방식은 고정 소수점 방식보다 표현할 수 있는 값의 범위가 넓지만, 정밀도의 문제가 있다.
따라서 실수를 부동 소수점으로 표현하더라도 오차가 존재하는 것을 유의해야 하며, 컴퓨터에서의 실수 표현은 근사값을 표현하는 것으로 이해해야 한다.

다음 예제를 실행하면 부동 소수점 방식으로 실수를 표현했을 때, 발생할 수 있는 오차를 발견할 수 있다.

double value1 = 12.23;
double value2 = 34.45;

// 46.68 ???
System.out.println(value1 + value2);

12.23와 34.45을 더했으니 결과로 46.68을 예상했겠지만, 실제로는 46.68000000000001가 출력된다.
이와 같이 실수 연산에서는 소수점 단위 값을 정확히 표현하는 것이 아니라 근사값으로 처리하기 때문에 오차가 발생할 수 있다.

근사한 차이지만, 금융과 관련된 서비스에서는 이 오차가 큰 영향을 미칠 수 있기 때문에 주의해야 한다.
그러면 이 문제를 어떻게 해결할 수 있을까?


BigDecimal

이러한 부동 소수점 표현 방식의 오차를 해결하기 위해 자바에서는 BigDecimal 클래스를 제공하고 있다. 소수점을 다루는 연산을 한다면 BigDecimal 클래스의 사용은 필수적이다.

BigDecimal 클래스는 java.math 패키지 안에 포함되어 있다.
보통 생성자와 파라미터로 문자열을 넘겨 생성하는 것이 기본적이지만, 정적 팩토리 메서드도 제공한다.
생성자를 사용할 때 주의할 점은, 문자열이 아닌 double 타입 값을 넘기면 안 된다.

// 생성자 + 문자열로 초기화하는 방법
BigDecimal value1 = new BigDecimal("12.23");

// double 타입으로 초기화하는 방법
// 내부적으로 생성자 + 문자열을 사용한다.
BigDecimal value2 = BigDecimal.valueOf(34.45);

// 아래와 같이 사용하면 안 된다.
// 12.230000000000000426325641456060111522674560546875
BigDecimal dontDoThis = new BigDecimal(12.23);

아래는 BigDecimal 클래스를 활용한 사칙연산 예제이다.

BigDecimal value1 = new BigDecimal("12.34");
BigDecimal value2 = new BigDecimal("1");
BigDecimal value3 = new BigDecimal("10");

// 13.34
System.out.println(value1.add(value2));

// 2.34
System.out.println(value1.subtract(value3));

// 123.40
System.out.println(value1.multiply(value3));

divide 메서드를 사용하면 나눗셈 연산이 가능하다. 하지만 정확하게 나누어 몫이 떨어지지 않는 수의 경우 ArithmeticException 예외를 던진다. 아래 예제를 확인해보자.

BigDecimal value1 = new BigDecimal("11");
BigDecimal value2 = new BigDecimal("3");

// Exception in thread "main" java.lang.ArithmeticException:
// Non-terminating decimal expansion; no exact representable decimal result.
System.out.println(value1.divide(value2));

BigDecimal 클래스는 나누어떨어지지 않는 수를 표현할 수 없다.
따라서 divide 메서드를 사용할 때는 소수점 몇 번째짜리까지, 어떻게 처리할 것인지 지정을 해줘야 한다.

아래 예제에서 2번째 파라미터는 N 번째 자리까지 표현할 것인가를 뜻하고, 3번째 파라미터는 처리 방식이다.
즉, 아래 예제의 경우 소수점 3번째 자리에서 반올림하여 2번째 자리까지 표기한다.

BigDecimal value1 = new BigDecimal("11");
BigDecimal value2 = BigDecimal.valueOf(3);

// 3.67
value1.divide(value2, 2, RoundingMode.HALF_UP);

RoundingMode는 소수점을 처리하는 방식을 의미한다.
자바에서는 BigDecimal 클래스에 다양한 소수점 처리 방식을 제공하고 있다. 몇 가지 예시로 살펴보면 아래와 같다.

// 소수점 첫 번째까지 표현, 두번째 자리에서 반올림
// 12.4
BigDecimal.valueOf(12.35).setScale(1, RoundingMode.HALF_UP);

// 소수점 이하 모두 제거하고 올림
// 13
BigDecimal.valueOf(12.34).setScale(0, RoundingMode.CEILING);

// 음수인 경우는 특정 자릿수 이하 제거
// -12.3
BigDecimal.valueOf(-12.34).setScale(1, RoundingMode.CEILING);

// 특정 자릿수 이하 버림
// 12.3
BigDecimal.valueOf(12.37).setScale(1, RoundingMode.FLOOR);

소수점 표현의 오차는 데이터베이스에서도 이어진다.
MySQL에서는 실수의 값을 정확하게 표현하기 위해 Decimal이라는 타입을 제공한다.

아래와 같이 지정할 수 있는데, 소수부를 포함한 전체 자릿수는 10이고, 소수부의 자릿수는 2자리를 뜻한다.
지정하지 않는 경우 전체 10자리, 소수부가 없는 0으로 지정된다.
참고로 전체 자릿수의 최대값은 65이다.

CREATE TABLE `myTable` (
  `decimal` DECIMAL(10, 2) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

문자열 타입

작은 따옴표(')로 감싼 한개의 문자는 char 타입이지만, 큰 따옴표(")로 감싼 여러 개의 문자열은 유니코드로 변환되지 않는다.

따라서 아래의 예제는 잘못된 코드이다.

char var1 = "A"; // 컴파일 에러
char var2 = "홍길동"; // 컴파일 에러

큰 다옴표(")로 감싼 문자들을 문자열이라고 부르는데, 문자열을 변수에 저장하고 싶다면 다음과 같이 String 타입을 사용해야 한다.

String var1 = "A";
String var2 = "홍길동";

연산식에서 자동 타입 변환

자바는 실행 성능을 향상시키기 위해 컴파일 단계에서 연산을 수행한다. 이때 정수 타입 변수가 산술 연산식에서 피연산자로 사용되면 자동 타입 변환이 이루어진다.

int 타입보다 작은 byte, short 타입의 변수는 int 타입으로 자동 타입 변환되어 연산을 수행한다.

만약 byte 타입 변수가 피연산자로 사용된다면

byte x = 10;
byte y = 20;
// 컴파일 에러 발생
// byte result = x + y;
int result = x + y;

변수값은 int 타입으로 변환되어 연산되고, 결과도 int 타입으로 생성된다.
따라서 결과값을 byte 변수에 저장할 수 없고, int 변수에 저장해야 한다.

자바에서는 서로 다른 타입의 정수 연산을 수행할 때 크기가 더 큰 타입으로 자동 형 변환된다


자바의 기본 타입 8개

자바의 기본 타입은 byte, short, int, long, float, double, boolean 이다.

자바는 기본 타입 값이 동일한지 비교할 때는 ==를 사용하고, String 값이 동일한지 비교할 때에는 equals()를 사용한다.

profile
개발이 재밌어서 하는 Junior Backend Developer

0개의 댓글