프로그래밍언어에서 변수는 값을 저장할 수 있는 메모리상의 공간을 의미한다.
이 메모리 공간에 저장된 값은 변경될 수 있기 때문에 '변수'라는 용어가 붙여졌다.
### 단 하나의 값을 저장할 수 있는 메모리 공간 ###
변수를 사용하려면 변수를 선언해야 한다.
int x; // int는 x라는 변수의 타입 x는 변수의 이름이다.
// x라는 이름의 int타입 변수를 선언했다는 뜻
위의 섹션(변수의 선언과 초기화)에서 '타입'이라는 용어가 등장한다.
'타입'이란 값의 종류를 말하며 변수에 타입을 지정해줌으로써 값이 저장될 공간의 크기와 저장형식을 선언할 수 있다.
이러한 값이 저장될 공간의 크기와 저장형식을 정의한 것을 '자료형'이라고 한다.
자료형은 크게 '기본형'과 '참조형' 두 가지로 나눌 수 있다.
'기본형'변수는 실제 값을 저장하고, '참조형'변수는 어떤 값이 저장되어 있는 주소(memory address)를 값으로 갖는다.
자바는 '참조형' 변수 간의 연산을 할 수 없으므로 실제 연산에 사용되는 것은 모두 '기본형' 변수이다.
기본형(primitive type)
- 논리형(boolean), 문자형(char), 정수형(byte, short, int, long), 실수형(float, double)
- 계산을 위해 실제 값을 저장한다. (총 8개)
참조형(reference type)
- 객체의 주소를 저장한다. 8개의 기본형을 제외한 나머지 타입
참조형 변수를 선언할 때는 변수의 타입으로 클래스의 이름을 사용하므로 클래스의 이름이 참조변수의 타입이 된다.
새로운 클래스를 작성한다는 것은 새로운 참조형을 추가하는 것이다.
기본형에는 모두 8개의 타입이 있다. 논리형, 문자형, 정수형, 실수형으로 구분된다.
논리형(boolean) - true와 false중 하나를 값으로 가진다. 조건식과 논리적 계산에 사용
문자형(char) - 문자를 저장하는데 사용, 변수에 하나의 문자만 저장할 수 있다.
정수형(byte, short, int, long) - 정수를 저장하는데 사용, 주로 int가 사용되며 byte는 이진 데이터를 다룰 때 사용
실수형(float, double) - 실수를 저장하는데 사용, 주로 double을 사용
'상수(constant)'는 변수와 마찬가지로 '값을 저장할 수 있는 공간'이지만, 변수와 달리 한번 값을 저장하면 다른 값으로 변경할 수 없다.
상수를 선언하는 방법은 변수를 선언하는 방법에서 'final'키워르를 붙여주기만 하면 된다.
final int MAX_SPEED = 10; //상수 선언, 초기화
상수는 선언과 동시에 초기화 해야한다. 그 후 부터는 상수의 값을 변경하는 것이 허용되지 않는다.
상수의 이름은 모두 대문자로 하는 것이 암묵적인 관례이며, 여러 단어로 이루어져있는 경우 '_'로 구분한다.
상수는 값에 '의미있는 이름'을 붙여서 코드의 이해와 수정을 쉽게 만들어 준다.
논리형은 'boolean' 한가지 밖에 없다. boolean타입의 변수에는 true와 false중 하나를 저장할 수 있으며 기본값은 false이다.
boolean형은 두 가지의 값만을 표현하면 되므로 1bit만으로도 충분하지만, 자바에서는 데이터를 다루는 최소단위가 byte이기 때문에, boolean의 크기는 1byte이다.
boolean checked = true; //checked라는 boolean타입의 변수를 true값으로 초기화 했다.
문자형도 'char'한 가지 타입 밖에 없다. 문자를 저장하기 위한 변수를 선언할때 사용된다.
char타입의 변수는 단 하나의 문자만을 저장할 수 있다.
char c = 'A'; // 문자 'A'를 c변수에 저장했다.
위 예시에서 변수에 'A'라는 '문자'가 저장되는 것 같지만, 사실은 문자가 아닌 '문자의 유니코드(정수)'가 저장된다.
컴퓨터는 숫자밖에 모르기 때문에 모든 데이터를 숫자로 변환하여 저장하는 것이다.
문자 'A'의 유니코드는 65이므로, 변수 c에는 65가 저장된다.
그래서 문자 대신 직접 유니코드를 저장할 수도 있다.
char c = 'A'; // 문자로 저장
char c = 65; // 유니코드로 저장 65는 문자로 'A'
만약 어떤 문자의 유니코드를 알고 싶으면, char형 변수에 저장된 값을 정수형(int)로 변환하면 된다.
int code = (int)c; // code변수에 65가 저장
컴퓨터는 숫자밖에 모르기 때문에 모든 데이터를 숫자로 변환하여 저장한다고 했다.
따라서 char타입의 변수에 문자를 저장하면 숫자로 변환하여 저장되는 것이다.
다음 처럼 char타입의 변수와 int타입의 변수에 각각 'A'와 65를 저장한다.
char c = 'A';
int x = 65;
그럼 컴퓨터에는 둘 다 2진수로 똑같은 값이 저장된다.
이 후 두 값을 출력해본다.
System.out.println(c); // A가 출력된다.
System.out.println(x); // 65가 출력된다.
서로 다르게 출력되는 이유는 '타입'때문이다. 타입이 문자형이면 저장된 숫자에 해당하는 유니코드 문자를 출력하고, 정수형 타입이면 변수에 저장된 값을 10진수로 해석하여 출력하기 때문에 서로의 값이 다르게 출력된다.
정수형에는 모두 4개의 자료형이 있다. 각 자료형이 저장할 수 있는 값의 범위가 서로 다르다.
byte단위로 크기를 나열하면 다음과 같다.
byte(1 byte) < short(2 byte) < int(4 byte) < long(8 byte)
크기에 따라 표현할 수 있는 범위가 있다.
이러한 범위에 따라 4개의 정수형 중에서 하나를 선택하면 되겠지만, byte와 short보다 int를 사용하는 것을 권장한다. 메모리를 절약할 수는 있겠지만, 값의 범위가 작아 연산 시에 범위를 넘어서 잘못된 결과를 얻기 쉽기 때문이다.
그리고 JVM의 피연산자 스택(operand stack)이 피연산자를 4byte단위로 저장하기 때문에 크기가 4byte보다 작은 자료형(byte, short)의 값을 계산할 때는 4byte로 변환하여 연산이 수행된다. 그래서 오히려 int를 사용하는 것이 더 효율적이다.
결론적으로 정수형을 선택할 때에는, 기본을 int타입으로 하고 int타입을 넘어설 경우 long타입을 사용하면 된다.
원래 2진수의 최대값인 '1111'에 1을 더하면 '10000'이 되지만, 크기가 4bit라면 4자리의 2진수만 저장할 수 있기 때문에 '0000'이 된다. 즉, 5자리의 2진수 '10000'중에서 하위 4bit만 저장하게 되는 것이다.
이처럼 연산과정에서 해당 타입이 표현할 수 있는 값의 범위를 넘어서는 것을 오버플로우(overflow)라고 한다.
실수형은 실수를 저장하기 위한 타입으로 float와 double 두 가지가 있으며 각 타입의 변수에 저장할 수 있는 값의 범위는 아래와 같다.
float: 1.4 * 10e-45 ~ 3.4 * 10e38
double: 4.9 * 10e-324 ~ 1.8 * 10e308
실수형도 표현범위를 벗어나게 되면 오버플로우가 발생하는데, 실수형은 오버플로우가 발생하게 되면 변수의 값이 무한대가 된다.
그리고 실수형에는 언더플로우(underflow)가 있는데, 언더플로우는 실수형으로 표현할 수 없는 아주 작은 값, 즉 양의 최소값보다 작은 값이 되는 경우를 말하는데 이 때 변수의 값은 0이 된다.
실수형은 정수형과 달리 오차가 발생할 수 있다.
그래서 실수형에는 값의 범위 뿐만 아니라 정밀도도 매우 중요한 요소다.
float타입의 정밀도는 7자리인데, '7자리의 10진수를 오차없이 저장할 수 있다.'는 뜻이다.
double의 정밀도는 15자리이다.
실수형 타입에서 float타입보다 double타입을 권장하는데, 이를 권장하는 이유도 값의 범위보다는 정밀도 때문이다.
실수 중에는 '파이'와 같은 무한소수가 존재하기 때문에 오차가 발생할 수 있다.
게다가 10진수가 아닌 2진수로 저장하기 때문에 10진수로는 유한소수이더라도, 2진수로 변환하면 무한소수가 되는 경우도 있다.
2진수로는 10진 소수를 정학이 표현하기 어렵기 때문이다.
프로그램을 작성하다보면 같은 타입뿐만 아니라 서로 다른타입간의 연산을 수행해야 하는 경우도 있다.
이럴때는 연산을 수행하기 전에 타입을 일치시켜야 하는데, 값의 타입을 다른 타입으로 변환하는 것을 '형변환'이라고 한다.
형변환이란, 변수 또는 상수의 타입을 다른 타입으로 변환하는 것
형변환하고자 하는 변수나 리터럴의 앞에 변환하고자 하는 타입을 괄호와 함께 붙여주기만 하면 된다.
double d = 85.4
int i = (int)d; // double타입 변수를 int타입으로 형변환
기본형에서는 boolean타입을 제외한 나머지 타입들은 서로 형변환이 가능하다.
참조형과 기본형의 형변환은 불가능하다.
서로 다른 타입간의 대입이나 연산을 할 때, 형변환으로 타입을 일치시키는 것이 원칙이지만, 경우에 따라 편의상의 이유로 형변환을 생략할 수 있다. 컴파일러가 생략된 형변환을 자동적으로 추가해주기 때문이다.
float f = 1234; //형변환의 생략 float f = (float)1234;와 같다.
위의 예시에서 우변은 int타입의 상수이고, 변수의 타입은 float타입이다.
서로 타입이 달라 형변환이 필요하지만 형변환을 생략하였고 자동적으로 형변환이 되었다.
그러나 다음과 같이 변수가 저장할 수 있는 값의 범위보다 더 큰 값을 저장하려는 경우 형변환을 생략하면 에러가 발생한다.
byte b = 1000; //byte의 범위 -128~127을 넘는 값을 저장
형변환을 생략하면 컴파일러가 자동으로 형변환을 해주는데, 컴파일러는 아래의 기준으로 타입을 일치시킨다.
기존의 값을 최대한 보존할 수 있는 타입으로 자동 형변환 한다.
표현범위가 좁은 타입에서 넓은 타입으로 형변환 하는 경우에는 값 손실이 없으므로 두 타입 중에서 표현범위가 더 넓은 쪽으로 형변환된다.
byte -> short, char -> int -> long -> float -> double
보통 자료형의 크기가 큰 것일수록 값의 표현범위가 크지만, 실수형은 정수형과 값을 표현하는 방식이 다르기 때문에 같은 크기일지라도 실수형이 정수형보다 훨씬 더 큰 표현 범위를 갖기 때문에 float과 duble이 같은 크기인 int와 long보다 오른쪽에 위치한다.