2.1 변수 선언
컴퓨터 메모리(RAM)는 수많은 번지들로 구성된 데이터 저장 공간이다. 프로그램은 데이터를 메모리에 저장하고 읽는 작업을 빈번히 수행한다. 이때 데이터를 어디에, 어떤 방식으로 저장할지 정해져 있지 않다면 메모리 관리가 무천 어려워진다. 프로그래밍 언어는 이 문제를 해결하기 위해 변수를 사용한다.
변수는 하나의 값을 저장할 수 있는 메모리 번지에 붙여진 이름이다. 변수를 통해 프로그램은 메모리 번지에 값을 저장하고 읽을 수 있다.
변수 = 하나의 값을 저장할 수 있는 메모리 번지에 붙여진 이름
자바의 변수는 다양한 타입의 값을 저장할 수 있다. 즉, 정수형 변수에는 정수값만 저장할 수 있고, 실수형 변수에는 실수값만 저장할 수 있다. 변수를 사용하려면 변수 선언이 필요한데, 변수 선언은 어떤 타입의 데이터를 저장할 것인지 그리고 변수 이름이 무엇인지를 결정하는 것이다. 변수 이름은 첫 번째 글자가 문자여야 하고, 중간부터는 문자, 숫자, $, _를 포함할 수 있다. 또한, 첫 문자를 소문자로 시작하되 캐멀 스타일로 작성하는 것이 관례이다.
변수가 선언되었다면 값을 저장할 수 있는데, 이때 대입 연산자인 =를 사용한다. 수학에서 등호는 '같다'라는 의미이지만, 자바에서는 우측 값을 좌측 변수에 대입하는 연산자로 사용된다.
변수 선언은 저장되는 값의 타입과 이름만 결정한 것이지, 아직 메모리에 할당된 것은 아니다. 변수에 최초로 값이 대입될 때 메모리에 할당되고, 해당 메모리에 값이 저장된다.
변수에 최초로 값을 대입하는 행위를 변수 초기화라고 하고, 이때의 값을 초기값이라고 한다. 초기값은 다음과 같이 변수를 선언함과 동시에 대입할 수도 있다.
int score = 90;
초기화되지 않은 변수는 아직 메모리에 할당되지 않았기 때문에 변수를 통해 메모리 값을 읽을 수 없다. 따라서 다음은 잘못된 코딩이다.
int value;
int result = value + 10;
1행에서 변수 value가 선언되었지만, 쵝화되지 않았기 때문에 2행의 value + 10 에서 value 변수 값은 읽어올 수 없다. 따라서 위 코드는 다음과 같이 변경해야 한다.
int value = 30;
int result = value = 10;
변수는 출력문이나 연산식에 사용되어 변수값을 활용한다. 다음 예제는 변수를 문자열과 결합 후 출력하거나 연산식에서 활용하는 모습을 보여준다.
package ch02.sec01;
public class VariableUseExample {
public static void main(String[] args) {
int hour = 3;
int minute = 5;
System.out.println(hour + "시간" + minute + "분");
int totalMinute = (hour*60) + minute;
System.out.println("총 " + totalMinute + "분");
}
}
변수는 또 다른 변수에 대입되어 메모리 간에 값을 복사할 수 있다. 다음 코드는 변수 x값을 변수 y값으로 복사한다.
int x = 10;
int y = x;
다음 예제는 두 변수의 값을 교환하는 방법을 보여준다. 두 변수의 값을 교환하기 위해서 새로운 변수 temp를 선언한 것에 주목하길 바란다.
package ch02.sec01;
public class VariableExchangeExample {
public static void main(String[] args) {
int x = 3;
int y = 5;
System.out.println("x: "+ x + ", y: " + y);
int temp = x;
x = y;
y = temp;
System.out.println("x: " + x + ", y" + y);
}
}
2.2 정수 타입
변수는 선언될 때의 타입에 따라 저장할 수 있는 값의 종류와 허용 범위가 달라진다. 자바는 정수, 실수, 논리값을 저장할 수 있는 기본 타입 8개를 다음과 같이 제공한다.
값의 분류 | 기본 타입 |
정수 | byte, char, short, int, long |
실수 | float, double |
논리(true/ false) | boolean |
정수 타입은 총 5개로, 다음과 같이 메모리 할당 크기와 저장되는 값의 범위를 가지고 있다.
타입 | 메모리 크기 | |
byte | 1byte | 8bit |
short | 2byte | 16bit |
char | 2byte | 16bit |
int | 4byte | 32bit |
long | 8byte | 64bit |
각 타입에 저장되는 값의 허용 범위를 모두 외울 필요는 없지만, 메모리 할당 크기는 알고 있는 것이 좋다.
메모리 크기를 n이라고 할 때 정수 타입은 다음과 같은 동일한 구조의 2진수로 저장된다. byte, short, int, long은 모두 부호 있는 정수 타입이므로 최상위 bit는 부호 bit로 사용되고, 나머지 bit는 값의 범위를 결정한다. 예를 들어 byte 타입은 최상위 bit를 부호 비트로 사용하고 나머지 7bit로 값의 범위를 결정한다. 최상위 bit가 1인 음수의 경우 나머지 7개의 bit를 모두 1의 보수(1은 0. 0은 1)로 바꾸고 1을 더한 값에 -를 붙이면 10진수가 된다. 예를 들어 -2는 다음과 같이 계산된다.
코드에서 프로그래머가 직접 입력한 값을 리터럴 이라고 부르는데, 변수에 대입할 정수 리터럴은 진수에 따라 작성하는 방법이 다르다.
- 2진수 : 0b 또는 0B로 시작하고 0과 1로 작성
- 8진수 : 0으로 시작하고 0~7 숫자로 작성
- 10진수 : 소수점이 없는 0~9 숫자로 작성
- 16진수 : 0x 또는 0X로 시작하고 0~9 숫자나 A, B, C, D, E, F 또는 a, b, c, d, e, f로 작성
다음 예제는 다양한 정수 리터럴을 int 타입 변수에 대입하고 10진수로 출력한다.
package ch02.sec02;
public class IntegerliteralExample {
public static void main(String[] args) {
int var1 = 0b1011;
int var2 = 0206;
int var3 = 365;
int var4 = 0xB3;
System.out.println("var1: " + var1);
System.out.println("var2: " + var2);
System.out.println("var3: " + var3);
System.out.println("var4: " + var4);
}
}
다음 예제는 byte 타입 변수에 허용 범위를 초과한 값을 대입했을 경우 컴파일 오류가 발생하는 것을 보여준다.
package ch02.sec02;
public class IntegerliteralExample {
public static void main(String[] args) {
byte var1 = -128;
byte var2 = -30;
byte var3 = 0;
byte var4 = 30;
byte var5 = 127;
byte var6 = 128; //에러가 난다.
System.out.println(var1);
System.out.println(var2);
System.out.println(var3);
System.out.println(var4);
System.out.println(var5);
System.out.println(var6);
}
}
long 타입은 수치가 큰 데이터를 다루는 프로그램에서 사용된다. 예를 들어 은행이나 과학 분야에서 사용되는 프로그램들이다. 기본적으로 컴파일러는 정수 리터럴을 int타입 값으로 간주하기 때문에, int 타입의 허용 범위를 초과하는 리터럴은 뒤에 소문자 'l'이나 대문자 'L' 을 붙여 long 타입 값임을 컴파일러에게 알려줘야 한다.
package ch02.sec02;
public class LongExample {
public static void main(String[] args) {
long var1 = 10;
long var2 = 20L;
long var3 = 100000000000000; // int로 간주하기 때문에 에러 발생
long var4 = 10000000000000L;
System.out.println(var1);
System.out.println(var2);
System.out.println(var4);
}
}
2.3 문자 타입
하나의 문자를 작은 따옴표로 감싼 것을 문자 리터럴이라고 한다. 문자 리터럴은 유니코드로 변환되어 저장되는데, 유니코드는 세계 각국의 문자를 0~65535 숫자로 매핑한 국제 표준 규약이다. 자바는 이러한 유니코드를 저장할 수 있도록 char 타입을 제공한다.
char var1 = 'A'; //'A'문자와 매핑되는 숫자: 65로 대입
char var3 = '가'; // '가' 문자와 매핑되는 숫자: 44032로 대입
유니코드가 정수이므로 char 타입도 정수 타입에 속한다. 그렇기 대문에 char 변수에 작은 따옴표로 감싼 문자가 아니라 유니코드 숫자를 대입할 수도 있다.
char c = 65; //10진수 65와 매핑되는 문자: 'A'
char c = 0x0041; //16진수 0x0041과 매핑되는 문자: 'A'
package ch02.sec03;
public class CharExample {
public static void main (String[] args) {
char c1 ='A';
char c2 = 65;
char c3 ='가';
char c4 = 44032;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
}
}
char 타입의 변수에 어떤 문자도 대입하지 않고 단순히 초기화를 할 목적으로 다음과 같이 작은 따옴표 두 개를 연달아 붙인 빈 문자를 대입하면 컴파일 에러가 발생한다. 이 경우에는 공백 하나를 포함해서 초기화해야 한다.
char c = ''; //컴파일 에러 발생
char c = ' '; //공백 하나를 포함해서 초기화
2.4 실수 타입
실수 타입에는 float와 double이 있으며 다음과 같이 메모리 할당 크기와 저장되는 값의 범위를 가지고 있다.
타입 | 메모리 크기 | |
float | 4 byte | 32 bit |
double | 8 byte | 64 bit |
그림으로 표현하면 double 타입이 float 타입보다 큰 실수를 저장할 수 있고 정밀도 또한 높은 것을 볼 수 있다. 자바는 IEEE 754 표준에 근거해서 float 타입과 double 타입의 값을 부동 소수점 방식으로 메모리에 저장한다. float 타입과 double 타입은 가수와 지수를 저장하기 위해 전체 bit를 다음과 같이 나누어 사용한다. (p.46)
최상위 1bit는 양수 및 음수를 결정짓는 부호 bit로 0이면 양수, 1이면 음수가 된다. 지수는 float타입은 8bit, double 타입은 11bit로 표현하고, 나머지 bit는 모두 가수를 표현하는 데 사용된다. double은 float보다 지수와 가수 부분의 bit수가 크기 때문에 더 크고 정밀한 실수를 저장할 수 있다. 코드에서 실수 리터럴은 다음과 같이 작성할 수 있다.
- 10진수 리터럴
double x = 0.25;
double y = -3.14;
- e 또는 E가 포함된 10의 거듭제곱 리터럴
double x = 5e2; //5 * 10^2
double y = 0.12E-2; //0.12 * 10^(-2)
컴파일러 실수 리터럴을 기본적으로 double 타입으로 해석하기 때문에 double 타입 변수에 대입해야 한다. float 타입에 대입하고 싶다면 리터럴 뒤에 소문자 'f' 나 대문자 'F'를 붙여 컴파일러가 float 타입임을 알 수 있도록 해야 한다.
double var = 3.14;
dounble var = 3.14e-2;
float var = 3.14f;
float var = 3E6F;
다음 예제는 float와 double 타입의 소수 이하 유효 자릿수를 확인한다. double 타입은 float 타입보다 약 2배의 유효 자릿수를 가지기 때문에 보다 정확한 데이터 저장이 가능하다. double이라는 이름도 float보다 약 2배의 정밀도를 갖는다는 의미에서 붙여진 것이다. 확인 후에는 10의 거듭제곱 리터럴을 대입해서 출력해보자.
package ch02.sec04;
public class FloatExample {
public static void main(String[] args) {
float var1 = 0.1234567890123456789f;
double var2 = 0.1234567890123456789;
Syste.out.println("var1: " + var1);
System.out.println("var2: " + var2);
double var3 = 3e6;
float var4 = 3e6F;
double var5 = 2e-3;
System.our.println("var3: " + var3);
System.our.println("var4: " + var4);
System.our.println("var5: " + var5);
}
}
2.5 논리 타입
참과 거짓을 의미하는 논리 리트럴은 true 와 false이다. 논리 리터럴은 boolean 타입 변수에 다음과 같이 대입할 수 있다.
boolean stop = true;
boolean stop = false;
boolean 타입 변수는 주로 두 가지 상태 값을 저장할 필요가 있을 경웨 사용되며, 상태값에 따라 조건문과 제어문의 실행 흐름을 변경하는 데 사용된다. 연산식 중에서 비교 밒 논리 연산의 산출값은 true 또는 false이므로 boolean 타입 변수에 다음과 같이 대입할 수 있다. 연산 기호는 3장에서 자세히 설명한다.
int x = 10;
boolean result = (x == 20);
boolean result = (x != 20);
boolean result = (x > 20);
boolean result = ( 0 < x && x < 20);
boolean result = ( x < 0 || x > 200);
다음 예제는 stop 변수 값에 따라 if 블록과 else 블록 중 하나를 실행하고, 연산식의 겨과를 boolean 변수에 저장해서 출력한다.
package ch02.sec05;
public class BooleanExample {
public static void main(String[] args) {
boolean stop = true;
if(stop) {
System.out.println("중지합니다.");
} else {
System.out.println("시작합니다.");
}
int x = 10;
boolean result1 = (x == 20);
boolean result2 = (x != 20);
System.out.println("result1: " + result1);
System.out.println("result2: " + result2);
}
}
2.6 문자열 타입
작은따옴표로 감싼 한 개의 문자는 char 타입이지만, 큰따옴표로 감싼 여러 개의 문자들은 유니코드로 변환되지 않는다. 따라서 다음은 잘못 작성된 코드이다.
char var1 = "A";
char var2 = "홍길동";
큰따옴표로 감싼 문자들은 문자열이라고 부르는데, 문자열을 변수에 저장하고 싶다면 다음과 같이 String 타입을 사용해야 한다.
String var1 = "A";
Stirng var2 = "홍길동";
* String 타입은 자바 기본 타입에 속하지 않는 참조 타입이다. 참조 타입에 대해서는 5장에서 상세히 설명한다.
\" | " 문자 포함 |
\' | ' 문자 포함 |
\\ | \ 문자 포함 |
\u 16진수 | 16진수 유니코드에 해당하는 문자 포함 |
\t | 출력 시 탭만큼 띄움 |
\n | 출력 시 줄바꿈(라인피드) |
\r | 출력 시 캐리지 리턴 |
문자열 내부에 역슬래쉬가 붙은 문자를 사용할 수가 있는데, 이것을 이스케이프 문자라고 한다. 이스케이프 문자를 사용하면 특정 문자를 포함할 수 있고 출력에 영향을 미치기도 한다.
다음 예제는 이스케이프 문자를 사용하는 방법을 보여준다. 문자열에 큰따옴표를 넣기 위해 \"를 사용했고, 탭만큼 띄워 출력하기 위해 \t를, 다음 행으로 이동하기 위해 \n을 사용하였다. 역슬래쉬 기호가 원으로 표시될 수도 있는데, 이것은 폰트 때문이니 상관 없다.
package ch02.sec06;
public class StringExample {
public static void main(String[] args) {
String name = "홍길동";
String job = "프로그래머";
System.out.println (name);
System.out.println (job);
String str = "나는 \"자바\"를 배웁니다.";
System.out.println(str);
str = "번호\t이름\t직업 ";
System.out.println(str);
System.out.print("나는\n");
System.out.print("자바를\n");
System.out.print("배웁니다.");
}
}
* print() 와 println() 의 차이
print() → 이어서 출력
println() → 한줄로 나눠서 출력
print("1");
print("2"); → 실행 결과 123
print("3");
println("1"); → 실행 결과 1
println("2"); 2
println("3"); 3
Java 13부터는 다음과 같은 텍스트 블록 문법을 제공한다.
String str = """
…
""";
큰따옴표 3개로 감싸면 이스케이프ㅏ거나 라인 피드를 할 필요가 없이 작성된 그대로 문자열로 저장된다. 다음 예제에서 str1과 str2는 동일한 문자열이 저장된다.
package ch02.sec06;
public class TextBlockExample {
public static void main(String[] args) {
String str1 = "" +
"{\n" +
"\t\"id\":\"winter\",\n" +
"\t\"name\":눈송이\"\n" +
"}";
String str2 = """
{
"id":"winter",
"name":눈송이"
}
""";
System.out.println(str1);
System.out.println("------------------------");
System.out.println(str2);
System.out.println("------------------------");
String str = """
나는 자바를 \
학습합니다.
나는 자바 고수가 될 겁니다.
""";
System.out.println(str);
}
}
텍스트 블록에서 줄바꿈은 \n에 해당한다. 만약 줄바꿈을 하지 않고 다음 줄에 이어서 작성하고 싶다면 23라인처럼 맨 끝에 \를 붙여주면 된다. 이 기능은 Java 14부터 제공된다.
2.7 자동 타입 변환
자동 타입 변환은 말 그대로 자동으로 타입 변환이 일어나는 것을 말한다. 자동 타입 변환은 값의 허용 범위가 작은 타입이 허용 범위가 큰 타입으로 대입될 때 발생한다. 기본 타입을 허용 범위 순으로 나열하면 다음과 같다.
byte < short, char < int < long < float < double
int 타입이 byte 타입보다 허용 범위가 더 크기 때문에 다음 코드는 자동 타입 변환이 된다.
byte byteValue = 10;
int intValue = byteValue; //자동 타입 변환됨
byte 타입은 1byte, int 타입은 4 byte 메모리 크기를 가지므로 메모리에서 값이 복사되는 모양을 그림으로 표현하면 다음과 같다,(p.53)
정수 타입이 실수 타입으로 대입될 겨우에는 무조건 자동 타입 변환이 된다. 실수 타입은 정수 타입보다 허용 범위가 더 크기 때문이다.
long longValue = 5000000000L;
float floatValue = longValue; //5.0E9f로 저장됨
double doubleValue = longValue; //5.0E9로 저장됨
char 타입의 경우 int 타입으로 자동 변환되면 유니코드 값이 int 타입에 대입된다.
char charValue = 'A';
int intValue = charValue; // 65가 저장됨
자동 타입 변환에서 예외가 있다. char 타입보다 허용 범위가 작은 byte 타입은 char 타입으로 자동 변환될 수 없다. 왜냐하면 char 타입의 허용 범위는 음수를 포함하지 않는데, byte 타입은음수를 포함하기 때문이다.
byte byteValue = 65;
char charValue = byteValue // 컴파일 에러
다음은 자동 타입 변환이 생기는 다양한 코드들이다.
package ch02.sec07;
public class PromotionExample {
public static void main{String[] args) {
byte byteValue = 10;
int intValue = byteValue;
System.out,println("intValue: " + intValue);
char charValue = '가';
intValue = charValue;
System.out,println("가의 유니코드: " + intValue);
intValue = 50;
long longValue = intValue;;
System.out,println("longValue: " + longValue);
longValue = 100;
float floatValue = longValue;
System.out,println("floatValue: " + floatValue);
floatValue = 100.5F;
double doubleValue = floatValue;
System.out,println("doubleValue: " + doubleValue);
}
}
2.9 연산식에서 자동 타입 변환
자바는 실행 성능을 향상시키기 위해 컴파일 단계에서 연산을 수행한다. 정수 리터럴 10과 20을 덧셈 연산해서 결과를 byte 타입 변수 result 에 저장하는 코드가 있다고 가정해보자.
자바 컴파일러는 커뮤ㅏ일 단계에서 10+ 20 을 미리 연산해서 30을 만들고 result 변수에 30을 저장하도록 바이트코드를 생성한다. 따라서 실행 시 덧셈 연산이 없으므로 실행 성능이 좋아진다. 하지만 정수 리터럴이 아니라 변수가 피연산자로 사용되면 실행 시 연산을 수행한다. 정수 타입 변수가 산술 연산식에서 피연산자로 사용되면 int 타입보다 작은 byte, short 타입의 변수는 int 타입으로 자동 타입 변환되어 연산을 수행한다.
byte 타입 변수가 피연산자로 사용된 경우 | int 타입 변수ㅜ가 피연산자로 사용된 경우 |
byte x = 10; byte y = 20; int result = x + y; |
int x = 10; int y = 10; int result = x + y; |
표의 왼쪽처럼 byte 변수 x, y 가 피연산자로 사용되면 변수값은 int 타입으로 변환되어 연산되고, 결과도 int 타입으로 생성된다. 따라서 결과값을 byte 변수에 저장할 수 없고, int 변수에 저장해야 한다.
특별한 이유가 없다면 정수연산에서 변수가 사용될 경우에는 표의 오른쪽과 같이 int 타입으로 변수를 선언하는 것이 타입 변환이 발생하지 않기 때문에 실행 성능에 도움이 된다.
정수 연산식에서 모든 변수가 int 타입으로 변환되는 것은 아니다 int 타입보다 허용 범위가 더 큰 long 타입이 피연산자로 사용되면 다른 피여난자는 long 타입으로 변환되어 연산을 수행한다. 따라서 연산 결과는 long 타입 변수에 저장해야 한다.
이번에는 실수 연산을 알아보자. 피연산자가 동일한 실수 타입이라면 해당 타입으로 연산된다. 아래 예시는 ㅣ연산자에 모두 f가 붙어있기 때문에 float 타입으로 연산을 수행한다. 따라서 결과도 당연히 float 타입이 된다.
float result = 1.2f + 3.4f; 컴파일 : float result = 4.6f
하지만 피연산자 중 하나가 double 타입이면 다른 피연산자도 double 타입으로 변환되어 연산되고, 연산 결과 또한 double 타입이 된다. int 타입과 double 타입을 연산하는 경우에도 int 타입 피연산자가 double 타입으로 자동 변환되고 연산을 수행한다.
int intValue = 10;
double diubleValue = 5.5;
double result = intValue + doubleValue; //10.0 + 5.5
만약 int 타입으로 연산을 해야 한다면 double 타입을 int 타입으로 강제 변환하고 덧셈 연산을 수행하면 된다.
int intValue = 10;
double doubleValue = 5.5;
int result = intValue + (int) doubleValue; // 10+5
수학에서 1을 2로 나누면 0.5가 나온다. 이것을 코드로 옮기면 다음과 같다.
int x = 1;
int y = 2;
double result = x / y;
System.out.println(result);
위 코드를 실행하면 0.5가 아니라 0.0이 출력된다. 자바에서 정수 연산의 결과는 항상 정수가 되기 때문이다. 따라서 x / y의 연산 결과는 -.5가 아니라 0이 되고, 0을 double 타입 변수 result에 저장하므로 0.0이 된다. 위 코드의 결과가 0.5가 되기 위해서는 x / y 의 부분을 정수 연산이 아니라 실수 연산으로 변경해야 한다. x와 y둘 중 하나 또는 둘 모두를 double 타입으로 변환하는 것이다.
방법1 | 방법2 | 방법3 |
int x = 1; int y = 2; double result = (double) x / y; System.out.println(result); |
int x = 1; int y = 2; double result = x / (double) y; System.out.println(result); |
int x = 1; int y = 2; double result = (double) x / (double) y; System.out.println(result); |
만약 (double) (x / y)로 수정하면 0.5가 아니라 0.0을 얻는다. 그 이유는 (x / y)가 먼저 연산이 되어 0이 되고, 이것을 double 타입으로 변환하면 0.0이 되기 때문이다.
'JAVA' 카테고리의 다른 글
객프 10주차 (0) | 2023.11.14 |
---|---|
객프 10주차 (0) | 2023.11.06 |
Power JAVA 개정3판 CHAPTER01 답 (0) | 2023.10.30 |
자바 소개 (0) | 2023.09.12 |
[JAVA] 코드 용어 이해 (0) | 2023.08.17 |