본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
2장 : 값·식·분기·반복

기본 타입과 리터럴

값의 범위와 연산에 맞는 타입·리터럴을 선택하고 컴파일러가 허용하는 이름과 읽기 좋은 이름을 구분합니다.

변수 이름이 의미를 설명한다면 타입은 그 값에 허용되는 범위와 연산을 제한합니다.

회원가입의 나이는 정수이고, 회원 식별자는 int 범위를 넘을 수 있으며, 활성 여부는 0과 1보다 boolean이 정확합니다.

리터럴은 소스에 직접 적은 값이므로 접미사와 따옴표까지 타입의 일부로 읽어야 합니다.


정수 리터럴의 범위

다음 프로그램은 값이 30억인 회원 식별자를 long 변수에 넣으려 하지만 오른쪽 리터럴 자체가 기본 int 범위를 넘습니다.

lab/TooLargeLiteral.java
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;
                            ^

왼쪽이 long이어도 오른쪽 정수 리터럴은 접미사가 없으면 먼저 int로 해석됩니다.

값 뒤에 대문자 L을 붙여 리터럴 자체를 long으로 만듭니다.

long memberId = 3_000_000_000L;

소문자 l도 문법상 가능하지만 숫자 1과 헷갈리므로 대문자 L을 사용합니다.

밑줄은 숫자 값을 바꾸지 않고 자릿수만 읽기 쉽게 나눕니다.

3_000_000_000L3000000000L은 같은 값입니다.

문자열 리터럴도 숫자로 자동 변하지 않습니다.

lab/StringIsNotInt.java
public final class StringIsNotInt {
    public static void main(String[] args) {
        int age = "45";
        System.out.println(age);
    }
}
error: incompatible types: String cannot be converted to int

"45"는 숫자처럼 보이는 두 문자로 이루어진 문자열입니다.

계산하려면 Integer.parseInt("45")처럼 변환 성공과 실패가 있는 별도 경계를 통과해야 합니다.


기본 타입과 값의 대응

src/MemberTypes.java
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 = "types@example.com";

        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=types@example.com

정수형은 byte, short, int, long이 있지만 일반적인 정수 계산은 int, 큰 누적값은 long을 우선합니다.

파일의 원시 바이트처럼 API가 byte를 요구할 때는 그 타입을 사용합니다.

작은 값을 담는다고 무조건 작은 타입을 택하면 변환과 오버플로 검토가 늘어날 수 있습니다.

실수형은 floatdouble이 있습니다.

접미사 없는 0.75double이고 float 리터럴은 0.75F처럼 표시합니다.

일반 계산은 double을 사용하지만 금액처럼 정확한 십진 연산이 필요한 경우에는 부동소수점 자체의 오차를 고려해 뒤에서 BigDecimal 같은 타입을 선택합니다.

booleantruefalse만 가집니다.

int active = 2처럼 유효하지 않은 세 번째 상태를 만들지 않습니다.

char는 작은따옴표로 감싼 문자 하나이고 String은 큰따옴표로 감싼 문자열입니다.

실제 업무의 제목과 상태 라벨은 길이가 변하므로 대부분 String이 자연스럽습니다.


리터럴과 타입

소스 표현리터럴 타입회원가입에서의 의미
45int회원 나이
3_000_000_000Llong큰 회원 식별자
0.75double가입 전환율
trueboolean활성 여부
'A'char문자 하나인 등급
"kim@example.com"String회원 이메일

리터럴은 고정된 소스 표현이고 변수의 현재 값은 대입으로 바뀔 수 있습니다.

int age = 45; age = 60;에서 45와 60은 각각 리터럴이며 age의 값만 시간에 따라 달라집니다.

숫자 타입의 범위를 암기하기보다 경계를 출력해 확인할 수 있습니다.

src/NumericRanges.java
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("float.precision=" + Float.PRECISION);
        System.out.println("double.precision=" + Double.PRECISION);
    }
}

Float.PRECISIONDouble.PRECISION은 이진 부동소수점 가수의 비트 정밀도를 보여 줍니다.

표현 가능한 최대 크기와 정확히 보존되는 십진 자릿수는 같은 개념이 아닙니다.


식별자 문법과 관례

다음 선언은 컴파일되지 않습니다.

lab/IllegalNames.java
public final class IllegalNames {
    public static void main(String[] args) {
        int 2age = 45;
        int class = 1;
    }
}

식별자는 숫자로 시작할 수 없고 class 같은 예약어를 변수 이름으로 쓸 수 없습니다.

문자, 숫자, 밑줄, 달러 기호를 조합할 수 있지만 숫자는 첫 글자가 될 수 없습니다.

Java는 대소문자를 구분하므로 ageAge는 다른 변수지만 이런 차이로 의미를 구분하면 읽기 어렵습니다.

문법상 가능한 다음 이름도 피합니다.

int $ = 45;
int _age = 50;
int a = 60;

일반 변수와 메서드는 memberAge처럼 소문자로 시작하는 로어 카멜 케이스를 사용합니다.

클래스는 MemberTypes처럼 대문자로 시작하고, 변경되지 않는 상수는 뒤에서 MAX_AGE처럼 대문자와 밑줄을 사용합니다.

m보다 age, flag보다 active가 값의 단위와 의미를 직접 알려 줍니다.


회원 정보의 타입 설계

회원가입 결과에 나이와 활성 여부를 추가합니다.

아직 범위 검증 전이므로 1세 이상이면 입력 형식이 맞다고 가정합니다.

src/MemberPreview.java
public final class MemberPreview {
    public static void main(String[] args) {
        String email = args.length > 0 ? args[0] : "types@example.com";
        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);
    }
}
java -cp out MemberPreview literals@example.com 45 3000000000
email=literals@example.com
age=45
memberId=3000000000
ageRatio=2.5
eligible=true

3_000_000_000LL은 리터럴 자체를 long으로 해석하게 합니다.

문자열 인자를 읽을 때도 Integer.parseIntLong.parseLong을 구분해야 합니다.

계산 타입은 마지막 저장 타입만 보고 정해지지 않습니다.


연습 문제

MemberValueCatalog를 작성해 다음 값을 적절한 타입과 이름으로 선언하고 출력하세요.

  • 회원 나이 90세
  • 회원 식별자 9,000,000,000
  • 가입 전환율 82.5%
  • 활성 여부
  • 등급 문자 B
  • 이메일 literals@example.com

큰 정수 리터럴과 변수 이름의 단위가 코드에 드러나야 합니다.

해설 보기
src/MemberValueCatalog.java
public final class MemberValueCatalog {
    public static void main(String[] args) {
        int age = 90;
        long memberId = 9_000_000_000L;
        double signupRate = 82.5;
        boolean active = true;
        char grade = 'B';
        String email = "literals@example.com";

        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);
    }
}

signupRate가 퍼센트 값 82.5를 담는다는 계약을 이름과 출력 라벨이 함께 보여 줍니다.

0.825 비율을 선택한다면 이름을 signupRatio로 바꾸어 단위를 드러냅니다.