为什么Java不支持无符号整型?

为什么Java不支持无符号整数?

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

此外,使用无符号整数可以是一种自我文档的形式,因为它们表明无符号整型所要保存的值永远不应该是负数。

最后,在某些情况下,无符号整数对于某些运算(如除法)更有效。

包含这些的缺点是什么?

123203 次浏览

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

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

这来自采访了高斯林等人,关于简单性:

高斯林:作为一名语言设计师,我现在并不认为自己是语言设计师,“简单”的真正含义是我能否期望J. Random Developer在他的头脑中掌握规范。这个定义说,例如,Java不是——事实上,很多这些语言最终都有很多极端情况,没有人真正理解的事情。问任何一个C开发人员关于unsigned的问题,很快你就会发现几乎没有C开发人员真正理解unsigned是怎么回事,什么是unsigned算术。像这样的东西使得C是复数的。我认为Java的语言部分非常简单。你得去查图书馆。

我听说它们将在接近最初的Java发行版时被包含。Oak是Java的前身,在一些规范文档中提到了使用值。不幸的是,这些都没有被引入Java语言。据任何人所知,可能由于时间限制,它们没有得到实施。

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

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

  • 通常,Java设计人员希望简化可用的数据类型
  • 对于日常用途,他们认为最常见的需求是有符号的数据类型
  • 为了实现某些算法,有时需要无符号算术,但是要实现这种算法的程序员也应该具备使用有符号数据类型进行无符号算术的知识

总的来说,我认为这是一个合理的决定。我可能会:

  • 使字节无符号,或者至少为这一数据类型提供了有符号/无符号的替代选项,可能使用不同的名称(使它有符号有利于一致性,但什么时候需要有符号字节?)
  • 不再使用“short”(你上次使用16位符号算术是什么时候?)

不过,只要稍加修饰,对32位以内的无符号值进行运算就不会太糟糕,而且大多数人不需要无符号64位除法或比较。

我认为Java是很好的,添加unsigned会使它变得复杂,没有太多好处。 即使使用简化的整数模型,大多数Java程序员也不知道基本的数字类型是如何行为的——只要阅读Java把戏这本书,就可以了解您可能持有的误解

至于实用的建议:

    如果你的值是任意大小,不适合int,使用long。 如果它们不适合long则使用BigInteger.

  • 只有在需要节省空间时,才对数组使用较小的类型。

  • 如果你正好需要64/32/16/8位,使用long/int/short/byte,不要担心符号位,除法、比较、右移和强制转换除外。

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

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

Java确实有unsigned类型,或者至少有一个:char是一个unsigned short类型。所以不管高斯林找什么借口,他都不知道为什么没有其他无符号类型。

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

这是一个古老的问题,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中,为LongInteger提供了新的api,它们在将longint值作为无符号值处理时提供了辅助方法。

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

此外,番石榴提供了许多辅助方法来处理整数类型,这有助于缩小由于缺乏对unsigned整数的本地支持而留下的差距。

以我之见,原因是他们太懒了,不愿执行/纠正这个错误。 暗示C/ c++程序员不理解unsigned,结构,联合,位标志…

如果你正在和一个基本的/bash/java程序员交谈,即将开始用C语言编程,没有任何真正的语言知识,或者你只是在说你自己的想法。;)

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

一个很好的例子是尝试使用无符号字节作为自旋转循环。 对于那些不理解最后一句话的人,你究竟是如何称自己为程序员的。< / p >

直流

JDK8中,确实有一些对它们的支持。

尽管有Gosling的担忧,但我们仍然可能看到Java对unsigned类型的完全支持。

我知道这个帖子太老了;然而,在Java 8及以后版本中,您可以使用int数据类型来表示一个无符号32位整数,其最小值为0,最大值为232−1。使用Integer类使用int数据类型作为无符号整数,并且静态方法如compareUnsigned()divideUnsigned()等已添加到Integer类中以支持无符号整数的算术操作。

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

事实上,在C中unsigned - int产生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 ...
}

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

因为n是无符号类型size_t,所以整个表达式n - (rays.size() - 1) / 2的计算结果为unsigned。该表达式的目的是为来自中间那条线的__abc0条射线的签署位置:来自左边那条线的第一条射线的位置为-1,右边那条线的位置为+1,等等。在取abs值并乘以delta角之后,我将得到__abc0射线与中间射线之间的角度。

不幸的是,对我来说,上面的表达式包含了邪恶的unsigned,它的计算结果不是-1,而是2^32-1。随后对double的转换密封了该错误。

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

我曾经和c++标准委员会的某个人一起上过一门c++课程,他暗示Java避免使用无符号整数的决定是正确的,因为(1)大多数使用无符号整数的程序也可以使用有符号整数,这在人们的思维方式方面更自然,(2)使用无符号整数会导致许多容易创建但难以调试的问题,例如整数算术溢出和在有符号和无符号类型之间转换时丢失重要位。如果你错误地使用有符号整数从0中减去1,它通常会更快地导致你的程序崩溃,并且比使用2^32 - 1更容易找到错误,编译器、静态分析工具和运行时检查必须假设你知道你在做什么,因为你选择使用无符号算术。此外,-1这样的负数通常可以代表一些有用的东西,比如一个字段被忽略/默认/未设置,而如果你使用unsigned,你必须保留一个特殊的值,如2^32 -1或类似的东西。

很久以前,当内存有限,处理器不能一次自动操作64位时,每一位都很重要,所以有符号字节与无符号字节或短字节实际上更重要,而且显然是正确的设计决策。如今,在几乎所有常规编程情况下,只使用带符号的int就足够了,如果您的程序确实需要使用大于2^31 - 1的值,您通常只需要一个较长的。一旦你进入了使用长整数的领域,就更难想出一个理由来解释为什么你真的不能用2^63 - 1个正整数。当我们使用128位处理器时,这个问题就更小了。

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

我对你的问题的回答是,Java希望它的所有基本类型:字节字符int应该分别被视为字节双字qword,就像在汇编中一样,并且Java操作符是签署对所有基本类型的操作符,除了字符,但只有在字符上,它们只有16位无符号。

因此静态方法假定为32位和64位的无符号操作

你需要final类,它的静态方法可以被无符号操作调用。

你可以创建这个最终类,给它起任何你想要的名字,并实现它的静态方法。

如果你不知道如何实现静态方法,那么这个链接可能会帮助你。

在我看来,Java是类似于c++的在所有,如果它既不支持无符号类型也不操作符重载,所以我认为Java应该被视为与c++和C完全不同的语言。

顺便说一下,这两种语言的名称也完全不同。

所以我不建议在Java中输入类似C的代码也不建议输入类似c++的代码,因为在Java中,你将无法在c++中做你想做的事情,也就是说,代码将不会继续像c++那样,对我来说,这样的代码很糟糕,在中间改变风格。

我建议也为有符号操作编写和使用静态方法,这样就不会在代码中看到有符号和无符号操作混合使用操作符和静态方法,除非在代码中只需要有符号操作,而且只使用操作符是可以的。

另外,我建议避免使用int基元类型,而分别使用双字qword,并且您将调用无符号操作和/或有符号操作的静态方法,而不是使用操作符。

如果你打算只做有符号的操作,并且只在代码中使用操作符,那么使用这些基元类型int是可以的。

实际上双字qword都存在于语言中,但你可以为它们创建新类,并且它们的实现应该非常容易:

只保存了基元类型,类双字只保存了基元类型int,类qword只保存了基元类型。现在,所有的无符号和有符号方法是静态的或非静态的,你可以在每个类中实现,即所有16位的无符号和有符号操作,通过在类中给出意义名称,所有32位的无符号和有符号操作,通过在双字类中给出意义名称,所有64位的无符号和有符号操作,通过在qword类中给出意义名称。

如果你不喜欢给每个方法取太多不同的名字,你总是可以在Java中使用重载,很高兴看到Java did也删除了它!

如果你想要8位有符号操作的方法而不是操作符,想要8位无符号操作的方法而根本没有操作符,那么你可以创建字节类(注意,第一个字母'B'是大写的,所以这不是基本类型字节)并在这个类中实现方法。

关于按值传递和按引用传递:

如果我没有错的话,就像在c#中一样,基元对象自然地通过值传递,但类对象自然地通过引用传递,这意味着字节双字qword类型的对象默认情况下将通过引用传递,而不是通过值传递。我希望Java像c#一样有结构体对象, 所以所有字节 双字和可以实现qword 结构体 ,所以默认情况下他们通过默认值而不是引用,就像任何结构体对象在c#中,像原始类型,按值传递,而不是通过引用默认情况下,但是,因为Java比c#和我们必须处理,然后只有类和接口,以引用的方式传递,而不是默认值。因此,如果你想通过值而不是引用传递字节双字qword对象,就像Java和c#中的任何其他类对象一样,你必须简单地使用复制构造函数,仅此而已

这是我能想到的唯一解决办法。我只是希望我可以将原始类型类型定义为word, dword和qword,但Java既不支持类型定义也不使用,不像c#支持使用,这相当于C的类型定义。

输出:

对于同一个位序列,可以以多种方式将它们打印出来:二进制、十进制(就像C printf中%u的含义)、八进制(就像C printf中%o的含义)、十六进制(就像C printf中%x的含义)和整数(就像C printf中%d的含义)。

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

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

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

这就是我所能回答你的问题并建议你的。

在“C”规范中,有一些因实用主义原因而被Java抛弃的珍宝,但随着开发人员的需求(闭包等),它们正在慢慢地回归。

我提到第一个是因为它和这个讨论有关;指针值对无符号整数算术的坚持。并且,与这个主题相关的是,在Java的Signed世界中维护Unsigned语义的困难。

我猜如果有人让Dennis Ritchie的另一个自我来建议Gosling的设计团队,他会建议给Signed's一个“无穷大的零”,这样所有的地址偏移请求都会先加上他们的algeaic RING SIZE来消除负值。

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

// ...
// 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永远不会从负索引中“获得”,即使恶意请求者试图这样做。记住,还有许多合法的请求用于请求先前的(负的)索引值。

注意:外层%模数去掉了对合法请求的引用,而内部%模数掩盖了明显的恶意,因为负数比-模数更负。如果这将出现在Java +..9 || 8+…+ spec,那么问题将真正成为一个“程序员不能“自我旋转”的错误”。

我相信所谓的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
}