为什么 BigInteger 不是原始类型
Why isn't BigInteger a primitive
如果您使用BigInteger
(或BigDecimal
)并想对其进行运算,则必须使用add
或subtract
等方法。这听起来不错,直到你意识到这
i += d + p + y;
对于 BigInteger
会这样写:
i = i.add(d.add(p.add(y)));
如您所见,第一行更容易阅读。如果 Java 允许运算符重载但不允许,则可以解决这个问题,所以这引出了一个问题:
为什么 BigInteger
不是原始类型,以便它可以利用与其他原始类型相同的运算符?
int
和 boolean
和 char
不是原语,因此您可以利用 +
和 /
等运算符。由于历史原因,它们是原语,其中最大的原因是性能。
在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 可以根据需要随时增加它的大小。
如果您使用BigInteger
(或BigDecimal
)并想对其进行运算,则必须使用add
或subtract
等方法。这听起来不错,直到你意识到这
i += d + p + y;
对于 BigInteger
会这样写:
i = i.add(d.add(p.add(y)));
如您所见,第一行更容易阅读。如果 Java 允许运算符重载但不允许,则可以解决这个问题,所以这引出了一个问题:
为什么 BigInteger
不是原始类型,以便它可以利用与其他原始类型相同的运算符?
int
和 boolean
和 char
不是原语,因此您可以利用 +
和 /
等运算符。由于历史原因,它们是原语,其中最大的原因是性能。
在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 可以根据需要随时增加它的大小。