为什么Java不支持无符号整数?
在我看来,这是一个奇怪的遗漏,因为它们允许人们编写不太可能在意外的大输入上产生溢出的代码。
此外,使用无符号整数可以是一种自我文档的形式,因为它们表明无符号整型所要保存的值永远不应该是负数。
最后,在某些情况下,无符号整数对于某些运算(如除法)更有效。
包含这些的缺点是什么?
为什么Java不支持无符号整数?
在我看来,这是一个奇怪的遗漏,因为它们允许人们编写不太可能在意外的大输入上产生溢出的代码。
此外,使用无符号整数可以是一种自我文档的形式,因为它们表明无符号整型所要保存的值永远不应该是负数。
最后,在某些情况下,无符号整数对于某些运算(如除法)更有效。
包含这些的缺点是什么?
当前回答
因为无符号类型是纯粹的邪恶。
事实上,在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 - (ray .size() - 1) / 2的计算结果为无符号。该表达式旨在表示从中间那条线开始的第n条射线的符号位置:从左边那条线开始的第1条射线的位置为-1,右边那条线的位置为+1,等等。在取abs值并乘以角之后,我将得到第n条射线与中间那条射线之间的夹角。
不幸的是,对我来说,上面的表达式包含了邪恶的unsigned,它的计算结果不是-1,而是2^32-1。随后转换为双密封的bug。
由于滥用无符号算术而导致的一两个错误之后,人们不得不开始考虑获得的额外比特是否值得额外的麻烦。我正在尽可能地避免在算术中使用无符号类型,尽管仍然将它用于非算术操作,如二进制掩码。
其他回答
我能想到一个不幸的副作用。在java嵌入式数据库中,使用32位id字段可以拥有的id数量是2^31,而不是2^32(~ 20亿,而不是~ 40亿)。
在“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
}
字里行间,我认为逻辑是这样的:
通常,Java设计人员希望简化可用的数据类型 对于日常用途,他们认为最常见的需求是有符号的数据类型 为了实现某些算法,有时需要无符号算术,但是要实现这种算法的程序员也应该具备使用有符号数据类型进行无符号算术的知识
总的来说,我认为这是一个合理的决定。我可能会:
使字节无符号,或者至少为这一数据类型提供了有符号/无符号的替代选项,可能使用不同的名称(使它有符号有利于一致性,但什么时候需要有符号字节?) 不再使用“short”(你上次使用16位符号算术是什么时候?)
不过,只要稍加修饰,对32位以内的无符号值进行运算就不会太糟糕,而且大多数人不需要无符号64位除法或比较。
你的问题是“为什么Java不支持无符号整数”?
我对你的问题的回答是,Java希望它所有的基本类型:byte, char, short, int和long应该分别被视为字节,word, dword和qword,就像在汇编中一样,Java操作符对所有的基本类型都是有符号的操作,除了char,但只有在char上它们只能是无符号的16位。
因此静态方法对于32位和64位也是无符号操作。
您需要final类,它的静态方法可以用于无符号操作。
你可以创建这个最终类,给它起任何你想要的名字,并实现它的静态方法。
如果你不知道如何实现静态方法,那么这个链接可以帮助你。
在我看来,如果Java既不支持无符号类型,也不支持操作符重载,那么它一点也不类似于c++,所以我认为Java应该被视为与c++和C完全不同的语言。
顺便说一下,这两种语言的名称也完全不同。
所以我不建议在Java中输入类似C的代码也不建议输入类似c++的代码,因为在Java中,你将无法在c++中做你想做的事情,也就是说,代码将不会继续像c++那样,对我来说,这样的代码很糟糕,在中间改变风格。
我建议也为有符号操作编写和使用静态方法,这样就不会在代码中看到有符号和无符号操作混合使用操作符和静态方法,除非在代码中只需要有符号操作,而且只使用操作符是可以的。
此外,我建议避免使用short、int和long基元类型,分别使用word、dword和qword,并且您将调用无符号操作和/或有符号操作的静态方法,而不是使用操作符。
如果您打算只执行有符号的操作,并且只在代码中使用操作符,那么使用这些原语类型short、int和long是可以的。
实际上word, dword和qword在语言中不存在,但你可以为它们创建新类,并且它们的实现应该非常容易:
The class word holds the primitive type short only, the class dword holds the primitive type int only and the class qword holds the primitive type long only. Now all the unsigned and the signed methods as static or not as your choice, you can implement in each class, i.e. all the 16 bit operations both unsigned and signed by giving meaning names on the word class, all the 32 bit operations both unsigned and signed by giving meaning names on the dword class and all the 64 bit operations both unsigned and signed by giving meaning names on the qword class.
如果你不喜欢给每个方法取太多不同的名字,你可以在Java中使用重载,很高兴看到Java没有删除它!
如果您想要8位有符号操作的方法而不是操作符,或者想要8位无符号操作的方法而根本没有操作符,那么您可以创建Byte类(注意,第一个字母'B'是大写的,所以这不是基本类型Byte)并在该类中实现方法。
关于按值传递和按引用传递:
If I am not wrong, like in C#, primitive objects are passed by value naturally, but class objects are passed by reference naturally, so that means that objects of type Byte, word, dword and qword will be passed by reference and not by value by default. I wish Java had struct objects as C# has, so all Byte, word, dword and qword could be implemented to be struct instead of class, so by default they were passed by value and not by reference by default, like any struct object in C#, like the primitive types, are passed by value and not by reference by default, but because that Java is worse than C# and we have to deal with that, then there is only classes and interfaces, that are passed by reference and not by value by default. So if you want to pass Byte, word, dword and qword objects by value and not by reference, like any other class object in Java and also in C#, you will have to simply use the copy constructor and that's it.
这是我能想到的唯一解决办法。我只是希望我可以将原始类型类型定义为word, dword和qword,但Java既不支持typedef,也不支持using,不像c#支持using,这相当于C的typedef。
输出:
对于相同的比特序列,可以以多种方式打印它们:二进制、十进制(就像C printf中%u的含义)、八进制(就像C printf中%o的含义)、十六进制(就像C printf中%x的含义)和整数(就像C printf中%d的含义)。
请注意,C printf不知道作为参数传递给函数的变量的类型,因此printf只知道传递给函数第一个形参的char*对象中的每个变量的类型。
所以在每一个类:Byte, word, dword和qword中,你可以实现print方法并获得printf的功能,即使类的基本类型是有符号的,你仍然可以通过遵循一些涉及逻辑和移位操作的算法将其作为无符号打印到输出中。
不幸的是,我给你的链接没有显示如何实现这些打印方法,但我相信你可以谷歌的算法,你需要实现这些打印方法。
这就是我所能回答你的问题并建议你的。
这是一个古老的问题,pat确实简单地提到了char,我只是想我应该为其他人扩展这个问题,他们将在未来的道路上看到这个问题。让我们仔细看看Java的基本类型:
字节- 8位有符号整数
短16位有符号整数
Int - 32位有符号整数
长64位有符号整数
Char - 16位字符(无符号整数)
虽然char不支持无符号算术,但它本质上可以被视为无符号整数。您必须显式地将算术运算转换回char类型,但它确实提供了一种指定无符号数字的方法。
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是一个可行的选择。
Edit
JDK8为Long和Integer提供了新的api,在将Long和int值作为无符号值处理时提供了辅助方法。
compareUnsigned divideUnsigned parseUnsignedInt parseUnsignedLong remainderUnsigned toUnsignedLong toUnsignedString
此外,Guava提供了许多帮助器方法来处理整数类型,这有助于弥补由于缺乏对无符号整数的本机支持而留下的空白。