为什么 BigInteger 不是原始类型

Why isn't BigInteger a primitive

如果您使用BigInteger(或BigDecimal)并想对其进行运算,则必须使用addsubtract等方法。这听起来不错,直到你意识到这

 i += d + p + y;

对于 BigInteger 会这样写:

 i = i.add(d.add(p.add(y)));

如您所见,第一行更容易阅读。如果 Java 允许运算符重载但不允许,则可以解决这个问题,所以这引出了一个问题:

为什么 BigInteger 不是原始类型,以便它可以利用与其他原始类型相同的运算符?

intbooleanchar 不是原语,因此您可以利用 +/ 等运算符。由于历史原因,它们是原语,其中最大的原因是性能。

在Java中,原语被定义为那些不是完整对象的东西。为什么要创建这些不寻常的结构(然后将它们重新实现为适当的对象,如 Integer,稍后)?主要是为了性能:对对象的操作过去(现在)比对原始类型的操作慢。 (正如其他答案所提到的,硬件支持使这些操作更快,但我不同意硬件支持是原语的 "essential property"。)

所以有些类型收到了 "special treatment"(并作为基元实现),而其他类型则没有。可以这样想:如果连广受欢迎的 String 都不是原始类型,为什么 BigInteger 会是?

原始类型通常是由处理器体系结构定义的历史类型。这就是为什么 byte 是 8 位的,short 是 16 位的,int 是 32 位的,long 是 64 位的。也许当有更多的 128 位架构时,将创建一个额外的原语......但我看不到有足够的驱动器......

那是因为 BigInteger 实际上不是任何接近原始的东西。它是使用数组和一些额外的字段来实现的,各种操作包括复杂的操作。例如,这里是 add:

的实现
public BigInteger add(BigInteger val) {
    if (val.signum == 0)
        return this;
    if (signum == 0)
        return val;
    if (val.signum == signum)
        return new BigInteger(add(mag, val.mag), signum);

    int cmp = compareMagnitude(val);
    if (cmp == 0)
        return ZERO;
    int[] resultMag = (cmp > 0 ? subtract(mag, val.mag)
                       : subtract(val.mag, mag));
    resultMag = trustedStripLeadingZeroInts(resultMag);

    return new BigInteger(resultMag, cmp == signum ? 1 : -1);
}

Java 中的原语是通常由主机的 CPU 直接实现的类型。例如,每台现代计算机都有用于整数加法的机器语言指令。因此它在JVM中也可以有非常简单的字节码。

BigInteger这样的复杂类型通常不能那样处理,也不能翻译成简单的字节码。它不能是原始的。


所以您的问题可能是 "Why no operator overloading in Java"。嗯,这是语言哲学的一部分。


为什么不破例,比如 String?因为不只是一个运营商是例外。 */+-<<^等运算符需要破例。而且您仍然会在对象本身中进行一些操作(例如 pow,它在 Java 中未由运算符表示),对于原语,这些操作由专业 类 处理(例如 Math).

从根本上说,因为 "primitive" 的非正式含义是可以用单个 CPU instruction. In other words, they are primitives because they fit in a 32 or 64 bits word, which is the data architecture that your CPU works with, so they can explicitely be stored in the registers.

直接处理的数据

因此您的 CPU 可以进行以下操作:

ADD REGISTER_3 REGISTER_2 REGISTER_1     ;;; REGISTER_3 = REGISTER_1 + REGISTER_2

一个可以占用任意大内存的BigInteger不能存储在单个REGISTER中,需要执行多条指令才能进行简单的求和。

这就是为什么它们不可能原始类型,而现在它们实际上是具有方法和字段的对象,比简单的原始类型复杂得多的结构。

注意:我之所以称其为 非正式 是因为最终 Java 设计师可以将 "Java primitive type" 定义为他们想要的任何东西,他们拥有词,然而这是模糊的约定用法。

这是因为基本类型有大小限制。例如 int 是 32 位,long 是 64 位。因此,如果您创建一个 int 类型的变量,JVM 会在堆栈上为其分配 32 位内存。但是对于 BigInteger,它 "theoretically" 没有大小限制。这意味着它的大小可以任意增长。正因为如此,没有办法知道它的大小并在栈上为它分配固定的内存块。因此它被分配在堆上,JVM 可以根据需要随时增加它的大小。