本文参考自Java编程思想第四版,并结合自己现有知识做的一些总结。
在最底层,Java中的数据都是通过使用操作符来操作的。
Java中大多数操作符的使用都与C一致,但同时Java也做了一些改进和优化。在本文中,我将结合书中所读给大家分享一下我认为的Java中使用操作符的注意点和技巧。
1. 优先级
当一个表达式中存在多个操作符时,操作符的优先级就决定了各部分的计算顺序。Java中完整的运算符优先级规则很多,我们只需要记住最简单常用的——先乘除后加减,先算数运算后按位运算,逻辑与(&&)>逻辑或(||),运算符优先级相同时先左后右。当遇到不确定谁该先计算的时候,不要节省括号的使用,把你想要先计算的表达式用括号括起来,记住()是第一优先级。
2. 赋值符号
赋值符号“=“,将右边的值赋给左边的值。
如下:
int a = 4;
int b = a;
float c = 3.0f;
float d = c;
这是最常见也是正确的写法,他不会带来任何相关的影响(如在后面的语句中改变a的值,不会影响b,改变c的值不会影响d),因为基本数据类型的值也存在于堆栈中,在赋值的时候,只是将右边的值内容复制一份,然后存储到左边的值中,二者在之后的语句中不会有相关影响。
但是在为对象“赋值”的时候,结果又与基本数据类型大不相同。
public class A{
int id;
public static void main(String[] args){
A a1 = new A();
A a2 = new A();
a1.id = 1;
a2.id = 2;
System.out.println(a1.id + " " + a2.id);
a1 = a2;
System.out.println(a1.id);
a1.id = 1;
System.out.println(a2.id);
}
}
输出:
1 2
2
1
在Java中,我们操作一个对象时,实际上操作对这个对象的引用。这个时候若将一个对象的引用赋值给另一个对象的引用,如上面代码中的a1 = a2;,两个不同的对象引用都指向了同一个实际的对象。这时,无论是利用哪个对象引用操作了实际对象,在另外一个对象引用操作实际对象时,该实际对象已经发生了改变。
因此在实际编码的时候,我们需要尽量减少这种显示的对象引用间的赋值。因为很可能在某一个地方你不小心改变了实际对象的值,当你用其它指向该实际对象的对象引用访问它时,你会觉得很奇怪(你没有意识到还有其它的对象引用指向到了它,并且操作会同步到这个实际对象上)。
3. 关系操作符
关系操作符包括:> < >= <= == !=,生成的值一个boolean数据。如果操作数之间的关系是真的,则返回true,否则返回false。
所有的关系操作符都可以作用于基本数据类型(boolean只能被==和!=作用),这些与我们在C语言中所学到的基本一致。需要注意的是,==和!=可以作用于两个对象,并且会产生让你觉得不正确的答案。
public class test2 {
public static void main(String[] args) {
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println(i1 == i2);
}
}
大家可以猜一下上面这段代码会输出什么,按照我们前面所学的知识以及常识来说,应该会输出true(因为它们实际的值相等),但事实上它输出了false。这是为什么呢?
关系操作符和!=作用两个对象时,实际上是比较这两个对象的引用是否相同。那如何知道两个对象的引用的是不相同的呢。还记得我们在上一篇文章中所说的,对象的引用是存放在堆栈中的,声明了两个不同的对象引用,并且不是某一个对象引用指向了另一个对象引用,它们自然是不相同的。如果声明Integer i2 = i1;这时i1i2就会返回true。
如果想要比较两个对象的真实内容是否相等时,我们可以重写并调用equals方法(来自Object类)。Integer中已经重写了equals方法。
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
可以看出Integer类重写的equals方法是比较两个Integer对象的真实值是否相等。
public class test2 {
public static void main(String[] args) {
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println(i1.equals(i2));
}
}
输出:
true
**注意:**我们自己定义的类的equals方法是继承自Object类的,它默认还是比较对象的引用,如果想要通过其它方式比较两个对象是否相等,我们需要重写equals方法。
4. 逻辑操作符
逻辑操作符:&&(与),||(或),!(非)
注意:逻辑操作符只能作用于boolean值!!!
在C语言中
while(2){
}
是可以编译通过的,同时c语言中0可以表示false,1可以表示true,同时!0=1,这在Java中是不允许的,编译器会在编译期间直接报错。
下面是Java中逻辑操作符允许的一些操作:
if(true || false)
if(true && false)
if((2 > 1) || (1 > 2))
if((2 > 1) || (1 > 2))
if(!true)
if(!false)
.......
"短路"现象:
表达式1 || 表达式2 || 表达式3 || 表达式4 …
在表达式x为true时,表达式x+1到最后一个表达式就不会去计算了,因为最后结果一定为true
表达式1 && 表达式2 && 表达式3 && 表达式4 …
在表达式x为false时,表达式x+1到最后一个表达式就不会去计算了,因为最后结果一定为false
public class shorttest {
static boolean test1(int val){
System.out.println("test1");
return val > 1;
}
static boolean test2(int val){
System.out.println("test2");
return val > 2;
}
static boolean test3(int val){
System.out.println("test3");
return val > 3;
}
public static void main(String[] args) {
boolean b = test1(2) && test2(1) && test3(4);
}
}
如果没有短路现象的话,输出的值应该为
test1
test2
test3
但由于短路现象的存在,并且test2返回false,全是&&逻辑运算符,所以最后不会执行方法test3。
5. 直接常量
程序中有的直接常量是模棱两可的,这时我们需要对编译器加以适当的“指导”,用于给直接量添加一些额外信息。
long:2324l,2324L
float:2.0f,2.0F
double:2.0d,2.0D
八进制:0开头,后面跟0-7的数
十六进制:0x223abc 0X223ABC
// short占两个字节
short a = 0x1;
上述代码是可以通过编译的,你试想一下0x1这个常量会被识别为什么数据类型是short,还是识别为int然后截取为short(丢掉高24位)?这里我们无法得到答案。
char a = 0xffff;
short b = 0xffff;
这个时候,a是可以通过编译的,但b不行,这是为什么呢?二者明明都是2个字节。这就牵扯到了0xffff是被识别为char或short,还是先识别为int,再进行截取。
- 如果是识别为char或short,那么它刚好两个字节,在计算机中,常量用补码存在,0xffff对应的值为-1,那么按道理二者都是通过编译的。但又不对,Java中char支持的编码是Unicode编码,为此char是个无符号数,是不存在负号的。那么结果应该是a不能通过编译,b可以,这与实际结果相反,所有0xffff没有被识别为两个字节。
- 事实是会被识别为int,这时0xffff先被识别为0x0000ffff(对应的值为255),然后自动转化为两个字节(值还是255,由于char是无符号数,255刚好是它的最大值,而short就不行(255大于short的最大值),编译器要求你0xffff,进行强制转化(short b = (short) 0xffff;)。这刚好符合实际结果。
Java中浮点数的默认值是double
float = 1.0;是无法通过编译的,因为1.0的数据类型默认是double,double->float需要显示转化,或者我们可以写成float = 1.0f;
当算数操作符作用于byte,short,char时,会自动将它们的数据类型升到int,这个是否如果最终的值还希望并且确实是byte,short,char时,需要进行显示的数据类型转化,如下:
byte c = 1; byte d = 1; byte e = (byte) (c + d); byte e = (byte) (c - d); byte e = (byte) (c * d); byte e = (byte) (c / d); byte e = (byte) (c << d); byte e = (byte) (c >> d); byte e = (byte) (c & 1); byte e = (byte) (c | 1); byte e = (byte) (~c);
6. 位操作符
按位与:
同为1时为1,否则为0
00000011 3
&
00000010 2
=
00000010 2
按位或:
同为0时为0,否则为1
00000011 3
&
00000010 2
=
00000011 3
按位非:
0变1,1变0
~1
~00000001
11111110(负数,补码)
为得到源码,减1取反
-1=
11111101
取反=
10000010
结果=-2
左移:
低位补0
1 << 2;
00000001 << 2;
00000100
等于4
带符号右移:
高位补符号位的值
4 >> 1;
00000100 >> 1;
00000010
等于2
-4 >> 1;
11111100 >> 1;
11111110
等于
减一取反 ->11111101->10000010
-2
无符号右移:
无论符号位为什么,高位均补0
使用byte类型的数据进行无符号右移时,会先转化为int类型,然后移位,
如果最后要再赋值给byte类型的数据需要截断。(移动时符号位也会移动)。