直到今天,我都认为,例如:
只是一条捷径:
但如果我们尝试一下:
那么,i = i + j;将不编译,但i += j;将编译罚款。
这是否意味着,事实上,i += j;是实现这一目标的捷径?EDOCX1?3?
- 我很惊讶Java允许它,比它的前辈更严格的语言。铸造中的错误会导致严重的故障,就像Ariane5航班501的情况一样,64位浮点铸造到16位整数导致了崩溃。
- 在爪哇写的飞行控制系统中,这将是你最不担心的"@ SQLDiver"。
- 实际上,i+=(long)j;甚至可以编译。
- 我认为道格拉斯·克罗克福德最终赢得了这场争论…这种行为很糟糕,许多语言在转换方面都有类似的问题。
- @不过这是一致的,因为整个表达都被铸造了,所以它和i = (int)(i + (long)j);是一样的。
- 一组开发人员对准确性和易用性的不断推动真的很有趣。我们几乎需要两个版本的语言,一个非常精确,另一个易于使用。将Java从两个方向推到不适合任何一个组。
- @为什么罗斯德鲁不是个好主意?
- @你做了什么?你把j铸造成long,但是j已经是long,所以你的铸造没有任何作用。
- 如果需要铸造,你会把它放在哪里?i += (int) f;在添加前先铸造f,所以它不是等价的。(int) i += f;在赋值后强制转换结果,也不等价。没有地方可以放置一个表示您希望在添加之后、但在分配之前强制转换值的强制转换。
- "它的浮点不精确"让我们纠正->"浮点不精确"(Java遵循IEEE浮点标准)。如果您在计算时使用浮点,并且没有分析错误是否在应用程序所需的范围内,并且应用程序很关键,那么您应该受到责备,而不是语言。
像往常一样,对于这些问题,JLS持有答案。在这种情况下,§15.26.2复合分配运算符。摘录:
A compound assignment expression of the form E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)), where T is the type of E1, except that E1 is evaluated only once.
引用自§15.26.2的示例
[...] the following code is correct:
and results in x having the value 7 because it is equivalent to:
1 2
| short x = 3;
x = (short)(x + 4.6); |
换句话说,你的假设是正确的。
- 所以我自己检查的时候,i+=j编译,但会导致精度损失,对吗?如果是这样,为什么它不允许它也发生在i=i+j中?为什么要在那里打扰我们?
- @Ronnieaka:我猜语言设计者认为,在一种情况下(i += j,假设精度的损失是期望的,而不是另一种情况(i = i + j)是比较安全的。
- 不,就在那儿,在我前面!对不起,我之前没注意到。就像你的答案一样,E1 op= E2 is equivalent to E1 = (T)((E1) op (E2)),这有点像隐式的向下排字(从long到int)。而在i=i+j中,我们必须明确地这样做,即提供(T)在E1 = ((E1) op (E2))中的部分,不是吗?
- 我假设在将双字面添加到短字符的情况下,使用这种方法可以避免显式的强制转换和/或f后缀。
- 那么e1 op1 op2 op3=e2有效吗?等于e1=t(e1 op3(e1 op2(e1 op1 e2))?
- @汉斯:你不能在一个分配操作员的左边有几个"操作"…我相信这在捷豹路虎的其他地方都有
- Java编译器添加类型转换的一个可能原因是,如果您试图在不兼容类型上执行算术,则无法使用收缩的形式执行结果的类型化。结果的类型转换通常比有问题的参数的类型转换更准确。当使用不兼容的类型时,没有类型转换会使收缩无效,因为它总是会导致编译器抛出错误。
- 为什么3+4.6四舍五入为7,而不是8?
- 它不是圆形的。它被铸造(截断)
- 书中的"Java难题"(Joshua Bloch,Neal Gafter)的谜题9正好涵盖了这个问题。在解决方案中,作者强调了两个短语:"复合赋值表达式自动将其执行的计算结果转换为左侧变量的类型。"——"不要对byte、short或char类型的变量使用复合赋值运算符。"
- 这个问题在Java今天仍然存在(Java 8还是9)?
- @焦躁不安:我真的很希望JLS不会在那个水平上无与伦比地崩溃:),所以如果不尝试(你可以自己尝试),我会说是的,"问题"依然存在。
这种铸造的一个好例子是使用*=或/=
1 2 3
| byte b = 10;
b *= 5.7;
System. out. println(b ); // prints 57 |
或
1 2 3
| byte b = 100;
b /= 2.5;
System. out. println(b ); // prints 40 |
或
1 2 3
| char ch = '0';
ch *= 1.1;
System. out. println(ch ); // prints '4' |
或
1 2 3
| char ch = 'A';
ch *= 1.5;
System. out. println(ch ); // prints 'a' |
- @Sajaldutta我没有得到那个证明。介意解释一下吗?
- @阿克沙塔加瓦尔是个人物。65*1.5=97.5->明白吗?
- 是的,但是我可以看到一些初学者来到这里,阅读这个,然后离开,认为你可以通过将任何字符乘以1.5将其从大写转换为小写。
- @davidwallace任何字符,只要是A;)
- @彼得·劳里,我会透露你的秘密-EDOCX1[1]d
很好的问题。Java语言规范证实了您的建议。
For example, the following code is correct:
and results in x having the value 7 because it is equivalent to:
1 2
| short x = 3;
x = (short)(x + 4.6); |
- 或者更有趣:"int x=33333333;x+=1.0f;"。
- @超级卫星,这是什么诡计?一种不正确地四舍五入的扩大转换,然后加上一个实际上不会改变结果的加法,再次强制转换为int,以产生一个对普通人来说最意想不到的结果。
- @nexus:imho,转换规则应该将double->float视为加宽,基于float类型的值识别实数,而不是double类型的实数。如果将double视为完整的邮政地址,将float视为5位邮政编码,则可以满足给定完整地址的邮政编码请求,但不能准确地指定仅给定邮政编码的完整地址请求。将街道地址转换为邮政编码是一种有损操作,但是…
- …需要完整地址的人通常不会只要求邮政编码。从float->double转换相当于将美国邮政编码90210转换为"US Post Office,Beverly Hills CA 90210"。
对,
基本上当我们写作的时候
编译器将此转换为
我刚刚检查了.class文件代码。
真是个好消息
- 你能告诉我这是哪个类文件吗?
- @六分法:你所说的类文件是什么意思?如果您询问我在文章中提到的类文件,而不是Java类的已编译版本
- 哦,你提到了"类文件代码",这让我相信涉及到一个特定的类文件。我现在明白你的意思了。
- 在所有字母中,您实际上选择了"L"(小写L)…
- @Bogdan这不应该是正确使用字体的问题。选择错误字体进行编程的程序员应该清楚地考虑如何进行…
- @我不同意在这种情况下应该依靠字体来区分…但每个人都有选择最佳想法的自由。
- 在一些旧的matlab版本中,默认的字体大小是使o和0在Times New Roman以及1和l中看起来相同的大小。这始终是我将字体sie更改为有用的内容的第一步…
你需要从long到intexplicitly的转换,如果是i = i + l的话,它会编译并给出正确的输出。喜欢
或
1
| i = (int)((long)i + l); // this is what happens in case of += , dont need (long) casting since upper casting is done implicitly. |
但在+=的情况下,它工作得很好,因为运算符隐式地将类型从右变量类型转换为左变量类型,因此不需要显式转换。
- 在这种情况下,"隐式强制转换"可能是有损的。事实上,正如@lukaseder在他的回答中所说,对int的演员表是在+之后进行的。编译器会(应该?)如果真的把long投给int,就发出警告。
这里的问题涉及类型转换。
当你加上int和long时,
int对象被强制转换为long&both都被添加并且得到long对象。
但long对象不能隐式强制转换为int,因此,必须显式执行。
但是,+=的编码方式使得它可以进行类型转换。i=(int)(i+m)
在Java类型转换中,当赋值操作右侧的表达式的类型可以被安全地提升为赋值左侧的变量类型时,自动执行转换。因此,我们可以安全地分配:
1
| byte -> short -> int -> long -> float -> double. |
反过来,这也不会奏效。例如,我们不能自动将long转换为int,因为第一个函数比第二个函数需要更多的存储空间,因此信息可能会丢失。要强制进行这种转换,我们必须进行显式转换。类型-转换
- 嘿,但是long比float大2倍。
- 一个float不能保存所有可能的int值,一个double不能保存所有可能的long值。
- "安全转换"是什么意思?从后面的部分,我可以推断出您的意思是自动转换(隐式强制转换),这在float->long的情况下当然不是真的。float pi=3.14f;long b=pi;将导致编译器错误。
- 最好将浮点基元类型与整数基元类型区分开来。他们不是一回事。
- Java具有简单化的转换规则,需要在许多模式中使用强制转换,其中没有转换的行为会匹配期望值,但不需要在通常错误的许多模式中强制转换。例如,即使结果3333333332.0可能不是预期的结果(顺便说一句,33333334.0f的算术正确答案可以表示为float或int),编译器也会接受double d=33333333+1.0f;,而不会抱怨。
有时,这样的问题可以在面试时问。
例如,当你写:
1 2 3
| int a = 2;
long b = 3;
a = a + b; |
没有自动排版。在C++中,编译上述代码不会有任何错误,但是在爪哇中,你会得到一些类似EDCOX1×8的东西。
为了避免这种情况,您必须这样编写代码:
1 2 3
| int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting |
- 感谢您对EDCOX1 11在C++中的用法及其在Java中的使用的比较。我总是喜欢看到这些琐碎的事情,我认为它们确实为谈话做出了一些贡献,而这可能经常被忽视。
- 然而,这个问题本身很有趣,在采访中问这个问题是愚蠢的。它并不能证明这个人能够生成一个高质量的代码——它只是证明他有足够的耐心来准备甲骨文证书考试。并且通过使用危险的自动转换来"避免"不兼容的类型,从而隐藏可能的溢出错误,甚至可能证明此人无法生成可能的高质量代码。该死的Java作者为所有这些自动转换和自动装箱和所有!
主要的区别在于,对于a = a + b,没有进行任何类型转换,因此编译器会因为没有进行类型转换而生您的气。但是对于a += b,它真正要做的是将b打印成与a兼容的类型。所以如果你这样做
1 2 3 4
| int a =5;
long b =10;
a +=b ;
System. out. println(a ); |
你真正要做的是:
1 2 3 4
| int a =5;
long b =10;
a =a +(int)b ;
System. out. println(a ); |
- 复合赋值运算符执行二元运算结果的窄转换,而不是右操作数。因此,在您的示例中,"a+=b"不等于"a=a+(int)b",但正如这里其他答案所解释的那样,"a=(int)(a+b)"。
这里有点微妙…
当j是双精度的,i是int时,i+j有一个隐式类型转换。当它们之间有一个操作时,Java总是将一个整数转换成一个整数。
为了澄清i+=j中,i是整数,j是双精度数,可以将其描述为
1
| i = <int>(<double>i + j) |
参见:隐式强制转换的描述
为了清晰起见,您可能需要将j转换为(int)。
- 我认为一个更有趣的例子可能是int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;。在someInt中加零不应影响其价值,但将someInt推广到float中可能会改变其价值。
Java语言规范定义EDCOX1〔0〕相当于EDCOX1〔1〕,其中EDCOX1〔2〕是EDCX1〔3〕的一种类型,EDCOX1〔3〕被评价一次。
这是一个技术性的答案,但你可能想知道为什么会这样。好吧,让我们考虑下面的程序。
1 2 3 4 5 6 7 8
| public class PlusEquals {
public static void main (String[] args ) {
byte a = 1;
byte b = 2;
a = a + b ;
System. out. println(a );
}
} |
这个程序打印什么?
你猜到3了吗?太糟糕了,这个程序无法编译。为什么?那么,在Java中添加字节是为了返回EDCOX1×5的定义。我相信这是因为Java虚拟机没有定义字节操作来保存字节码(毕竟,这些字节数有限),相反,使用整数操作是用语言公开的实现细节。
但如果a = a + b不起作用,这意味着如果E1 += E2被定义为E1 = E1 + E2的话,a += b就永远不会对字节起作用。如前一个例子所示,情况确实如此。作为使+=运算符适用于字节和短路的黑客,涉及到隐式强制转换。这不是一个伟大的黑客,但在Java 1的工作回来,重点放在语言的发布开始。现在,由于向后兼容,在Java 1中引入的这个黑客无法被删除。