概述
操作符
操作符接受一个或多个参数,并生成一个新值. 换句话说操作符作用于操作数,生成一个新值.有些操作符会改变操作数自身.
这种效应我们叫做side effect.
赋值
赋值操作符”=”的意思是,取右值赋值给左值.
右值可以是任意的常数,变量或者表达式(只要它能生成一个值). 但是左值必须是一个明确的已命名的变量. 也就是说必须有物理空间可以存储右值.
赋值也就是值传递,无论任何数据类型都遵循这一规则,都是取右值复制给左值. 但是对于不同的数据类型,赋值会有两种神奇的效果:
- 对基本数据类型的赋值:
- 基本数据类型的赋值直接赋的是”值”,就是直接把一个地方的内容复制到了另外一个地方
- 对右值而言,没有任何改变和影响
- 对于引用类型的赋值:
- 引用类型的赋值实际赋的是对象的”引用”,赋值完成后,左值和右值都是同一个对象的引用.
- 对于之后的两个变量来说,实际指向了同一个对象所以都可以对该对象进行操作.
String类型是一种比较奇葩的类型. String s1="aaa";
首先String类型是不可变的类型. 比如这么一条语句,执行过程是先在常量池中开辟一块内存区域,存入”aaa” 然后将s1指向这块常量池中的这个区域.
所以对于下面这段代码,最后s1和s2分别会打印什么呢?
String s1="aaa";
String s2=s1;
s2="bbb";
s1是aaa,s2是bbb. 基础原则就是String类型是不可变的. 赋值过程很奇葩,就像上面说的一样.
关系操作符和逻辑操作符
关系操作符和逻辑操作符都会生成一个boolean值. 但是java中的布尔类型就是布尔类型,底层咋实现的我不知道,与C/C++中非0则true的实现不一样.
关系操作符中的等于==和不等于!=
在java中==操作符对于基本数据类型而言比较的是值,对于引用数据类型而言,比较的是”引用”.
@Test
public void equalTest(){
Integer i1= new Integer(47);
Integer i2 = new Integer(47);
System.out.println(i1==i2);//false
System.out.println(i1.equals(i2));//true
MyInteger myI1= new MyInteger(47);
MyInteger myI2 = new MyInteger(47);
System.out.println(myI1==myI2);//false
System.out.println(myI1.equals(myI2));//false
}
上面这段代码就很清楚了,对于引用类型而言,i1和i2虽然都是Integer并且值为47. 但是其所指向的引用是不一样的. 所以==返回false. 但是euqals方法却返回true.因为Integer类型重写了queals方法. 像下面这个我自己创建了一个类,没有重写equals方法,那么equals方法默认行为还是比较引用.
什么事儿都逃不过一个理字, 基础数据类型的值是直接存放在栈上的,首先它没有引用.
另外即使java中所谓的引用就是C/C++中的指针,甚至是内存地址(当然它不是),每个变量存放的地址是不一样的,那比较它是没有任何意义.
对于”类”类型. 比较其”值”是没有意义的. 堆中不同的两个内存区域,即使存放的内容一毛一样,那也还是两块内存区域. 再者说,对于”类”类型(暂且这样叫吧,我也不知道它该叫啥,精神领会),绝大部分是用户自己定义的,java编译器不是神仙,不可能知道你定义的”类”类型里到底是个啥,它也揣摩不透你的比较逻辑.所以,所有类的祖宗Object类中给出了一个可以让你重写的equals方法. 给了你足够的自由.
逻辑操作符
逻辑操作符就那仨,与或非. 由于java布尔类型的特殊实现, 逻辑操作符无法作用于布尔类型之外的任何数据类型.
如果是在应该使用String值的地方使用了布尔类型,那么布尔类型会转成适当的String类型.
关于逻辑操作符,有个乱路机制. 也就是说一旦可以明确无误的缺点一个表达式的值,就不会再计算后面的部分了.
public class ShortCircuit {
public static boolean fun1(int val){
System.out.println("fun1("+val+")");
System.out.println("result:" + (val>1));
return val>1;
}
public static boolean fun2(int val){
System.out.println("fun2("+val+")");
System.out.println("result:" + (val>4));
return val>4;
}
public static boolean fun3(int val){
System.out.println("fun3("+val+")");
System.out.println("result:" + (val>5));
return val>5;
}
}
@Test
public void logicalTest(){
int i =3;
boolean b = ShortCircuit.fun1(i)&&ShortCircuit.fun2(i)&&ShortCircuit.fun3(i);
System.out.println(b);
}
//output:
/*
fun1(3)
result:true
fun2(3)
result:false
false
*/
上面这例子可以看到,计算到fun2的时候,就可以确定这个逻辑表达式是false了. 所以就不会再往下计算了.
这让我想起来一件事儿,高中的时候,当我一本正经的在听老师讲题的时候. 同桌哥们在鼓捣其他事情. 我提醒他好好听讲,他问我,讲这题你会不.我说会呀. 他说,既然你会了干嘛还听它呢. 想想很有道理,于是打瞌睡去了…
当已经能明确确定这个表达式的值的时候,还往下计算,除了浪费资源之外还有什么用呢.
位操作符
位操作符按照剧本应该不会出现在java中,这种运算跟java屏蔽底层细节的画风严重不符,我也不明白它在java中有什么作用. 但是据说java当年是为了嵌入电视机机顶盒开发的,所以这种面向底层的操作还是被保留下来了.
移位操作符
- 左移<<,低位补0.
- 有符号右移>>,有符号的意思就是使用符号扩展,如果符号为正,则高位插入0 如果符号为负,则高位插入1;
- 无符号右移>>>,无论符号正负,高位都插入0
对于char,byte和short类型,在移位操作前都会被转换成int类型.
原码,反码,补码
整数在计算机中的存储采用反码的形式.为什么采用反码呢,因为在使用反码的时候,计算逻辑是最简单的(因为不用区分符号位),而且不会出现0的两种编码形式.
原码就是符号位+真值. 整数符号位是0 负数符号位是1.
反码:正数的反码是其自身. 负数的反码是符号位不变,其他位取反
补码:正数的补码是其自身,负数的补码是符号位不变,其他位取反然后加1.
http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html 更详细的参见这个.
类型转换
java中向上转型不需要特殊声明. 向下转型需要显式声明.
向下转型的时候,可能发生截尾. 如果需要舍入,则需要使用java.lang.Math下的round()方法.
关于类型转换还有两点:
- 基本数据类型在和String一起拼接的时候,会调用toString方法,将其转换成String类型.
- 不同的数字类型一起运算的时候,表达式中最大的数据类型决定了最终运算结果的数据类型.
java中没有sizeof()操作符. 因为这玩意儿不需要sizeof()… 下层是jvm,平台移植的问题由jvm来处理,不需要程序员去考虑这些事情.