394

为什么 Java 不包含对无符号整数的支持?

在我看来,这是一个奇怪的遗漏,因为它们允许人们编写不太可能在意外大输入时产生溢出的代码。

此外,使用无符号整数可以是一种自我证明的形式,因为它们表明无符号整数打算保存的值永远不会是负数。

最后,在某些情况下,无符号整数对于某些操作(例如除法)可能更有效。

包含这些有什么缺点?

4

16 回答 16

206

这是来自对Gosling 和其他人的采访,关于简单性:

Gosling:作为一名语言设计师,我现在并不认为自己是这样的,“简单”最终的真正含义是我能否期望 J. Random Developer 将规范牢记在心。该定义表明,例如,Java 不是——事实上,这些语言中的许多最终都会出现很多极端情况,即没有人真正理解的事情。向任何 C 开发人员询问有关无符号的问题,很快你就会发现几乎没有 C 开发人员真正了解无符号的情况,无符号算术是什么。这样的事情使 C 变得复杂。我认为 Java 的语言部分非常简单。您必须查找的库。

于 2009-01-10T01:43:35.853 回答
55

在字里行间阅读,我认为逻辑是这样的:

  • 一般来说,Java 设计者希望简化可用数据类型的全部内容
  • 对于日常目的,他们认为最常见的需求是签名数据类型
  • 为了实现某些算法,有时需要无符号算术,但是将实现此类算法的程序员也将具备“工作”的知识,以使用有符号数据类型进行无符号算术

大多数情况下,我会说这是一个合理的决定。可能,我会:

  • 使字节无符号,或者至少为这种数据类型提供了可能具有不同名称的有符号/无符号替代方案(使其有符号有利于一致性,但是您何时需要有符号字节?)
  • 取消了“short”(你最后一次使用 16 位有符号算术是什么时候?)

尽管如此,对不超过 32 位的无符号值的操作还算不错,而且大多数人不需要无符号的 64 位除法或比较。

于 2009-01-10T04:29:03.693 回答
22

这是一个较老的问题,pat 确实简要提到了 char,我只是想我应该为其他将在路上看到这个的人扩展这个。让我们仔细看看 Java 原始类型:

byte- 8 位有符号整数

short- 16 位有符号整数

int- 32 位有符号整数

long- 64 位有符号整数

char- 16 位字符(无符号整数)

虽然char不支持unsigned算术,但本质上可以将其视为unsigned整数。您必须将算术运算显式转换回char,但它确实为您提供了一种指定unsigned数字的方法。

char a = 0;
char b = 6;
a += 1;
a = (char) (a * b);
a = (char) (a + b);
a = (char) (a - 16);
b = (char) (b % 3);
b = (char) (b / a);
//a = -1; // Generates complier error, must be cast to char
System.out.println(a); // Prints ? 
System.out.println((int) a); // Prints 65532
System.out.println((short) a); // Prints -4
short c = -4;
System.out.println((int) c); // Prints -4, notice the difference with char
a *= 2;
a -= 6;
a /= 3;
a %= 7;
a++;
a--;

是的,没有对无符号整数的直接支持(显然,如果有直接支持,我不必将大部分操作转换回 char )。但是,肯定存在无符号原始数据类型。我也希望看到一个无符号字节,但我想将内存成本加倍,而使用 char 是一个可行的选择。


编辑

JDK8 提供了新的 API Long,它们在将和值视为无符号值Integer时提供了帮助方法。longint

  • compareUnsigned
  • divideUnsigned
  • parseUnsignedInt
  • parseUnsignedLong
  • remainderUnsigned
  • toUnsignedLong
  • toUnsignedString

此外,Guava提供了许多帮助方法来为整数类型做类似的事情,这有助于缩小由于缺乏对unsigned整数的原生支持而留下的差距。

于 2011-07-27T20:22:49.883 回答
17

Java 确实有无符号类型,或者至少有一种: char 是无符号短类型。所以无论高斯林抛出什么借口,这实际上只是他的无知,为什么没有其他无符号类型。

也短类型:短裤一直用于多媒体。原因是您可以在单个 32 位无符号长整数中拟合 2 个样本并矢量化许多操作。8 位数据和无符号字节也是如此。您可以将 4 或 8 个样本放入寄存器中以进行矢量化。

于 2009-10-12T07:20:10.270 回答
15

一旦有符号和无符号整数在表达式中混合,事情就会开始变得混乱,您可能丢失信息。将 Java 限制为已签名的整数只会真正解决问题。我很高兴我不必担心整个已签名/未签名的业务,尽管我有时确实会错过一个字节中的第 8 位。

于 2009-01-10T02:05:31.143 回答
13

http://skeletoncoder.blogspot.com/2006/09/java-tutorials-why-no-unsigned.html

这家伙说,因为 C 标准定义了涉及无符号和有符号整数的操作被视为无符号。这可能会导致负符号整数滚动到一个大的无符号整数,从而可能导致错误。

于 2009-01-10T01:42:05.060 回答
12

我认为 Java 本身就很好,添加 unsigned 会使它复杂化而没有太多收获。即使使用简化的整数模型,大多数 Java 程序员也不知道基本数字类型的行为 - 只需阅读《Java Puzzlers 》一书,看看你可能持有哪些误解。

至于实用建议:

  • 如果您的值有些随意大小并且不适合int,请使用long. 如果它们不适合long使用BigInteger

  • 当您需要节省空间时,仅对数组使用较小的类型。

  • 如果您恰好需要 64/32/16/8 位,请使用long/ int/ short/byte并停止担心符号位,除了除法、比较、右移和强制转换。

另请参阅有关“将随机数生成器从 C 移植到 Java”的答案。

于 2009-01-10T08:39:20.100 回答
8

我知道这篇文章太旧了;但是为了您的兴趣,在 Java 8 及更高版本中,您可以使用int数据类型来表示无符号的 32 位整数,其最小值为 0,最大值为 2 32 -1。使用Integer该类将int数据类型用作无符号整数,并且已将 等静态方法compareUnsigned()添加divideUnsigned()Integer该类中以支持无符号整数的算术运算。

于 2014-01-30T09:18:46.580 回答
6

使用JDK8,它确实对它们有一些支持。

尽管 Gosling 担心,但我们可能会看到 Java 完全支持无符号类型。

于 2013-02-24T15:30:09.313 回答
5

我听说它们将被包含在原始 Java 版本附近。Oak 是 Java 的前身,在一些规范文档中提到了 usigned 值。不幸的是,这些从未进入 Java 语言。据任何人都能够弄清楚他们只是没有得到实施,可能是由于时间限制。

于 2009-01-10T01:45:42.433 回答
5

我曾经和 C++ 标准委员会的某个人一起上过 C++ 课程,他暗示 Java 做出了正确的决定来避免使用无符号整数,因为 (1) 大多数使用无符号整数的程序在使用有符号整数时也能做得很好,这在就人们的思维方式而言,以及 (2) 使用无符号整数会导致许多易于创建但难以调试的问题,例如整数算术溢出和在有符号和无符号类型之间转换时丢失重要位。如果你错误地使用有符号整数从 0 中减去 1,它通常会更快地导致你的程序崩溃,并且比如果它环绕到 2^32 - 1 更容易找到错误,并且编译器和静态分析工具和运行时检查必须假设您知道自己在做什么,因为您选择使用无符号算术。还,

很久以前,当内存有限且处理器不能一次自动在 64 位上运行时,每一位都更重要,因此有符号字节与无符号字节或短路实际上更重要,显然是正确的设计决策。今天,在几乎所有常规编程案例中,仅使用带符号的 int 就绰绰有余了,如果您的程序确实需要使用大于 2^31 - 1 的值,那么您通常只需要 long 。一旦你进入使用长整数的领域,就更难想出一个你真的不能用 2^63 - 1 个正整数的原因。每当我们使用 128 位处理器时,问题就更小了。

于 2016-11-01T19:13:40.967 回答
2

您的问题是“为什么 Java 不支持无符号整数”?

我对你的问题的回答是 Java 希望它的所有原始类型:bytecharshortintlong应该分别被视为byteworddwordqword,就像在汇编中一样,并且 Java 运算符是有符号的对除char之外的所有原始类型的操作,但仅在char上,它们仅是无符号的 16 位。

所以静态方法也假设是32 位和 64 位的无符号操作。

您需要最终类,可以为无符号操作调用其静态方法。

您可以创建这个最终类,将其命名为您想要的任何名称并实现它的静态方法。

如果您不知道如何实现静态方法,那么此链接可能会对您有所帮助。

我看来,如果Java既不支持无符号类型也不支持运算符重载,Java 与 C++ 一点也不相似,所以我认为 Java 应该被视为与 C++ 和 C 完全不同的语言。

顺便说一句,语言的名称也完全不同。

所以我不建议在 Java 中键入类似于 C 的代码,我根本不建议键入类似于 C++ 的代码,因为在 Java 中你将无法在 C++ 中做你接下来想做的事情,即代码将不会继续像 C++ 一样,对我来说,这样的代码是不好的,改变中间的样式。

我建议也为签名操作编写和使用静态方法,因此您不会在代码中看到用于签名和未签名操作的运算符和静态方法的混合,除非您只需要代码中的签名操作,并且可以仅使用运算符。

此外,我建议避免使用shortintlong原始类型,并分别使用worddwordqword,并且您将调用静态方法来进行无符号操作和/或有符号操作,而不是使用运算符。

如果您打算只进行有符号操作并且只在代码中使用运算符,那么可以使用这些原始类型shortintlong

实际上worddwordqword在语言中不存在,但是您可以为每个创建新类,并且每个的实现应该很容易:

word仅包含原始类型short,类dword仅包含原始类型int,类qword仅包含原始类型long。现在所有的无符号和有符号方法都是静态的或者不是你的选择,你可以在每个类中实现,即所有的 16 位操作都是无符号的和有符号的,通过在单词类上给出含义名称,所有的 32 位操作都是无符号和通过在dword类上给出含义名称进行签名,所有 64 位操作都通过在qword类上给出含义名称来进行无符号和有符号操作。

如果您不喜欢为每个方法提供太多不同的名称,您总是可以在 Java 中使用重载,很高兴看到 Java 也没有删除它!

如果您想要方法而不是用于 8 位有符号操作的操作符和用于完全没有操作符的 8 位无符号操作的方法,那么您可以创建Byte类(请注意,第一个字母“B”是大写的,所以这不是原始类型byte)并实现此类中的方法。

关于值传递和引用传递:

如果我没记错的话,就像在 C# 中一样,原始对象自然是按值传递的,但类对象自然是按引用传递的,这意味着Byteworddwordqword类型的对象将按引用而不是按值传递默认情况下。我希望 Java 有C#的struct对象,所以所有Byteworddwordqword都可以实现为struct而不是class, 所以默认情况下它们是按值传递而不是默认通过引用传递的,就像 C# 中的任何结构对象一样,像原始类型一样,默认情况下是按值传递而不是按引用传递,但是因为 Java 比 C# 差,我们有为了解决这个问题,只有类和接口,默认情况下通过引用而不是值传递。因此,如果您想通过值而不是通过引用传递Byteworddwordqword对象,就像 Java 和 C# 中的任何其他类对象一样,您将不得不简单地使用复制构造函数,仅此而已。

这是我能想到的唯一解决方案。我只是希望我可以将原始类型定义为 word、dword 和 qword,但 Java 既不支持 typedef 也不支持 using,这与支持using的 C# 不同,这相当于 C 的 typedef。

关于输出:

对于相同的位序列,您可以通过多种方式打印它们:二进制、十进制(如 C printf 中 %u 的含义)、八进制(如 C printf 中 %o 的含义)、十六进制(如C printf 中 %x 的含义)和整数(类似于 C printf 中 %d 的含义)。

请注意,C printf 不知道作为参数传递给函数的变量的类型,因此 printf 仅从传递给函数的第一个参数的 char* 对象中知道每个变量的类型。

所以在每个类:Byteworddwordqword中,您可以实现 print 方法并获得 printf 的功能,即使该类的原始类型是有符号的,您仍然可以通过遵循一些涉及的算法将其打印为无符号逻辑和移位操作以获取要打印到输出的数字。

不幸的是,我给您的链接没有显示如何实现这些打印方法,但我相信您可以通过谷歌搜索实现这些打印方法所需的算法。

这就是我能回答你的问题并建议你的全部内容。

于 2017-08-06T20:44:51.117 回答
1

因为unsigned类型是纯粹的邪恶。

unsigned - int在 C 中产生的事实unsigned更加邪恶。

这是不止一次让我着迷的问题的快照:

// We have odd positive number of rays, 
// consecutive ones at angle delta from each other.
assert( rays.size() > 0 && rays.size() % 2 == 1 );

// Get a set of ray at delta angle between them.
for( size_t n = 0; n < rays.size(); ++n )
{
    // Compute the angle between nth ray and the middle one.
    // The index of the middle one is (rays.size() - 1) / 2,
    // the rays are evenly spaced at angle delta, therefore
    // the magnitude of the angle between nth ray and the 
    // middle one is: 
    double angle = delta * fabs( n - (rays.size() - 1) / 2 ); 

    // Do something else ...
}

你注意到这个错误了吗?我承认我只是在使用调试器后才看到它。

因为n是无符号类型size_t,所以整个表达式的n - (rays.size() - 1) / 2计算结果为unsigned。该表达式旨在成为中间光线的有符号位置n:左侧中间光线的第一条光线的位置为-1,右侧的第一条光线的位置为+1,依此类推。取 abs 值并乘以delta角度,我会得到n光线和中间光线之间的角度。

对我来说不幸的是,上面的表达式包含邪恶的无符号,而不是评估为 -1,而是评估为 2^32-1。随后的转换以double密封该错误。

在由于误用算术引起的一两个错误之后,unsigned人们不得不开始怀疑获得的额外位是否值得额外的麻烦。我正在尽可能地避免unsigned在算术中使用任何类型,尽管仍然将它用于非算术运算,例如二进制掩码。

于 2016-02-02T22:36:56.023 回答
1

Java 出于实用的原因放弃了“C”规范中的一些宝石,但随着开发人员的需求(闭包等),这些宝石正在慢慢回升。

我提到第一个是因为它与这个讨论有关;指针值对无符号整数算术的遵守。并且,关于这个线程主题,在 Java 的签名世界中维护无符号语义的困难。

我猜如果有人要让 Dennis Ritchie 改变自我来建议 Gosling 的设计团队,它会建议给 Signed 一个“无穷处为零”,这样所有地址偏移请求都会首先添加它们的 ALGEBRAIC RING SIZE 以避免负值。

这样,在数组中抛出的任何偏移量都不会生成 SEGFAULT。例如,在一个封装类中,我称之为需要无符号行为的双精度环数组 - 在“自旋转循环”上下文中:

// ...
// Housekeeping state variable
long entrycount;     // A sequence number
int cycle;           // Number of loops cycled
int size;            // Active size of the array because size<modulus during cycle 0
int modulus;         // Maximal size of the array

// Ring state variables
private int head;   // The 'head' of the Ring
private int tail;   // The ring iterator 'cursor'
// tail may get the current cursor position
// and head gets the old tail value
// there are other semantic variations possible

// The Array state variable
double [] darray;    // The array of doubles

// somewhere in constructor
public RingArray(int modulus) {
    super();
    this.modulus = modulus;
    tail =  head =  cycle = 0;
    darray = new double[modulus];
// ...
}
// ...
double getElementAt(int offset){
    return darray[(tail+modulus+offset%modulus)%modulus];
}
//  remember, the above is treating steady-state where size==modulus
// ...

上面的 RingArray 永远不会从负索引中“获取”,即使恶意请求者试图这样做。请记住,还有许多合法请求要求提供先前(负)索引值。

注意:外部 %modulus 取消引用合法请求,而内部 %modulus 从比 -modulus 更负面的负面中掩盖了公然的恶意。如果这曾经出现在 Java +..+9 || 8+..+ 规范,那么问题将真正成为“无法“自转”故障的程序员”。

我确信所谓的Java unsigned int '缺陷'可以用上面的单线来弥补。

PS:只是为了给上面的 RingArray 管家提供上下文,这里有一个候选的 'set' 操作来匹配上面的 'get' 元素操作:

void addElement(long entrycount,double value){ // to be called only by the keeper of entrycount
    this.entrycount= entrycount;
    cycle = (int)entrycount/modulus;
    if(cycle==0){                       // start-up is when the ring is being populated the first time around
        size = (int)entrycount;         // during start-up, size is less than modulus so use modulo size arithmetic
        tail = (int)entrycount%size;    //  during start-up
    }
    else {
        size = modulus;
        head = tail;
        tail = (int)entrycount%modulus; //  after start-up
    }
    darray[head] = value;               //  always overwrite old tail
}
于 2019-06-27T15:03:37.720 回答
-2

我能想到一个不幸的副作用。在 Java 嵌入式数据库中,32 位 id 字段可以拥有的 id 数量是 2^31,而不是 2^32(约 20 亿,而不是约 40 亿)。

于 2009-01-11T23:56:55.957 回答
-8

恕我直言的原因是因为他们/太懒惰而无法实施/纠正该错误。建议 C/C++ 程序员不懂无符号、结构、联合、位标志……简直是荒谬。

以太您正在与即将开始编写 la C 语言的基本/bash/java 程序员交谈,没有任何真正的语言知识,或者您只是在胡说八道。;)

当您每天处理来自文件或硬件的格式时,您开始质疑,他们到底在想什么。

一个很好的例子是尝试使用无符号字节作为自旋转循环。对于那些不明白最后一句话的人,你到底如何称自己为程序员。

直流

于 2012-03-10T21:37:56.123 回答