优先级
As the Java programmer's beginning
优先级从上到下递减,不要问为什么,因为这就像在问1加1为什么等于2一样,it's rules, OK. 规则由它所在的环境决定。在二进制的世界里,1加1等于10。
Description | Separators/Operators | Associativity |
---|---|---|
分隔符 | ( )、, 、. 、[ ]、{ }、... 、@、:: 、; | 左=>右 |
单目运算符 | !、~、+(正)、-(负)、+ +、- -、(Type) | 右=>左 |
算术 | *、/、% | 左=>右 |
算术 | +、- | 左=>右 |
移位 | <<、>>、>>> | 左=>右 |
关系 | >、<、>=、<=、instanceof | 左=>右 |
关系 | ==、!= | 左=>右 |
按位与 | & | 左=>右 |
按位异或 | ^ | 左=>右 |
按位或 | | | 左=>右 |
条件 | && | 左=>右 |
条件 | | | | 左=>右 |
三目 | ? : | 右=>左 |
赋值 | =、~=、*=、/=、+=、-=、&=、^=、|=、%=、<<=、>>=、>>>= | 右=>左 |
说实话,我到现在也记不住这些个东西,但同样能够“征战沙场”、“攻城略池”。但是,要想高效Coding,必须完全摸清这些基本的东西。
总式:单目 > 双目 > 三目 > 多目(赋值),优先级低的
先考虑
。
从表格中可以看到,优先级的区分重点集中在双目运算
。最后需要注意一下,条件与/或会短路
,移位/位运算的数字是以二进制
作为操作数的,还有那三类从右向左
的结合性。
//短路
if(23 > 24 && 100/0 == 0){
System.out.println("true");
}
//移位
int bitmask = 0x000F;
int val = 0x2222;
System.out.println(Integer.toBinaryString(val));
System.out.println(Integer.toBinaryString(bitmask));
System.out.println(Integer.toBinaryString(val & bitmask));
System.out.println(Integer.toBinaryString(val | bitmask));
System.out.println(Integer.toBinaryString(~val));
//位
int n = -10;
System.out.println(Integer.toBinaryString(n));
System.out.println(Integer.toBinaryString(n >> 3));
System.out.println(Integer.toBinaryString(n >>> 3));
遵循“优先级低的先考虑
”原则,运算才有意义。这里先考虑
的意思不一定等于先计算,这相当于先根据运算符的优先级从低到高找出来并根据结合性
和后算的先入栈的方法将操作数按顺序放入栈中,然后再一个一个弹出来运算。下面if包含的代码块中,添加了小括号方便阅读,去掉后结果不变。
//Test one
int num = 0;
StringBuilder sb = new StringBuilder();
if (((sb instanceof StringBuilder == num++ > 0) && (++num > 1)) || (num != 2)) {
System.out.println("true");
}
System.out.println("num = " + num);
//Test two
int num = 0;
StringBuilder sb = new StringBuilder();
if (((sb instanceof StringBuilder == num++ > 0) || (++num > 1)) && (num != 2)) {
System.out.println("true");
}
System.out.println("num = " + num);
//Test three
int num = 0;
StringBuilder sb = new StringBuilder();
if (sb instanceof StringBuilder ? false : (false == (((num++ > 0) && (++num > 1)) || (num != 2)))) {
int a = 4;
int b = 3;
int c = a > b ? ++b == b ? a++ : b : a;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
System.out.println("num = " + num);
先看Test one与Test two,它们只是条件与和或符号对调。if语句中优先级最低的都是条件或,以次为界,将表达式左右分开,并确认先算左边的式子(结合性)。Test one中左边的式子优先级最低的是条件与,同样以次为界,将表达式左右分开,并确认先算左边的式子(结合性),之后的同理即可。Test two同样以此类推。
Test three同样先在表达式中找出优先级最低的运算符,这里为三目。跟上面以结合性确认先算哪边的方式不同,这里多了一步,需要先算出?左边的真假,才能进一步确认接下来要进入冒号:左边的分支还是右边的分支。
基于栈的字节码指令集
float a = 1.234f;
int b = (int) -++a, c = -b + -(int) a++;
b = ++b;
c = c++;
System.out.println(b);
System.out.println(c);
如果能够算出正确答案,那么恭喜你,成功入门了。但是,我相信,大部分人靠的是“熟能生巧”。也就是说,只是记住了运算形式
。像这种“虚无缥缈”的东西,转瞬即逝。我们需要找到一种可靠的、看得见的一种概念模型
来进行辅助。Fortunately,Java编译器提供了这样的东西。用javap -v <class文件名>
命令查看。
到每行代表一条指令,包含操作码(一个字节),可能还有操作数(n个字节)。由于都是以
字节
为单位进行存储的,这些指令也叫“字节码”。
//对应b = ++b
22: iinc 2, 1
25: iload_2
26: istore_2
//对应c = c++
27: iload_3
28: iinc 3, 1
31: istore_3
上面的字节码结合下图👇可以很容易理解。上下两部分主要是对整数前后自增的分析。我们都知道,自增符号在前的会先加完再参与运算;反之则不参与。因此,b会加1,c保持不变。由字节码可以看出,两者的差异主要在“加载(iload
,i表示int)变量入栈”的时机,即在自增(iinc
)前还是自增后入栈的问题。入栈即表示参与运算,而在自增前入栈,那么自增的值不会影响之后的运算结果。
栈帧
...
//对应(int) -++a
3: fload_1
4: fconst_1
5: fadd
6: dup
7: fstore_1
8: fneg
9: f2i
...
//对应-b + -(int) a++
11: iload_2
12: ineg
13: fload_1
14: dup
15: fconst_1
16: fadd
17: fstore_1
18: f2i
19: ineg
20: iadd
...
浮点数与整数自增区别:前者通过
fconst_1
在栈中生成需要增加的浮点型常量,而后者直接在变量槽中自增。
虽然浮点数与整数的自增在实现方式上有区别,但前后自增的效果/差异与整数相同。唯一需要注意的地方是dup
指令。前自增:先加载(fload_1
)到栈中,栈中生成(fconst_1
)需要增加常量,将栈中的这两个数相加(fadd
),再用(dup
)生成跟这个相加结果一样的数并同样压入栈中。此时栈里有两个相同的数,栈顶的将弹出并存储到对应的变量槽中;剩下的将继续参与运算。后自增同理可知。
其实这些字节码流也真的只是个概念模型,到最后真正运行时并非如此,会经过优化,转成汇编代码来提高运行效率,只是结果都一样而已。
评论区