기본 타입과 리터럴
정수·실수·문자·참거짓 값에 맞는 타입을 고르고, 소스에 직접 적은 값의 타입이 어떻게 정해지는지와 변수 이름을 짓는 규칙을 익힙니다.
앞 글에서는 String email, int age, boolean active처럼 변수 이름 앞에 타입을 적었습니다. 타입(type)은 그 변수에 어떤 종류의 값을 담을 수 있고, 그 값으로 어떤 계산을 할 수 있는지를 정합니다. 나이에는 정수가 어울리고, 활성 여부에는 참과 거짓만 있으면 됩니다. 그런데 회원 번호가 30억까지 커진다면 int로는 담을 수 없습니다.
이 글에서는 Java가 제공하는 기본 타입을 살펴보고, 소스 코드에 직접 적은 45, 0.75, 'A' 같은 값의 타입이 어떻게 정해지는지 알아봅니다. 마지막으로 변수 이름으로 쓸 수 있는 것과 쓰면 좋은 것을 구분합니다.
30억을 long 변수에 넣었는데 컴파일되지 않는 이유
int는 대략 −21억부터 21억까지의 정수만 담을 수 있습니다. 회원 번호가 30억이라면 더 넓은 범위를 담는 long 타입이 필요합니다. 그래서 다음처럼 long 변수를 선언했는데, 이 코드는 컴파일되지 않습니다.
public final class TooLargeLiteral {
public static void main(String[] args) {
long memberId = 3_000_000_000;
System.out.println(memberId);
}
}lab\TooLargeLiteral.java:3: error: integer number too large
long memberId = 3_000_000_000;
^
1 error오류 표시(^)는 왼쪽의 변수가 아니라 오른쪽의 3_000_000_000을 가리킵니다. 소스 코드에 값을 직접 적은 것을 리터럴(literal)이라고 하는데, 리터럴은 그 자체로 타입을 가집니다. 접미사가 없는 정수 리터럴은 int로 읽히므로, 컴파일러는 이 리터럴을 int 값으로 만들려다가 범위를 넘는다는 것을 발견합니다. 왼쪽 변수가 long이라는 사실은 오른쪽 값이 만들어진 다음에야 따지기 때문에 도움이 되지 않습니다.
해결하려면 리터럴 끝에 L을 붙여 리터럴 자체를 long으로 만듭니다.
long memberId = 3_000_000_000L;소문자 l도 문법상 허용되지만 숫자 1과 헷갈리므로 대문자 L을 씁니다. 숫자 사이의 밑줄(_)은 값에 아무 영향을 주지 않고 자릿수만 읽기 쉽게 나눕니다. 그래서 3_000_000_000L과 3000000000L은 같은 값입니다. 다만 밑줄은 숫자와 숫자 사이에만 올 수 있어서, 100_이나 100_L처럼 끝에 붙이면 illegal underscore 오류가 납니다.
비슷한 실수가 하나 더 있습니다. 숫자처럼 보이는 문자열은 숫자가 아닙니다.
public final class StringIsNotInt {
public static void main(String[] args) {
int age = "45";
System.out.println(age);
}
}lab\StringIsNotInt.java:3: error: incompatible types: String cannot be converted to int
int age = "45";
^
1 error큰따옴표로 감싼 "45"는 문자 4와 5로 이루어진 String 리터럴입니다. 이번에는 리터럴 자체에는 문제가 없습니다. 문제는 만들어진 String 값을 int 변수에 넣을 수 있는지 확인하는 다음 단계에서 생기고, Java는 문자열을 숫자로 자동 변환하지 않으므로 컴파일을 거부합니다. 문자열을 정수로 바꾸려면 Integer.parseInt("45")처럼 변환을 직접 요청해야 합니다. 이 변환은 프로그램을 실행하는 중에 문자열을 해석하므로, "forty"처럼 숫자가 아닌 문자열을 받으면 실행 중에 실패할 수 있습니다.
JAVA · COMPILE FLOW
리터럴의 표기가 대입 전에 타입을 결정한다
왼쪽 변수보다 먼저 오른쪽 리터럴의 표기·범위·초기 타입을 판정해야 대입 성공과 실패를 설명할 수 있습니다.
먼저 오른쪽 리터럴의 타입과 범위를 확인합니다
L 없음 · 리터럴 단계에서 실패
long memberId = 3_000_000_000;왼쪽은 long이어도, L 없는 십진 정수 리터럴이 int 범위를 넘습니다.
컴파일 오류:
integer number too largeL 있음 · long 값을 만듦
long memberId = 3_000_000_000L;접미사 L이 리터럴 자체를 long으로 해석하게 하므로 대입할 값을 만들 수 있습니다.
리터럴 타입:
long그 값이 왼쪽 변수 타입에 대입 가능한지 확인합니다
문자열을 정수로 바로 대입 · 실패
int age = "45";"45"는 유효한 String 리터럴이지만 int로 자동 변환되지 않습니다.
컴파일 오류: String → int 대입 불가
명시적으로 파싱 · 별도 변환 경계
int age = Integer.parseInt("45");이 예제는 정수 45를 만듭니다. 다른 문자열은 형식이나 범위에 따라 변환에 실패할 수 있습니다.
표기도 타입의 일부입니다
45 → int · 0.75 → double · true → boolean · 'A' → char · "45" → String
타입은 목적지 변수의 장식이 아닙니다. 소스 표현이 먼저 값을 만들고, 그 결과가 목적지 계약을 통과해야 합니다.
두 오류는 서로 다른 단계에서 생깁니다. 3_000_000_000은 리터럴을 읽는 단계에서 이미 int가 될 수 없어 실패합니다. 반면 "45"는 올바른 String 값으로 만들어진 뒤, 그 값을 int 변수에 넣을 수 있는지 검사하는 단계에서 실패합니다. 어느 쪽이든 먼저 오른쪽 값의 타입이 정해지고, 그다음 그 값이 왼쪽 변수의 타입에 맞는지 확인한다는 순서는 같습니다.
기본 타입 여덟 가지
Java에는 언어에 처음부터 들어 있는 기본 타입(primitive type)이 여덟 가지 있습니다. 모두 소문자로 쓰는 키워드입니다. 표의 크기는 값 하나를 저장하는 데 쓰는 비트(bit) 수입니다. 비트는 0이나 1 하나를 담는 가장 작은 단위라서, 비트가 하나 늘 때마다 나타낼 수 있는 값의 개수가 두 배가 됩니다.
| 분류 | 타입 | 크기 | 담을 수 있는 값 |
|---|---|---|---|
| 정수 | byte | 8비트 | −128부터 127까지 |
| 정수 | short | 16비트 | −32,768부터 32,767까지 |
| 정수 | int | 32비트 | 약 −21억부터 21억까지 |
| 정수 | long | 64비트 | 약 −922경부터 922경까지 |
| 실수 | float | 32비트 | 십진수 6–7자리 정도의 정밀도 |
| 실수 | double | 64비트 | 십진수 15–16자리 정도의 정밀도 |
| 문자 | char | 16비트 | UTF-16 코드 단위 하나 (0부터 65,535까지) |
| 논리 | boolean | 명세에 정해지지 않음 | true 또는 false |
String은 이 목록에 없습니다. String은 Java 표준 라이브러리가 제공하는 클래스이고, 클래스 이름은 대문자로 시작하는 관례가 있어서 String도 대문자로 시작합니다. 클래스 타입의 변수에는 값 자체가 아니라 객체를 가리키는 참조가 들어간다는 점이 기본 타입과 다릅니다. 다만 문자열은 워낙 자주 쓰여서 "..."라는 리터럴 표기가 따로 마련되어 있습니다.
회원 정보 하나를 여러 타입으로 표현해 보겠습니다.
public final class MemberTypes {
public static void main(String[] args) {
int age = 45;
long memberId = 3_000_000_000L;
double signupRate = 0.75;
boolean active = false;
char grade = 'A';
String email = "[email protected]";
System.out.println("age=" + age);
System.out.println("memberId=" + memberId);
System.out.println("signupRate=" + signupRate);
System.out.println("active=" + active);
System.out.println("grade=" + grade);
System.out.println("email=" + email);
}
}age=45
memberId=3000000000
signupRate=0.75
active=false
grade=A
[email protected]소스에서는 밑줄로 자릿수를 나누었지만, 출력된 memberId에는 밑줄이 없습니다. 밑줄은 소스를 읽는 사람을 위한 표기일 뿐 값에는 포함되지 않기 때문입니다. 나머지 줄도 각 변수에 넣은 값이 그대로 문자열로 바뀌어 출력됩니다. 이제 타입을 하나씩 고르는 기준을 살펴보겠습니다.
정수: 대부분 int, 범위가 넘치면 long
정수는 특별한 이유가 없으면 int를 씁니다. 나이, 개수, 순번처럼 21억을 넘지 않는 값은 모두 int로 충분합니다. 회원 번호나 밀리초 단위 시각처럼 21억을 넘을 수 있는 값에는 long을 씁니다.
값이 작다고 byte나 short를 고르면 오히려 불편해집니다. Java는 byte와 short 값으로 계산할 때 먼저 int로 바꾼 뒤 계산하기 때문입니다. 예를 들어 byte small = 10; 다음에 small = small + 1;이라고 쓰면, small + 1의 결과가 int이므로 byte 변수에 다시 넣을 수 없다는 오류가 납니다.
error: incompatible types: possible lossy conversion from int to byte그래서 byte는 파일이나 네트워크에서 읽은 원시 바이트처럼 API가 그 타입을 요구할 때 주로 씁니다. 이런 변환 규칙은 변수 스코프와 숫자 형변환에서 자세히 다룹니다.
실수: double을 쓰되 오차를 알고 쓰기
소수점이 있는 값에는 double을 씁니다. 0.75처럼 접미사 없이 적은 실수 리터럴은 double이고, float 리터럴을 쓰려면 0.75F처럼 F를 붙여야 합니다. 그래서 float signupRate = 0.75;는 possible lossy conversion from double to float 오류가 납니다. double 값을 정밀도가 낮은 float에 넣으면 정보가 손실될 수 있기 때문입니다. 메모리를 아껴야 하는 특별한 경우가 아니면 float보다 double을 씁니다.
double은 십진수 소수를 정확히 표현하지 못하는 경우가 많다는 점도 알아 두어야 합니다. System.out.println(0.1 + 0.2);를 실행하면 0.3이 아니라 0.30000000000000004가 출력됩니다. 컴퓨터는 실수를 이진수로 저장하는데, 십진수 0.1은 이진수로 끝없이 이어지는 소수라서 가장 가까운 값으로 반올림되어 저장되기 때문입니다. 실수가 비트로 어떻게 저장되고 반올림되는지는 소수·부동소수점·문자에서 자세히 다룹니다. 비율이나 평균처럼 아주 작은 오차가 문제 되지 않는 계산에는 double이 적합하지만, 금액처럼 십진수로 정확해야 하는 값에는 BigDecimal 같은 십진 연산용 클래스를 씁니다.
참과 거짓: boolean
boolean에는 true와 false 두 값만 들어갑니다. 활성 여부를 int active = 1;처럼 정수로 나타낼 수도 있겠지만, 그러면 active = 2;처럼 아무 뜻도 없는 세 번째 상태가 생길 수 있습니다. boolean을 쓰면 그런 값이 들어갈 자리가 처음부터 없습니다. 일부 언어와 달리 Java는 정수를 참·거짓 대신 쓰는 것도 허용하지 않아서, boolean active = 1;은 int cannot be converted to boolean 오류가 납니다.
문자 하나: char
작은따옴표로 감싼 'A'나 '가'는 char 리터럴이고, 큰따옴표로 감싼 "A"는 String 리터럴입니다. 둘은 서로 다른 타입이라서 char grade = "A";라고 쓰면 String cannot be converted to char 오류가 납니다. 회원 등급처럼 언제나 글자 하나인 값이라면 char를 쓸 수 있지만, 이메일이나 이름처럼 길이가 달라지는 값에는 String을 씁니다. 실제 프로그램의 상태 라벨이나 제목은 대부분 길이가 일정하지 않으므로 String이 자연스럽습니다.
더 깊이: char는 '글자 하나'가 아니다
정확히 말하면 char 하나에는 UTF-16 인코딩의 16비트 코드 단위(code unit) 하나가 들어갑니다. A나 가처럼 자주 쓰는 문자는 코드 단위 하나로 표현되므로 char에 담을 수 있습니다. 하지만 이모지 😀 같은 문자는 코드 단위 두 개가 필요하므로 char 하나에 담을 수 없고, String으로 다루어야 합니다. 실제로 "😀".length()는 1이 아니라 2를 돌려줍니다. String의 길이가 화면에 보이는 글자 수가 아니라 코드 단위 수이기 때문입니다. Java 언어 명세의 기본 타입 정의도 char의 값을 UTF-16 코드 단위를 나타내는 16비트 부호 없는 정수로 정의합니다. 문자와 바이트, 인코딩의 관계는 문자와 바이트: 유니코드와 UTF-8에서 자세히 다룹니다.
리터럴의 타입은 적는 방식으로 정해진다
지금까지 본 리터럴을 정리하면 다음과 같습니다. 리터럴의 타입은 값의 크기가 아니라 적는 방식, 즉 소수점과 접미사, 따옴표의 종류로 정해집니다.
| 소스 표현 | 리터럴 타입 | 정해지는 이유 | 회원가입에서의 의미 |
|---|---|---|---|
45 | int | 접미사 없는 정수 | 회원 나이 |
3_000_000_000L | long | 접미사 L | 큰 회원 번호 |
0.75 | double | 접미사 없는 실수 | 가입 전환율 |
0.75F | float | 접미사 F | 이 교재에서는 쓰지 않음 |
true | boolean | true와 false 두 가지뿐 | 활성 여부 |
'A' | char | 작은따옴표 | 글자 하나인 등급 |
"[email protected]" | String | 큰따옴표 | 회원 이메일 |
리터럴은 소스에 고정된 표현이고, 변수의 값은 대입할 때마다 바뀔 수 있습니다. int age = 45; age = 60;에서 45와 60은 각각 리터럴이고, 시간에 따라 달라지는 것은 age의 값뿐입니다.
정수 리터럴을 쓸 때 주의할 점이 하나 있습니다. 숫자 앞에 0을 붙이면 8진수로 읽힙니다. 예를 들어 System.out.println(010);은 10이 아니라 8을 출력하고, 8진수에 없는 숫자가 들어간 09는 컴파일 오류가 납니다. 그러니 월이나 날짜의 자릿수를 맞추려고 앞에 0을 붙이는 습관은 Java 소스에서는 버려야 합니다. 같은 방식으로 0x로 시작하면 16진수, 0b로 시작하면 2진수 리터럴이 됩니다.
타입마다 범위를 직접 출력해 보기
각 타입이 담을 수 있는 범위는 외우지 않아도 됩니다. 정수 타입마다 짝이 되는 클래스(Byte, Short, Integer, Long)에 최솟값과 최댓값이 상수로 들어 있으므로, 필요할 때 출력해 보면 됩니다.
public final class NumericRanges {
public static void main(String[] args) {
System.out.println("byte=" + Byte.MIN_VALUE + ".." + Byte.MAX_VALUE);
System.out.println("short=" + Short.MIN_VALUE + ".." + Short.MAX_VALUE);
System.out.println("int=" + Integer.MIN_VALUE + ".." + Integer.MAX_VALUE);
System.out.println("long=" + Long.MIN_VALUE + ".." + Long.MAX_VALUE);
System.out.println("double.max=" + Double.MAX_VALUE);
System.out.println("float.precision=" + Float.PRECISION);
System.out.println("double.precision=" + Double.PRECISION);
}
}byte=-128..127
short=-32768..32767
int=-2147483648..2147483647
long=-9223372036854775808..9223372036854775807
double.max=1.7976931348623157E308
float.precision=24
double.precision=53int의 최댓값 2147483647이 앞에서 말한 '약 21억'입니다. 30억이 int에 들어가지 않았던 이유를 숫자로 확인할 수 있습니다. 다섯 번째 줄의 E308은 '10의 308제곱을 곱한다'는 뜻으로, double은 1.8 곱하기 10의 308제곱 정도까지 큰 수를 표현할 수 있습니다.
그렇다고 double이 그만큼 많은 자릿수를 정확히 기억하는 것은 아닙니다. 마지막 두 줄의 PRECISION은 실수의 유효 숫자(가수)를 나타내는 비트 수로, float는 24비트, double은 53비트입니다. 53비트는 십진수로 15–16자리 정도라서, double은 아주 큰 수를 표현하더라도 앞쪽 15–16자리만 정확하게 담습니다. 즉 표현할 수 있는 크기와 정확히 보존되는 자릿수는 서로 다른 개념입니다. Float.PRECISION과 Double.PRECISION은 Java 19부터 제공되는 상수입니다.
더 깊이: int를 중심으로 짜인 정수 규칙
정수 리터럴은 따로 표시하지 않으면 int가 되고, byte와 short 값은 계산하기 전에 int로 바뀝니다. 이 규칙은 JVM의 설계와 맞물려 있습니다. JVM 명세의 바이트코드 명령어는 정수 연산을 대부분 int와 long에 대해서만 제공하며, byte, short, char 값은 int로 넓혀서 계산합니다. 작은 정수를 계산 전에 int로 넓히는 것은 C 언어의 정수 승격(integer promotion)과 비슷한 방식이라, C 계열 언어를 써 본 개발자에게는 익숙한 규칙입니다.
범위 규칙에는 흥미로운 예외가 하나 있습니다. int의 최솟값은 −2147483648인데, 리터럴 2147483648 자체는 int의 최댓값보다 1이 큽니다. 그래서 명세는 십진 리터럴 2147483648을 음수 부호 바로 뒤에서만 허용합니다. int min = -2147483648;은 컴파일되지만, 같은 숫자를 부호 없이 쓰면 integer number too large 오류가 납니다.
변수 이름으로 쓸 수 있는 것과 쓰면 좋은 것
변수 이름처럼 프로그래머가 짓는 이름을 식별자(identifier)라고 합니다. 다음 선언은 둘 다 컴파일되지 않습니다.
public final class IllegalNames {
public static void main(String[] args) {
int 2age = 45;
int class = 1;
}
}lab\IllegalNames.java:3: error: not a statement
int 2age = 45;
^
lab\IllegalNames.java:3: error: ';' expected
int 2age = 45;
^
lab\IllegalNames.java:4: error: not a statement
int class = 1;
^
lab\IllegalNames.java:4: error: ';' expected
int class = 1;
^
lab\IllegalNames.java:4: error: <identifier> expected
int class = 1;
^
5 errors오류 메시지 어디에도 '이름이 잘못되었다'는 말은 없습니다. 컴파일러는 int 다음에 이름이 올 것이라고 예상했는데, 숫자로 시작하는 2age나 키워드인 class는 이름으로 읽을 수 없으니 문장 구조 자체를 해석하지 못한 것입니다. 그래서 not a statement나 ';' expected 같은 메시지가 여러 개 쏟아집니다. 선언문에서 이런 메시지가 나오면 먼저 변수 이름을 의심해 보세요.
식별자를 짓는 규칙은 다음과 같습니다.
- 글자, 숫자, 밑줄(
_), 달러 기호($)를 쓸 수 있습니다. 여기서 글자에는 한글 같은 유니코드 문자도 포함됩니다. 다만 밑줄 한 글자_만으로 된 이름은 쓸 수 없습니다._는 키워드이고, Java 22부터는 쓰지 않을 변수 자리를 표시하는 '이름 없는 변수'로 쓰입니다. - 숫자로 시작할 수 없습니다.
age2는 되지만2age는 안 됩니다. class,int,if같은 키워드와true,false,null은 이름으로 쓸 수 없습니다.- 대소문자를 구분합니다.
age와Age는 서로 다른 변수입니다.
규칙상 허용되는 이름이라고 해서 모두 좋은 이름은 아닙니다. 다음 선언은 모두 컴파일되지만 피하는 것이 좋습니다.
int $ = 45;
int _age = 50;
int a = 60;Java 언어 명세는 $를 도구가 자동으로 생성하는 소스 코드에서만 쓰라고 권합니다. 실제로 컴파일러도 클래스 안에 선언한 클래스의 파일 이름을 Outer$Inner.class처럼 만들 때 $를 씁니다. _age처럼 밑줄로 시작하는 이름은 Java 관례에 맞지 않고, a는 무엇을 담는지 전혀 알려 주지 않습니다. age와 Age처럼 대소문자로만 구별되는 이름을 함께 쓰는 것도 읽는 사람을 헷갈리게 합니다. 한글 이름은 문법상 가능하지만, 대부분의 Java 코드와 라이브러리가 영어 이름을 쓰므로 이 교재도 영어 이름을 씁니다.
Java 개발자들은 다음 관례를 따릅니다. 변수와 메서드 이름은 memberAge처럼 소문자로 시작하고, 이어지는 단어의 첫 글자만 대문자로 씁니다. 이런 표기를 로어 카멜 케이스(lower camel case)라고 합니다. 클래스 이름은 MemberTypes처럼 대문자로 시작하고, 값이 바뀌지 않는 상수는 MAX_AGE처럼 대문자와 밑줄로 씁니다. 상수는 final 변수·필드·상수에서 다룹니다. 무엇보다 이름은 값의 의미를 알려 주어야 합니다. m보다 age가, flag보다 active가 좋은 이름인 이유는 코드를 읽는 사람이 다른 곳을 찾아보지 않아도 그 값이 무엇인지 알 수 있기 때문입니다.
회원 정보에 맞는 타입 고르기
이제 실행 인자로 받은 회원 정보를 알맞은 타입의 변수에 담아 보겠습니다. 1장에서 본 것처럼 실행 인자는 모두 문자열로 들어오므로, 나이와 회원 번호는 숫자 타입으로 바꿔야 합니다. 예제에는 아직 배우지 않은 표현이 세 가지 나옵니다. 지금은 다음 뜻으로만 읽으면 됩니다.
조건 ? 값1 : 값2는 조건이 참이면 값1을, 거짓이면 값2를 고릅니다. 첫 줄은 실행 인자가 있으면args[0]을, 없으면 기본 이메일을 고릅니다. switch 문·switch 식과 삼항 연산자에서 자세히 배웁니다.Integer.parseInt와Long.parseLong은 문자열을 각각int와long값으로 바꿉니다. 앞에서 본"45"와45사이를 잇는 변환입니다.(double) age는 나눗셈 전에 나이를double로 바꿉니다. 이렇게 하지 않으면 정수끼리의 나눗셈이 되어 소수 부분이 사라지는데, 그 이유는 다음 글에서 다룹니다.
이 예제는 나이와 회원 번호 인자가 각각 int와 long 범위 안의 정수 문자열이고, 나이는 1세 이상이라고 가정합니다. 숫자가 아닌 인자가 들어왔을 때의 처리는 다음 장에서 배웁니다.
public final class MemberPreview {
public static void main(String[] args) {
String email = args.length > 0 ? args[0] : "[email protected]";
int age = args.length > 1 ? Integer.parseInt(args[1]) : 45;
long memberId = args.length > 2 ? Long.parseLong(args[2]) : 3_000_000_000L;
int minimumAge = 18;
double ageRatio = (double) age / minimumAge;
boolean eligible = age >= minimumAge;
System.out.println("email=" + email);
System.out.println("age=" + age);
System.out.println("memberId=" + memberId);
System.out.println("ageRatio=" + ageRatio);
System.out.println("eligible=" + eligible);
}
}[email protected]
age=45
memberId=3000000000
ageRatio=2.5
eligible=true실행 인자 세 개가 각각 email, age, memberId에 들어갔습니다. 회원 번호 3000000000은 int 범위를 넘으므로 Long.parseLong으로 바꿔 long에 담았습니다. 이 값을 Integer.parseInt로 바꾸려 했다면, 리터럴의 경우처럼 컴파일 오류가 나는 대신 실행 중에 NumberFormatException이 발생합니다. 문자열의 내용은 실행할 때 비로소 알 수 있기 때문입니다.
ageRatio=2.5는 나이 45를 최소 가입 나이 18로 나눈 값이고, 결과에 소수 부분이 있으므로 double에 담았습니다. eligible=true는 age >= minimumAge, 즉 45가 18 이상인지 비교한 결과입니다. 비교의 결과는 참 아니면 거짓이므로 boolean에 담았습니다. 이처럼 변수의 타입은 그 변수에 들어갈 값이 정수인지, 소수 부분이 있는지, 참·거짓인지를 보고 고릅니다.
인자 없이 java -cp out MemberPreview로 실행하면 세 변수에 모두 기본값이 들어갑니다. 기본 나이와 회원 번호가 위 실행에서 준 값과 같으므로, 출력에서 달라지는 것은 첫 줄의 [email protected]뿐입니다.
정리
- 기본 타입은 정수
byte,short,int,long, 실수float,double, 문자char, 논리boolean의 여덟 가지입니다.String은 기본 타입이 아니라 클래스입니다. - 정수는
int, 21억을 넘을 수 있으면long, 소수가 있으면double, 참·거짓은boolean이 기본 선택입니다. - 리터럴의 타입은 적는 방식으로 정해집니다. 접미사 없는 정수는
int,L은long, 접미사 없는 실수는double,F는float, 작은따옴표는char, 큰따옴표는String입니다. - 오른쪽 값의 타입이 먼저 정해지고, 그다음 왼쪽 변수에 넣을 수 있는지 검사합니다. 그래서
long변수에 넣더라도 큰 정수 리터럴에는L이 필요합니다. double은 넓은 범위를 표현하지만 정확한 자릿수는 15–16자리 정도이고,0.1 + 0.2처럼 십진 소수에는 오차가 생길 수 있습니다.- 식별자는 숫자로 시작할 수 없고 키워드를 쓸 수 없습니다. 변수는
memberAge, 클래스는MemberTypes, 상수는MAX_AGE처럼 짓고, 의미가 드러나는 이름을 씁니다.
연습 문제
1. 컴파일 오류 찾기
다음 코드에는 컴파일 오류가 네 개 있습니다. 컴파일하기 전에 어느 줄이 왜 틀렸는지 예측하고, 각 줄을 고치세요.
public final class LiteralQuiz {
public static void main(String[] args) {
int a = 2_147_483_647;
int b = 2_147_483_648;
long c = 2_147_483_648L;
float d = 1.5;
char e = "A";
boolean f = 0;
}
}해설 보기
틀린 줄은 b, d, e, f입니다. a는 int의 최댓값과 정확히 같으므로 문제가 없고, c는 L이 붙은 long 리터럴이므로 문제가 없습니다.
int b = 2_147_483_648;은int최댓값보다 1 큰 리터럴이라서integer number too large오류가 납니다. 이 값이 꼭 필요하다면long b = 2_147_483_648L;로 바꿉니다.float d = 1.5;의1.5는double리터럴입니다.float d = 1.5F;로 고칩니다.char e = "A";의"A"는String입니다.char e = 'A';로 고칩니다.boolean f = 0;은 Java가 정수를 참·거짓으로 쓰지 않으므로 틀렸습니다.boolean f = false;로 고칩니다.
실제로 컴파일해 보면 처음에는 b 줄의 오류 하나만 보입니다.
lab\LiteralQuiz.java:4: error: integer number too large
int b = 2_147_483_648;
^
1 errorb 줄을 고치고 다시 컴파일해야 나머지 세 오류가 함께 나옵니다. 앞에서 본 것처럼 너무 큰 리터럴은 소스를 읽는 단계에서 걸러지고, 나머지 세 줄은 값을 변수에 넣을 수 있는지 검사하는 다음 단계에서 걸러지기 때문입니다. javac는 앞 단계에서 오류가 나면 다음 단계의 검사를 하지 않고 멈춥니다. 그러니 오류를 하나 고쳤는데 새 오류가 나타났다고 해서 상황이 나빠진 것은 아닙니다.
2. 회원 정보 목록 만들기
src/MemberValueCatalog.java를 작성해 다음 값을 알맞은 타입과 이름으로 선언하고 출력하세요. 큰 정수 리터럴은 자릿수가 눈에 보이게 쓰고, 단위가 있는 값은 변수 이름에 단위를 드러내야 합니다.
- 회원 나이 90세
- 회원 번호 9,000,000,000
- 가입 전환율 82.5%
- 활성 여부
- 등급 문자
B - 이메일
[email protected]
해설 보기
public final class MemberValueCatalog {
public static void main(String[] args) {
int age = 90;
long memberId = 9_000_000_000L;
double signupPercent = 82.5;
boolean active = true;
char grade = 'B';
String email = "[email protected]";
System.out.println("age=" + age);
System.out.println("memberId=" + memberId);
System.out.println("signupPercent=" + signupPercent + "%");
System.out.println("active=" + active);
System.out.println("grade=" + grade);
System.out.println("email=" + email);
}
}age=90
memberId=9000000000
signupPercent=82.5%
active=true
grade=B
[email protected]회원 번호 90억은 int 범위를 넘으므로 long에 담고, 리터럴에 L과 밑줄을 붙였습니다. 전환율은 signupRate라고만 쓰면 82.5가 퍼센트인지 0과 1 사이의 비율인지 이름만 보고 알 수 없습니다. 그래서 이름을 signupPercent로 짓고 출력에도 %를 붙였습니다. 반대로 0.825라는 비율로 저장하고 싶다면 double signupRatio = 0.825;처럼 이름에 비율임을 드러내면 됩니다. 숫자만 맞는 코드와 그 숫자의 단위까지 읽히는 코드는 다릅니다.
다음 글 산술 연산과 문자열 결합에서는 이렇게 담은 값으로 계산을 합니다. 같은 + 기호가 언제 덧셈이고 언제 문자열 이어 붙이기인지, 정수끼리 나누면 왜 소수 부분이 사라지는지 살펴봅니다.