Java表达式中的那些坑

【1】您确定真正了解后缀表达式与前缀表达式的区别吗?

public class IncrementDemo{
    public static void main(String[] args) {
        int i = 0;
        int j = 0;
        i = i++;
        j = ++j;
        System.out.printf("i=%d,j=%d \n",i,j);
    }
}

输出结果是什么呢?

关于前缀表达式与后缀表达式,JLS中是这样解释的:

1.The value of the postfix increment expression is the value of the variable before the new value is stored. JLS 15.14.2
2.The value of the postfix decrement expression is the value of the variable before the new value is stored. JLS 15.14.3
3.The value of the prefix increment expression is the value of the variable after the new value is stored.JLS 15.15.1tt
4.The value of the prefix decrement expression is the value of the variable after the new value is stored.JLS 15.15.2

翻译过来就是:
1.后缀表达式的返回值为新值被存储之前的变量的值
2.前缀表达式的返回值为新值被存储之后的变量的值
因此;上面的代码设计上可以理解为

public class IncrementDemo{
    public static void main(String[] args) {
        int i = 0;
        int j = 0;
        i = i++;//int temp = i;i =  i + 1;return temp;
        j = ++j;// j = j + 1;return j;
        System.out.printf("i=%d,j=%d \n",i,j);
    }
}

【2】 i%2 == 1 是什么梗?

在《Java解惑》中的第一个谜题就是判断一个数是否为奇数,想到这里,你可能马上会想起关于奇数的定义:

能被2整除的数是偶数,不能被2整除的数是奇数

你可能马上会写出这样的函数

public static boolean isOdd(int i){
    return i%2 == 1;
}

但是你看了片刻之后,好像记得曾经在哪里看过判断奇偶性有些坑,但是想不起为什么会有坑。
OK,关于数的取余操作确实有些与我们的常识有所出入的地方。
关于取余的操作规则,JLS中是这样规定的:

The remainder operation for operands that are integers after binary numeric promotion produces a result value such that (a/b)Xb+(a%b) is equal to a .JLS 15.17.3
It follows from this rule that the result of the remainder operation can be negative only if the dividend is negative, and can be positive only if the dividend is positive.JLS 15.17.3

按照规则,我们可以将 a%b 等效为 a - a / b X b;
从中我们可以看出 a%b 的结果的正负与 a 相同,所以一旦a为负数,那么该判断奇偶的方法就失效了。
所以正确的判断方法是:

public static boolean isOdd(int i){
    return i%2 != 0; // 或者 return (i & 1) !=0;
}

【3】 i += 1 ; 不等价于 i = i + 1;?

看到这里,你可能要骂爹了,明明没有却别的两个表达式怎么可能不等价呢?您不妨看一下下面的例子

public class OperatorDemo{
    public static void main(String[] args) {
        byte b1 = 1;
        byte b2 = 1;
        b1 += 1;
        b2 = b2 + 1;
        System.out.printf("b1 = %d , b2 = %d \n",b1,b2);
    }
}

当你尝试编译这个程序的时候发现它报错了。

OperatorDemo.java:7: 错误: 不兼容的类型: 从int转换到byte可能会有损失
        b2 = b2 + 1;
                ^

什么?b2 + 1 的结果为int类型,好吧,整数字面量的默认类型为 int 他可能自动转型了。
那你再试试这个程序

public class OperatorDemo{
    public static void main(String[] args) {
        byte b = 1;
        byte b1 = 1;
        byte b2 = 1;
        b1 += b;
        b2 = b2 + b;
        System.out.printf("b1 = %d , b2 = %d \n",b1,b2);
    }
}

这下他该没问题了吧,再编译运行一下看看,what! 怎么还是报错了?

这是什么鬼?
OperatorDemo.java:19: 错误: 不兼容的类型: 从int转换到byte可能会有损失
        b2 = b2 + b;
                ^

怎么两个byte相加结果还是为 int 呢?还有,为什么没有 b1 += b 与 b1 += 1一直没有报错呢?
好吧,这时我们我们还是得祭出我们的大杀器 JLS了。
在JLS中关于复合赋值语句是这样描述的

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. JLS 15.26.2

翻译过来就是复合赋值语句会隐式地对结果进行强转,b1 += 1 相当于 b1 = (byte) ( b1 + 1); 这下能够理解为什么 b1 += b 与 b1 += 1一直没有报错了吧,但是为什么两个byte类型的数字相加得到的是int类型的结果呢?别急,JLS中关于加法的运算规则是这样描述的

The binary + operator performs addition when applied to two operands of numeric type, producing the sum of the operands. Binary numeric promotion is performed on the operands.The type of an additive expression on numeric operands is the promoted type of its operands.JLS 15.18.2

怎么感觉在扯淡呢,二元 + 操作符在运用于数字类型的两个操作数时,会执行加法,并产生操作数的和,在操作数上会执行二元数字的提升,返回值类型是操作数提升后的类型。等等二元数字提升是什么鬼? 再找找喽。

When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

  1. If any operand is of a reference type, it is subjected to unboxing conversion
  2. Widening primitive conversion is applied to convert either or both
    operands as specified by the following rules:
    • If either operand is of type double , the other is converted to double .
    • Otherwise, if either operand is of type float , the other is converted to float .
    • Otherwise, if either operand is of type long , the other is converted to long .
    • Otherwise, both operands are converted to type int .
    After the conversion(s), if any, value set conversion is then applied to
    each operand.

Bingo,终于找到答案了!

  1. 如果两个操作数中只要有一个为double则另外一个就将转换为double,
  2. 否则如果两个操作数中有一个为float则另外一个就将转换为float,
  3. 否则如果两个操作数中有一个为long则另外一个就将转换为long,
  4. 否则,两个操作数都被转换为 int 类型
    看来这 byte、short、char 之间相加都会转换为 int 类型啊。 值得注意的是在 +、-、X、/、% 时都存在二元数字提升

OK,看到这里想必您应该解决了一些疑惑吧,但是还没完...

【4】某谭什么时候出了《Java语言程序设计》?

public class OperatorDemo{
    public static void main(String[] args) {
        int i = 1;
        int k = 1;
        int j = (i=2) *  i; //expression 1
        i = 1;
        i += (i=2) * i; //expression 2
        j = j + (j=2); //expression 3
        k += (k=4) * (k+2); //expression 4
        System.out.printf("i=%d,j=%d,k=%d",i,j,k); 
    }
}

怎么,某谭什么时候出了《Java语言程序设计》?好吧,博主也认为这一类型的题目简直是语言垃圾,谁特么会写这样的代码!
但是,很无奈,一些坑爹的考试偏偏喜欢考这种题目。好吧继续看吧。
我们都知道Java中,一般情况下表达式是自左向右进行计算的。

  1. 运算符右边的表达式肯定比右边先执行!
  2. 但是在复合赋值中,左端的值是在右端计算之前保存的。

1.The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated.
2.Value Of Left-Hand Side Of Compound Assignment Is Saved Before
Evaluation Of Right-Hand Side

expression 1

int j = (i=2) * i;

乘号左边的表达式为 (i=2)先执行,所以i变成了2 所以等效于 int j = 2 X 2 = 4;

expression 2

i += (i=2) * i;

乘号左边的表达式为 (i=2)先执行,所以i变成了2 所以等效于 i += 2 X 2; 但是 左端的值 i = 1 是在右端计算之前保存的,
所以结果为 i = 1+(2 X 2)=5

expression 3

j = j + (j=2);

加号右边的表达式为 (j=2)后执行,所以等效于 j = 4 + 2 = 6;

expression 4

k += (k=4) * (k+2);

乘号左边的表达式为 (k=4)先执行,所以k变成了4 表达式等效于 k += 4X(4+2); 但是 左端的值k = 1 是在右端计算之前保存的,
所以结果为 k = 1+4X(4+2)= 25
即输出

i=5,j=6,k=25

怎么样,现在你的一些疑惑应该解决了吧。

圉于博主的水平,理解可能有所偏差,还望各位大佬不吝指正!
参考:The Java Language Specification, Java SE 8 Edition

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容

  • text-align: center的作用是什么,作用在什么元素上?能让什么元素水平居中? 用于行内元素水平居中。...
    赫鲁晓夫的玉米棒子阅读 315评论 0 0
  • 潇湘馆前黛葬花,金玉良缘定生涯。 踏访古都四大家,兴衰荣辱尽桑麻。 红尘难抑随风沙,凭谁记取度年华。 文人墨客闲指...
    书袋和尚阅读 677评论 0 0
  • 应当\不应当立法禁止百度推广等竞价排名模式 立法 禁止 竞价排名 中国的舆论氛围好像早就成了百度过街人人喊打,每每...
    刘小博阅读 472评论 0 1