1. 概述

这篇快速入门教程中,我们将了解 Java 中的取模运算符(modulo operator)是什么,以及它在一些常见场景中的用法。

2. 取模运算符

我们先来看看 Java 中普通除法的一些局限性。

当两个操作数都是 int 类型时,除法的结果也是 int 类型,并且会丢失余数:

@Test
public void whenIntegerDivision_thenLosesRemainder() {
    assertThat(11 / 4).isEqualTo(2);
}

而如果我们把其中一个操作数改为 floatdouble 类型,结果就不同了:

@Test
public void whenDoubleDivision_thenKeepsRemainder() {
    assertThat(11 / 4.0).isEqualTo(2.75);
}

可以发现,在整数除法中我们丢失了余数。

这时候取模运算符 % 就派上用场了 —— 它返回的就是这个被丢弃的余数:

@Test
public void whenModulo_thenReturnsRemainder() {
    assertThat(11 % 4).isEqualTo(3);
}

⚠️ 在这个例子中,11(被除数)除以 4(除数)之后余数是 3。

和除法一样,如果右边的操作数为 0,取模也会抛出异常:

@Test(expected = ArithmeticException.class)
public void whenDivisionByZero_thenArithmeticException() {
    double result = 1 / 0;
}

@Test(expected = ArithmeticException.class)
public void whenModuloByZero_thenArithmeticException() {
    double result = 1 % 0;
}

也就是说,使用取模时也要避免除以零的情况。

3. 取模运算符的应用场景

取模运算符有很多实用场景。下面我们来看几个常见的用途。

⚠️ 注意:负数取模可能会得到负余数,这点要特别小心。

3.1. 常见应用场景

最常见的用途之一是判断一个数是奇数还是偶数。

任何数字对 2 取模的结果如果是 1,说明它是奇数:

@Test
public void whenDivisorIsOddAndModulusIs2_thenResultIs1() {
    assertThat(3 % 2).isEqualTo(1);
}

如果是 0,说明是偶数:

@Test
public void whenDivisorIsEvenAndModulusIs2_thenResultIs0() {
    assertThat(4 % 2).isEqualTo(0);
}

另一个典型应用是在循环数组中计算下一个可写入位置。

比如实现一个简单的循环队列(Circular Queue),元素存储在一个固定大小的数组中。

每次插入元素时,我们可以通过取模运算来计算下一个写入位置:

@Test
public void whenItemsIsAddedToCircularQueue_thenNoArrayIndexOutOfBounds() {
    int QUEUE_CAPACITY = 10;
    int[] circularQueue = new int[QUEUE_CAPACITY];
    int itemsInserted = 0;
    for (int value = 0; value < 1000; value++) {
        int writeIndex = ++itemsInserted % QUEUE_CAPACITY;
        circularQueue[writeIndex] = value;
    }
}

通过这种方式,我们确保 writeIndex 不会越界,也就不会出现 ArrayIndexOutOfBoundsException

不过要注意,一旦插入的元素数量超过 QUEUE_CAPACITY,新元素会覆盖旧数据。

3.2. 负数取模问题

⚠️ 当被除数是负数时,Java 的取模结果也可能是负数:

@Test
public void whenDividendIsNegativeAndModulusIs2_thenResultIsNegative() {
    assertEquals(-1, -9 % 2);
}

这可能不符合预期。我们可以手动调整为正数:

@Test
public void whenDividendIsNegativeAndRemainderIsCheckedForNegativeValue_thenResultIsPositive() {
    int remainder = -9 % 2;
    if (remainder < 0) {
        remainder += 2;
    }
    assertEquals(1, remainder);
}

这里我们检查余数是否小于 0,如果是,则加上除数使其变为正数。

✅ 更优雅的做法是使用 Math.floorMod() 方法:

@Test
public void whenDividendIsNegativeAndUsesMathClass_thenResultIsPositive() {
    int remainder = Math.floorMod(-9, 2);
    assertEquals(1, remainder);
}

该方法会自动处理负数情况,直接返回正确的正余数。

4. 总结

本文介绍了 Java 中的取模运算符 %,它用于获取整数除法的余数。

它可以用来做基础判断(如奇偶性),也可以用于更复杂的结构(如循环队列索引计算)。

虽然功能简单,但在实际开发中非常有用,尤其是在需要周期性行为或状态控制的场景下。

一如既往,示例代码可以在 GitHub 仓库 找到。


原始标题:The Modulo Operator in Java | Baeldung