연산자와 오버플로 문서의 구현 코드다.
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 04_java-operators_impl.java
// 연산자와 오버플로 문서의 출력과 값을 확인한다.
class OperatorsImpl {
static int calls = 0;
static void check(boolean ok, String what) {
if (!ok) throw new AssertionError(what);
}
static boolean touch(boolean v) {
calls++;
return v;
}
public static void main(String[] args) {
// 1. 부호, 증감
int x = 10;
check(-x == -10 && +x == 10, "부호");
int y = x++; // 먼저 쓰고 나중에 올린다
check(y == 10 && x == 11, "x++");
int z = ++x; // 먼저 올리고 쓴다
check(z == 12 && x == 12, "++x");
int a = 10;
int b = a++ + ++a; // 10 + 12
check(b == 22 && a == 12, "a++ + ++a = 22");
int k = 5;
int m = k-- - --k; // 5 - 3
check(m == 2 && k == 3, "k-- - --k = 2");
// 2. 산술
check(7 / 2 == 3 && 7 % 2 == 1, "7/2, 7%2");
check(-7 / 2 == -3 && -7 % 2 == -1, "-7/2, -7%2");
check(7 % -2 == 1, "7 % -2 = 1");
check(7 / 2.0 == 3.5, "7/2.0");
// 3. 오버플로와 언더플로: 끝을 넘으면 반대쪽 끝으로 돈다
byte bb = 127;
bb++;
check(bb == -128, "byte 127 + 1 = -128");
bb--;
check(bb == 127, "byte -128 - 1 = 127");
check(Integer.MAX_VALUE + 1 == Integer.MIN_VALUE, "int 최댓값 + 1");
check(Integer.MIN_VALUE - 1 == Integer.MAX_VALUE, "int 최솟값 - 1");
check(1000000 * 1000000 == -727379968, "int 곱셈 오버플로");
check(1000000L * 1000000 == 1000000000000L, "long으로 계산하면 맞다");
check(Math.abs(Integer.MIN_VALUE) == Integer.MIN_VALUE, "abs(최솟값)은 음수 그대로");
boolean threw = false;
try {
Math.addExact(Integer.MAX_VALUE, 1);
} catch (ArithmeticException e) {
threw = true;
}
check(threw, "addExact는 넘치면 예외를 낸다");
// 4. 정확한 계산 (문서의 사과 예제)
int apple = 1;
double pieceUnit = 0.1;
int number = 7;
double result = apple - number * pieceUnit;
check(String.valueOf(result).equals("0.29999999999999993"), "1 - 7*0.1 = " + result);
int totalPieces = apple * 10;
int remain = totalPieces - number;
check(String.valueOf(remain / 10.0).equals("0.3"), "정수로 바꿔 계산하면 0.3");
check(0.1 + 0.2 != 0.3, "0.1 + 0.2 는 0.3과 다르다");
check(String.valueOf(0.1 + 0.2).equals("0.30000000000000004"), "0.1 + 0.2");
// 5. 0으로 나누기
threw = false;
try {
int q = 5 / (x - x);
} catch (ArithmeticException e) {
threw = e.getMessage().equals("/ by zero");
}
check(threw, "정수 / 0 은 ArithmeticException: / by zero");
threw = false;
try {
int q = 5 % (x - x);
} catch (ArithmeticException e) {
threw = true;
}
check(threw, "정수 % 0 도 ArithmeticException");
double inf = 5 / 0.0;
double nan = 5 % 0.0;
check(Double.isInfinite(inf) && String.valueOf(inf).equals("Infinity"), "5 / 0.0 = Infinity");
check(Double.isNaN(nan) && String.valueOf(nan).equals("NaN"), "5 % 0.0 = NaN");
check(String.valueOf(-5 / 0.0).equals("-Infinity"), "-5 / 0.0 = -Infinity");
check(Double.isNaN(0.0 / 0.0), "0.0 / 0.0 = NaN");
check(Double.isInfinite(5.0 / 0), "5.0 / 0 = Infinity (0이 0.0으로 바뀐다)");
check(Double.isInfinite(inf + 2), "Infinity + 2 = Infinity");
check(Double.isNaN(nan + 2), "NaN + 2 = NaN");
check(Double.isNaN(inf - inf), "Infinity - Infinity = NaN");
check(!(nan == nan), "NaN == NaN 은 false");
check(nan != nan, "NaN != NaN 은 true");
// 6. 비교
check('A' < 'B', "'A' < 'B' (65 < 66)");
check(10 == 10.0, "10 == 10.0 (int가 double로 바뀐다)");
check(!(0.1 == 0.1f), "0.1 == 0.1f 는 false");
check((float) 0.1 == 0.1f, "(float)0.1 == 0.1f 는 true");
// 7. 논리
check((true ^ true) == false && (true ^ false) == true, "XOR");
check(!true == false, "NOT");
calls = 0;
boolean r1 = touch(false) && touch(true); // 앞이 false면 뒤를 보지 않는다
check(!r1 && calls == 1, "&&는 뒤를 건너뛴다");
calls = 0;
boolean r2 = touch(false) & touch(true); // &는 둘 다 본다
check(!r2 && calls == 2, "&는 둘 다 계산한다");
calls = 0;
boolean r3 = touch(true) || touch(false);
check(r3 && calls == 1, "||는 앞이 true면 뒤를 건너뛴다");
int n = 0;
boolean safe = (n != 0) && (10 / n > 1); // 뒤를 계산하지 않아 예외가 없다
check(!safe, "&&로 0 나눗셈을 막는다");
// 8. 대입
int v = 10;
v += 5; check(v == 15, "+=");
v -= 3; check(v == 12, "-=");
v *= 2; check(v == 24, "*=");
v /= 5; check(v == 4, "/=");
v %= 3; check(v == 1, "%=");
byte sb = 10;
sb += 5; // sb = (byte)(sb + 5) 와 같다. sb = sb + 5 는 컴파일 오류
check(sb == 15, "byte += 는 된다");
sb += 120;
check(sb == -121, "복합 대입은 넘쳐도 조용히 자른다");
int p, q2;
p = q2 = 5; // 오른쪽부터
check(p == 5 && q2 == 5, "a = b = 5");
// 9. 삼항
int score = 85;
char grade = (score > 90) ? 'A' : ((score > 80) ? 'B' : 'C');
check(grade == 'B', "85점은 B");
int max = (3 > 7) ? 3 : 7;
check(max == 7, "큰 값 고르기");
// 10. 우선순위와 방향
check(1 + 2 * 3 == 7, "곱셈 먼저");
check((1 + 2) * 3 == 9, "괄호 먼저");
check(10 - 3 - 2 == 5, "왼쪽부터");
check(100 / 10 * 2 == 20, "같은 순위는 왼쪽부터");
int s = 3, t = -2;
check((s > 0 && t < 0) == true, "비교가 && 보다 먼저");
check((true || false && false) == true, "&&가 || 보다 먼저");
check(((true || false) && false) == false, "괄호로 순서를 바꾸면 false");
int w = 2;
int e = -w * w + 10 % 3 << 1; // ((-w)*w + (10%3)) << 1 = (-4 + 1) << 1 = -6
check(e == -6, "카드: " + e);
int c3 = 3;
int r5 = c3++ * 2 + --c3; // 3*2 + (4-1=3) = 9
check(r5 == 9 && c3 == 3, "카드: " + r5);
// 11. 삼항 연산자는 오른쪽부터 묶인다: a ? b : (c ? d : e)
boolean ta = true, tb = false, tc = true, td = true, te = true;
boolean right = ta ? tb : (tc ? td : te); // false
boolean left = (ta ? tb : tc) ? td : te; // true
boolean plain = ta ? tb : tc ? td : te;
check(right == false && left == true && plain == right, "삼항은 오른쪽부터 묶인다");
System.out.println("ALL CHECKS PASSED");
}
}