2. 타입(Type)
1. 키워드
- 변수(Variable)와 상수(Constant)
- 리터럴(Literal)
- 고정 소수점(Fixed Point)과 부동 소수점(Floating Point)
- 묵시적 타입 변환(Implicit Conversion)과 명시적 타입 변환(Explicit Conversion)
2. 변수(Variable)
- 변수(Variable)란 데이터를 저장하기 위해 프로그램에 의해 이름을 할당받은 메모리 공간을 의미한다.
- 즉, 변수란 데이터를 저장할 수 있는 메모리 공간을 의미하며, 이렇게 저장된 값은 변경될 수 있다.
1) 변수의 이름 생성 규칙
- 자바에서는 변수뿐만 아니라 클래스, 메서드 등의 이름을 짓는데 반드시 지켜야 하는 공통된 규칙이 있다.
- 자바에서 이름을 생성할 때에 반드시 지켜야 하는 규칙은 다음과 같다.
1] 변수의 이름은 영문자(대소문자), 숫자, _
(언더스코어), $
(달러)로만 구성할 수 있다.
2] 변수의 이름은 숫자로 시작할 수 없다.
3] 변수의 이름 사이에는 공백을 포함할 수 없다.
4] 변수의 이름으로 자바에서 미리 정의된 키워드는 사용할 수 없다.
Note
- 변수의 이름은 해당 변수에 저장될 데이터의 의미를 잘 나타내도록 짓는 것이 좋다.
2) 변수의 종류
- 자바에서 변수는 타입에 따라 크게 다음과 같이 구분할 수 있다.
1] 기본형(Primitive type) 변수
2] 참조형(Reference type) 변수
- 기본형 변수는 실제 연산에 사용되는 변수이다.
- 자바에서는 다음과 같이 8가지 종류의 기본형 변수를 제공하고 있다.
1] 정수형: byte
(8-bit), short
(2-byte), int
(4-byte), long
(8-byte)
2] 실수형: float
(4-byte), double
(8-byte)
3] 문자형: char
(2-byte)
4] 논리형: boolean
(undefined, 1-byte)
- 참조형 변수는 8개의 기본형 변수를 사용하여 사용자가 직접 만들어 사용하는 변수를 의미한다.
3) 변수의 선언
- 자바에서는 변수를 사용하기 전에 반드시 먼저 변수를 선언하고 초기화해야 한다.
- 변수를 선언하는 방법에는 다음과 같이 두 가지 방법이 있다.
1] 변수의 선언만 하는 방법
2] 변수의 선언과 동시에 초기화하는 방법
(1) 변수의 선언만 하는 방법
- 이 방법은 먼저 변수를 선언하여 메모리 공간을 할당받고, 나중에 변수를 초기화하는 방법이다.
- 하지만 이렇게 선언만 된 변수는 초기화되지 않았으므로, 해당 메모리 공간에는 알 수 없는 쓰레깃값만이 들어가 있다.
- 따라서 선언만 된 변수는 반드시 초기화한 후에 사용해야만 한다.
- 자바에서는 프로그램의 안전성을 위해 초기화하지 않은 변수는 사용할 수 없도록 하고 있다.
- 만약 초기화되지 않은 변수를 사용하려고 하면, 자바 컴파일러는 오류를 발생시킬 것이다.
int num; // 변수 선언
System.out.println(num); // 오류 발생
num = 20; // 변수 초기화
System.out.println(num); // 20
- 위의 예제처럼 정수를 저장하기 위한 메모리 공간을 할당받으면, 반드시 해당 타입의 데이터만을 저장해야 한다.
- 만약 다른 타입의 데이터를 저장하려고 하면, 자바 컴파일러는 오류를 발생시킬 것이다.
(2) 변수의 선언과 동시에 초기화하는 방법
- 자바에서는 변수의 선언과 동시에 그 값을 초기화할 수 있다.
- 또한, 선언하고자 하는 변수들의 타입이 같다면 이를 동시에 선언할 수도 있다.
int num1, num2; // 같은 타입의 변수를 동시에 선언
double num3 = 3.14; // 선언과 동시에 초기화
double num4 = 1.23, num5 = 4.56; // 같은 타입의 변수를 동시에 선언하면서 초기화
Note
- 선언하고자 하는 변수의 타입이 서로 다르면 동시에 선언할 수 없다.
- 하지만 다음 예제처럼 여러 변수를 동시에 초기화할 수는 없다.
- 만약 다음 예제처럼 변수의 초기화를 동시에 하려고 하면, 자바 컴파일러는 오류를 발생시킬 것이다.
3. 상수(Constant)
- 상수(Constant)란 변수와 마찬가지로 데이터를 저장할 수 있는 메모리 공간을 의미한다.
- 하지만 상수는 프로그램이 실행되는 동안 메모리에 저장된 데이터를 변경할 수 없다.
- 상수는 선언과 동시에 반드시 초기화해야 한다.
- 자바에서는
final
키워드를 사용하여 상수를 선언한다.
- 자바에서 상수를 만드는 일반적인 방식은 다음과 같다.
- 위의 예제처럼
final
키워드를 사용한 상수는 선언과 함께 반드시 초기화해야 한다.
Note
- 자바에서 상수의 이름은 일반적으로 모두 대문자를 사용하여 선언한다.
- 만약 여러 단어로 이루어진 이름의 경우에는
_
(언더스코어)를 사용하여 구분한다.
1) 리터럴(Literal)
- 리터럴(Literal)이란 값 자체를 의미한다.
- 변수 및 상수와 달리 데이터가 저장된 메모리 공간을 가리키는 이름을 가지고 있지 않다.
2) 타입에 따른 리터럴
- 자바에서 리터럴 타입에 따라 다음과 같이 구분할 수 있다.
1] 정수형 리터럴(Integer Literals)
123
,-456
과 같이 아라비아 숫자와 부호로 직접 표현된다.
2] 실수형 리터럴(Floating-point Literals)
3.14
,-45.6
과 같이 소수 부분을 가지는 아라비아 숫자로 표현된다.
3] 논리형 리터럴(Boolean Literals)
true
나false
로 표현된다.
4] 문자형 리터럴(Character Literals)
'a'
,'Z'
와 같이''
(작은따옴표)로 감싸진 문자로 표현된다.
5] 문자열 리터럴(String Literals)
"자바"
,"홍길동"
과 같이""
(큰따옴표)로 감싸진 문자열로 표현된다.
6] null 리터럴(Null Literals)
- 단 하나의 값인
null
로 표현된다.
Note
null
이란 아무런 값도 가지고 있지 않은 빈 값을 의미한다.
3) 리터럴 타입 접미사(Literal Type Suffix)
- 자바에서
3.14
와 같은 실수형 리터럴을 그대로 사용하면, 해당 리터럴은 실수형 타입 중에서도double
형으로 인식할 것이다. - 하지만 실수형 리터럴 맨 뒤에
F
나f
를 추가하면, 자바는 해당 실수형 리터럴을float
형으로 인식할 것이다. - 이처럼 리터럴 뒤에 추가되어 해당 리터럴의 타입을 명시해 주는 접미사를 리터럴 타입 접미사라고 한다.
- 자바에서 사용할 수 있는 리터럴 타입 접미사는 다음과 같다.
타입 접미사 | 리터럴 타입 | 예제 |
---|---|---|
L 또는 l |
long 형 |
123456789L |
F 또는 f |
float 형 |
1.234567F , 8.9f |
D 또는 d (생략 가능) |
double 형 |
1.2345D , 6.789d |
4. 기본 타입(Primitive Type)
- 데이터 타입(Data Type)은 해당 데이터가 메모리에 어떻게 저장되고, 프로그램에서 어떻게 처리되어야 하는지를 명시적으로 알려주는 역할을 한다.
- 자바에서는 여러 형태의 타입을 미리 정의하여 제공하고 있는데, 이것을 기본 타입이라고 한다.
- 자바의 기본 타입은 크게 정수형, 실수형, 문자형 그리고 논리형 타입으로 나눌 수 있다.
1) 정수형 타입
- 자바에서 정수란 부호를 가지고 있으며, 소수 부분이 없는 수를 의미한다.
- 자바의 기본 타입 중 정수를 나타내는 타입은 다음과 같다.
1] byte
2] short
3] int
4] long
- 다음 표는 각각의 정수형 타입에 따른 메모리의 크기 및 데이터의 표현 범위를 나타낸다.
정수형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 |
---|---|---|
byte |
1바이트 | \(-128\) ~ \(127\) |
short |
2바이트 | \(-2^{15}\) ~ \((2^{15} - 1)\) |
int |
4바이트 | \(-2^{31}\) ~ \((2^{31} - 1)\) |
long |
8바이트 | \(-2^{63}\) ~ \((2^{63} - 1)\) |
- 정수형 데이터의 타입을 결정할 때에는 반드시 자신이 사용하고자 하는 데이터의 최대 크기를 고려해야 한다.
- 해당 타입이 표현할 수 있는 범위를 벗어난 데이터를 저장하면, 오버플로우(Overflow)가 발생해 전혀 다른 값이 저장될 수 있기 때문이다.
- 오버플로우란 해당 타입이 표현할 수 있는 최대 범위보다 큰 수를 저장할 때 발생하는 현상을 가리킨다.
- 오버플로우가 발생하면 최상위 비트(MSB, Most Significant Bit)를 벗어난 데이터가 인접 비트를 덮어쓰므로, 잘못된 결과를 얻을 수 있다.
- 또한, 언더플로우(Underflow)란 해당 타입이 표현할 수 있는 최소 범위보다 작은 수를 저장할 때 발생하는 현상을 가리킨다.
- 다음 예제는 오버플로우나 언더플로우가 발생하면 결과에 어떠한 영향을 주는지를 보여주는 예제이다.
byte num1 = 127;
byte num2 = -128;
num1++; // 127 + 1
num2--; // -128 - 1
System.out.println(num1); // -128
System.out.println(num2); // 127
- 자바에서
byte
타입이 표현할 수 있는 범위는 1바이트이므로-128
부터127
까지이다. - 하지만 위의 예제에서 변수
num1
에127
을 저장한 후1
을 더해128
을 저장하려고 한다. - 이렇게 해당 타입이 표현할 수 있는 최대 범위보다 더 큰 수를 저장하려고 하면, 오버플로우가 발생하여 잘못된 결과가 저장된다.
- 또한, 변수
num2
에는 해당 타입이 표현할 수 있는 최소 범위보다 더 작은 수인-129
를 저장하려고 한다. - 따라서 이때도 언더플로우가 발생하여 잘못된 결과값이 저장된다.
Note
- 자바에서 정수형 타입 중 기본이 되는 타입은
int
형이다. - 따라서 컴퓨터는 내부적으로 정수형 중에서도
int
형의 데이터를 가장 빠르게 처리한다.
2) 실수형 타입
- 자바에서 실수란 소수부나 지수부가 있는 수를 가리키며, 정수보다 훨씬 더 넓은 표현 범위를 가진다.
- 자바의 기본 타입 중 실수를 나타내는 타입은 다음과 같다.
1] float
2] double
- 과거에는 실수를 표현할 때
float
형을 많이 사용했지만, 하드웨어의 발달로 인한 메모리 공간의 증가로 현재에는double
형을 가장 많이 사용한다. - 따라서 실수형 타입 중 기본이 되는 타입은
double
형이다. - 실수형 데이터의 타입을 결정할 때에는 표현 범위 이외에도 반드시 유효 자릿수를 고려해야 한다.
실수형 타입 | 지수의 길이 | 가수의 길이 | 유효 자릿수 |
---|---|---|---|
float |
8비트 | 23비트 | 소수 부분 6자리까지 오차없이 표현 |
double |
11비트 | 52비트 | 소수 부분 15자리까지 오차없이 표현 |
실수형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 | 리터럴 타입 접미사 |
---|---|---|---|
float |
4바이트 | \((3.4 \times 10^{-38})\) ~ \((3.4 \times 10^{38})\) | F 또는 f |
double |
8바이트 | \((1.7 \times 10^{-308})\) ~ \((1.7 \times 10^{308})\) | D 또는 d (생략 가능) |
- 컴퓨터에서 실수를 표현하는 방식은 오차가 발생할 수밖에 없는 태생적 한계를 지닌다.
- 이러한 실수형 데이터의 오차는 자바뿐만 아니라 모든 프로그래밍 언어에서 발생하는 공통된 문제이다.
3) 문자형 타입
- 자바에서 문자형 데이터란 작은 정수나 문자 하나를 표현할 수 있는 타입을 의미한다.
- 자바의 기본 타입 중 문자를 나타내는 타입은 다음과 같다.
1] char
- 컴퓨터는 2진수밖에 인식하지 못하므로 문자도 숫자로 표현해야 인식할 수 있다.
- 따라서 어떤 문자를 어떤 숫자에 대응시킬 것인가에 대한 약속이 필요하다.
- C나 C++의 경우 아스키코드(ASCII)를 사용하여 문자를 표현하는데, 아스키코드는 영문 대소문자를 사용하는 7비트의 문자 인코딩 방식이다.
- 아스키코드는 문자 하나를 7비트로 표현하므로, 총 128개의 문자를 표현할 수 있다.
- 하지만 자바에서는 유니코드(Unicode)를 사용하여 문자를 표현하는데, 아스키코드가 영문자와 숫자밖에 표현하지 못하는 것과 달리 유니코드는 각 나라의 모든 언어를 표현할 수 있다.
- 유니코드는 문자 하나를 16비트로 표현하므로, 총 65,536개의 문자를 표현할 수 있다.
문자형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 |
---|---|---|
char |
2바이트 | \(0\) ~ \(2^{16}\) |
4) 논리형 타입
- 논리형은 참이나 거짓 중 한 가지 값만을 가질 수 있는 불리언 타입을 의미한다.
- 자바의 기본 타입 중 논리형 타입은 다음과 같다.
1] boolean
boolean
형의 기본값은false
이며, 기본 타입 중 가장 작은 크기인 1바이트의 크기를 가진다.
논리형 타입 | 할당되는 메모리의 크기 | 데이터의 표현 범위 |
---|---|---|
boolean |
1바이트 | true 또는 false |
5. 실수의 표현
- 컴퓨터에서 실수를 표현하는 방법은 정수에 비해 훨씬 복잡하다.
- 왜냐하면 컴퓨터에서는 실수를 정수와 마찬가지로 2진수로만 표현해야 하기 때문이다.
- 따라서 실수를 표현하기 위한 다양한 방법들이 연구되었으며, 현재에는 다음과 같은 방식이 사용되고 있다.
1] 고정 소수점(Fixed Point) 방식
2] 부동 소수점(Floating Point) 방식
1) 고정 소수점(Fixed Point) 방식
- 실수는 보통 정수부와 소수부로 나눌 수 있다.
- 따라서 실수를 표현하는 가장 간단한 방식은 소수부의 자릿수를 미리 정하여, 고정된 자릿수의 소수를 표현하는 것이다.
- 32비트 실수를 고정 소수점 방식으로 표현하면 다음과 같다.
- 하지만 이 방식은 정수부와 소수부의 자릿수가 크지 않으므로, 표현할 수 있는 범위가 매우 적다는 단점이 있다.
2) 부동 소수점(Floating Point) 방식
- 실수는 보통 정수부와 소수부로 나누지만, 가수부와 지수부로 나누어 표현할 수도 있다.
- 부동 소수점 방식은 이렇게 하나의 실수를 가수부와 지수부로 나누어 표현하는 방식이다.
- 고정 소수점 방식은 제한된 자릿수로 인해 표현할 수 있는 범위가 매우 작다.
- 하지만 부동 소수점 방식은 다음 수식을 이용하여 매우 큰 실수까지도 표현할 수 있다.
\(\pm(1.가수부) \times 2^{지수부 - 127}\)
- 현재 대부분의 시스템에서는 부동 소수점 방식으로 실수를 표현하고 있다.
(1) IEEE 부동 소수점 방식
- 현재 사용되고 있는 부동 소수점 방식은 대부분 IEEE 754 표준을 따르고 있다.
- 32비트의
float
형 실수를 IEEE 부동 소수점 방식으로 표현하면 다음과 같다.
- 64비트의
double
형 실수를 IEEE 부동 소수점 방식으로 표현하면 다음과 같다.
(2) 부동 소수점 방식의 오차
- 부동 소수점 방식을 사용하면 고정 소수점 방식보다 훨씬 더 많은 범위까지 표현할 수 있다.
- 하지만 부동 소수점 방식에 의한 실수의 표현은 항상 오차가 존재한다는 단점을 가지고 있다.
- 부동 소수점 방식은 \(\pm(1.가수부) \times 2^{지수부 - 127}\)의 공식을 따른다.
- 이 공식을 사용하면 표현할 수 있는 범위는 늘어나지만, 10진수를 정확하게 표현할 수는 없게 된다.
- 따라서 컴퓨터에서 실수를 표현하는 방법은 정확한 표현이 아닌 언제나 근사치를 표현할 뿐임을 항상 명심해야 한다.
- 다음 예제는 부동 소수점 방식으로 실수를 표현할 때 발생할 수 있는 오차를 보여주는 예제이다.
double num = 0.1;
for (int i = 0; i < 1000; i++) {
num += 0.1;
}
System.out.print(num); // 100.09999999999859
- 위의 예제에서
0.1
을1000
번 더한 합계는100
이 되어야 하지만, 실제로는100.09999999999859
가 출력된다. - 이처럼 컴퓨터에서 실수를 가지고 수행하는 모든 연산에서는 언제나 작은 오차가 존재하게 된다.
- 다음 예제는 자바의 실수형 타입인
double
형과float
형이 표현할 수 있는 정밀도를 보여주는 예제이다.
float num3 = 1.23456789f;
double num4 = 1.23456789;
System.out.println("float형 변수 num3 : " + num3);
System.out.println("double형 변수 num4 : " + num4);
// float형 변수 num3: 1.2345679
// double형 변수 num4: 1.23456789
- 위의 예제는
float
형 타입이 소수 6자리까지는 정확하게 표현할 수 있으나, 그 이상은 정확하게 표현하지 못함을 보여준다. - 자바의
double
형 타입은 소수 부분 15자리까지 오차없이 표현할 수 있다. - 하지만 그 이상의 소수 부분을 표현할 때는 언제나 작은 오차가 발생하게 된다.
6. 타입 변환(Type Conversion)
- 하나의 타입을 다른 타입으로 바꾸는 것을 타입 변환이라고 한다.
- 자바에서는
boolean
형을 제외한 나머지 기본 타입 간의 타입 변환을 자유롭게 수행할 수 있다. - 자바에서 다른 타입끼리의 연산은 우선 피연산자들을 모두 같은 타입으로 만든 후에 수행된다.
- 메모리에 할당받은 바이트의 크기가 상대적으로 작은 타입에서 큰 타입으로의 타입 변환은 생략할 수 있다.
- 하지만 메모리에 할당받은 바이트의 크기가 큰 타입에서 작은 타입으로의 타입 변환은 데이터의 손실이 발생한다.
- 따라서 상대적으로 바이트의 크기가 작은 타입으로 타입 변환을 할 경우 자바 컴파일러는 오류를 발생시킨다.
1) 타입 변환의 종류
- 자바에서 타입 변환은 크게 다음과 같이 두 가지 방식으로 나뉜다.
1] 묵시적 타입 변환(자동 타입 변환)
2] 명시적 타입 변환(강제 타입 변환)
(1) 묵시적 타입 변환(자동 타입 변환, Implicit Conversion)
- 묵시적 타입 변환이란 대입 연산이나 산술 연산에서 컴파일러가 자동으로 수행해 주는 타입 변환을 가리킨다.
- 자바에서는 데이터의 손실이 발생하지 않거나, 데이터의 손실이 최소화되는 방향으로 묵시적 타입 변환을 진행한다.
- 또한, 자바에서는 데이터의 손실이 발생하는 대입 연산은 허용하지 않는다.
- 다음 예제는 대입 연산과 산술 연산에서 일어나는 묵시적 타입 변환을 보여준다.
① double num1 = 10;
② // int num2 = 3.14;
③ double num3 = 7.0f + 3.14;
System.out.println(num1); // 10.0
System.out.println(num3); // 10.14
- 위 예제의 ①번 라인에서는
double
형 변수에int
형 데이터를 대입하므로,int
형 데이터가double
형으로 자동으로 타입 변환된다. - 하지만 주석 처리된 ②번 라인에서는
int
형 변수가 표현할 수 있는 범위보다 더 큰double
형 데이터를 대입하므로, 데이터의 손실이 발생한다. - 따라서 이 대입 연산에 대해서는 자바 컴파일러가 오류를 발생시킬 것이다.
- ③번 라인에서는
float
형 데이터와double
형 데이터의 산술 연산을 수행한다. - 이때에도 데이터의 손실이 최소화되도록
float
형 데이터가double
형으로 자동 타입 변환된다. - 이렇게 자바 컴파일러가 자동으로 수행하는 타입 변환은 언제나 데이터의 손실이 최소화되는 방향으로 이루어진다.
- 따라서 자바에서는 타입의 표현 범위에 따라 다음과 같은 방향으로 자동 타입 변환이 이루어진다.
byte
형 → short
형/char
형 → int
형 → long
형 → float
형 → double
형
① byte num1 = 100; // OK
② byte num2 = 200; // Type mismatch
③ float num3 = 3.14; // Type mismatch
④ int num4 = 9876543210; // Out of range
⑤ long num5 = 9876543210; // Out of range
- 위 예제의 ①번 라인에서는
byte
형 변수에 표현 범위가 더 큰int
형 데이터를 대입하고 있다. - 하지만 정수
100
은byte
형 변수가 표현할 수 있는 범위 내에 있으므로, 자동으로 타입 변환이 이루어져 대입된다. - 그러나 ②번 라인처럼
byte
형 변수가 표현할 수 있는 범위를 벗어난int
형 데이터는 타입 변환되지 못하고, 오류를 발생시킬 것이다. - ③번 라인도 마찬가지로
float
형 변수가 표현할 수 있는 범위를 벗어난double
형 데이터를 대입하므로, 오류를 발생시킬 것이다. - ④ 라인과 ⑤번 라인에서는 모두
Out of range
오류가 발생할 것이다. - 이것은
int
형 리터럴이 표현할 수 있는 최대 범위인2,147,483,647
을 초과하는 정수형 리터럴을 사용했기 때문이다.
- 이렇게
int
형 리터럴보다 더 큰 정수를 사용하기 위해서는 다음 예제처럼 리터럴의 마지막에L
이나l
접미사를 추가하여long
형 리터럴로 명시해야만 한다.
- 하지만 ⑥번 라인에서는
int
형 변수가 표현할 수 있는 범위를 벗어난long
형 데이터를 대입하므로, 오류를 발생시킬 것이다.
(2) 명시적 타입 변환(강제 타입 변환, Explicit Conversion)
- 명시적 타입 변환이란 사용자가 타입 캐스트 연산자를 사용하여 강제적으로 수행하는 타입 변환을 가리킨다.
- 자바에서는 다음과 같이 명시적 타입 변환을 수행할 수 있다.
- 이때의
()
(괄호)를 타입 캐스트(Type Cast) 연산자라고 한다.
- 다음 예제는 명시적 타입 변환을 보여주는 예제이다.
int num1 = 1, num2 = 4;
① double result1 = num1 / num2;
② double result2 = (double) num1 / num2;
System.out.println(result1); // 0.0
System.out.println(result2); // 0.25
- 위 예제의 ①번 라인에서 나눗셈 연산의 결과로는
0
이 반환된다. - 자바에서 산술 연산을 수행하고 얻는 결괏값의 타입은 언제나 피연산자의 타입과 일치해야 한다.
- 즉,
int
형 데이터끼리의 산술 연산에 대한 결괏값은 언제나int
형 데이터의 결과로 나오게 된다. - 따라서
1
나누기4
의 결과로는0.25
가 반환되지만,int
형으로 자동 타입 변환되어0
이 반환된다. - 그리고
double
형 변수에 그 결과가 대입될 때,double
형으로 자동 타입 변환되어0.0
이라는 결과가 출력된다. - 따라서 정확한 결과를 얻고자 한다면 ②번 라인처럼 피연산자 중 하나의 타입을
double
형으로 강제 타입 변환해야 한다. - 이렇게 피연산자 중 하나의 타입이
double
형이 되면, 나눗셈 연산을 위해 나머지 하나의 피연산자도double
형으로 자동 타입 변환된다. - 따라서 그 결과 또한
double
형인0.25
가 될 것이며, 이 결과가double
형 변수에 제대로 대입될 것이다.