VB.NET이나 VBA에서 넘어오면 Java에서 제일 먼저 아쉬운 게 Select Case의 범위 조건입니다. VB에서는 Case 1 To 5처럼 범위를 그대로 쓸 수 있는데, Java의 switch는 정확히 같은 값(상수)만 비교할 수 있어서 "1에서 5 사이면" 같은 조건을 바로 표현할 수 없습니다. 이럴 때 범위 검사용 함수를 하나 만들어 if-else if로 나누면 읽기 좋은 코드가 됩니다.
public static void main(String[] args) {
int num=(int)(Math.random() * 11);
if (isBetween(num, 1, 5)) {
System.out.println("testing case 1 to 5");
} else if (isBetween(num, 6, 10)) {
System.out.println("testing case 6 to 10");
}
}
public static boolean isBetween(int x, int lower, int upper) {
return lower <= x && x <= upper;
}
코드가 하는 일
Math.random()은 0 이상 1 미만의 실수를 돌려줍니다. 여기에 11을 곱하고 (int)로 소수점을 버리면 0부터 10까지 정수 중 하나가 나옵니다. isBetween은 lower <= x && x <= upper, 즉 양 끝을 포함하는 범위 검사입니다. 그래서 1~5면 첫 번째 문장이, 6~10이면 두 번째 문장이 출력됩니다.
실행해 보면 한 번씩 아무것도 안 찍히고 끝날 때가 있는데, 버그가 아니라 0이 뽑힌 경우입니다. 0은 두 범위 어디에도 속하지 않으니까요. 범위 밖 값을 처리하고 싶다면 마지막에 else를 하나 붙여 두면 됩니다. 실무 코드에서는 이렇게 "어느 범위에도 안 걸리는 값"을 따로 처리해 두는 습관이 디버깅할 때 꽤 도움이 됩니다.
경계값을 어떻게 볼 것인가
이 함수는 양쪽 끝을 모두 포함합니다. 정수라면 문제가 없지만, 점수 구간이나 금액 구간처럼 실수를 다룰 때는 "5에서 6 사이의 5.5는 어디로 가나" 같은 빈틈이 생깁니다. 그런 경우에는 lower <= x && x < upper처럼 위쪽을 열린 구간으로 두고 구간을 [0, 60), [60, 70) 식으로 이어 붙이면 빠지는 값도 겹치는 값도 없습니다. 함수 이름도 isBetweenExclusive처럼 구분해 두면 나중에 헷갈리지 않습니다.
조건이 많아지면 if 사슬이 길어지는데, 구간이 일정한 간격이라면 나눗셈으로 구간 번호를 만들어 switch에 넣는 방법도 있습니다. 예를 들어 0~100점을 10점 단위로 나눈다면 score / 10의 값으로 case 9:, case 8: 처럼 분기할 수 있습니다. 간격이 불규칙하면 지금처럼 isBetween을 쓰는 쪽이 더 명확합니다.
참고로 Java 14 이후의 새 switch 문법에서도 범위 조건은 직접 지원하지 않습니다. 한 case에 여러 값을 쉼표로 나열하는 건 되지만 범위는 아니라서, 범위 분기는 여전히 이런 도우미 함수가 편합니다.
정수 말고 다른 타입에도 쓰고 싶다면
int 전용으로 만들면 long, double, 날짜마다 함수를 따로 만들어야 합니다. Comparable 을 받는 제네릭으로 바꾸면 LocalDate, BigDecimal, String 까지 하나로 처리됩니다.
public static <T extends Comparable<T>> boolean isBetween(T x, T lower, T upper) {
return x.compareTo(lower) >= 0 && x.compareTo(upper) <= 0;
}
isBetween(LocalDate.now(), start, end) 처럼 기간 안에 드는지 확인할 때 특히 편합니다. BigDecimal 은 equals 가 소수 자릿수까지 비교해서 1.0 과 1.00 을 다르게 보는데, compareTo 는 값만 비교하니 이 함수에서는 그런 문제가 없습니다. 기본형 int 를 넘기면 Integer 로 자동 변환되어 그대로 쓸 수 있습니다.
구간이 많으면 TreeMap으로
등급표처럼 구간이 여러 개이고 간격이 제각각이면, 구간 시작값을 키로 넣은 TreeMap 의 floorEntry 가 깔끔합니다. 주어진 값 이하에서 가장 큰 키를 찾아 주니 if 사슬 없이 한 줄로 구간을 고를 수 있습니다.
TreeMap<Integer, String> grade = new TreeMap<>();
grade.put(0, "F");
grade.put(60, "D");
grade.put(70, "C");
grade.put(80, "B");
grade.put(90, "A");
String g = grade.floorEntry(85).getValue(); // "B"
가장 작은 키보다 작은 값(여기서는 음수)을 넣으면 floorEntry 가 null 을 돌려주니 그 경우는 따로 막아야 합니다. 위 예제의 난수도 ThreadLocalRandom.current().nextInt(0, 11) 로 쓰면 형 변환 없이 0~10 사이 정수를 바로 얻을 수 있습니다. 두 번째 인자는 포함되지 않는 상한입니다.