java操作符(Thinking in Java)

在最底层,Java中的数据是通过使用操作符来操作的。

一.赋值

            赋值使用操作符“=”。它的意思是“取右边的值(即右值),把它复制给左边(即左值)”。右值 可以是任何常数、变量或者表达式(只要它能生成一个值就行)。但左值必须是一个明确的、已命名的变量。也就是说必须有一个物理空间可以存储等号右边的值。举例来说,可以将一个常数赋给一个变量:a=4;但是不能把任何东西赋给一个常数,常数不能作为左值(比如不能说4=a;)。
        对基本数据类型的赋值是简单的。基本类型存储了实际的数值,而并非指向一个对象的引用,所以在为其赋值的时候,是直接将一个地方的内容复制到另一个地方。例如,对基本数据类型使用a=b,那么b的内容就复制给了a。若接着又修改了a,而b根本不会受这种修改的影响。作为程序员,这正是我们所期望的。
        但是在为对象“赋值”的时候,情况却发生变化。对一个对象进行操作时,我们真正操作的是对对象的引用。所以倘若“将一个对象赋值给另一个对象”,实际上是将“引用”从一个地方复制到另一个地方。这意味着假若对对象使用c=d,那么c和d都指向原本只有d指向的那个对象。下面这个例子将向大家阐释这一点。
class Tank{
	int level;
}
public class Assignment{
	public static void main(String args[]){
		Tank t1=new Tank();
		Tank t2=new Tank();
		t1.level=9;
		t2.level=47;
		System.out.println("1:t1.level:"+t1.level+",t2.level:"+t2.level);
		t1=t2;
		System.out.println("1:t1.level:"+t1.level+",t2.level:"+t2.level);
		t1.level=27;
		System.out.println("1:t1.level:"+t1.level+",t2.level:"+t2.level);
	}
}/*Output:
1:t1.level:9,t2.level:47
1:t1.level:47,t2.level:47
1:t1.level:27,t2.level:27
*///:~
         Tank类非常简单,它的两个实例(t1和t2)是在main()里创建的。对每个Tank类对象的level域都赋予了一个不同的值,然后将t2赋给了t1。在许多编程语言中,我们可能期望t1和t2总是相互独立的。但由于赋值操作的是一个对象的引用,所以修改t1的同时也改变了t2!这是由于t1和t2包含的是相同的引用,它们指向相同的对象。(那个不再被引用的对象会由“垃圾回收器”自动清理。)
(请注意System.out.println()语句中包含“+”操作符。在这种上下文环境中,“+”意味着字符串的连接,并且如果必要,还要“执行字符串转换”。当编译器观察到一个String后面紧跟一个非String类型的元素时,就会尝试着将这个非String类型的元素转换为String。比如上例中的t1.level和t2.level就成功转换为String。
       这种特殊的现象称作“别名现象”,是Java操作对象的一种基本方式。在这个例子中,如果想避免别名问题应该怎么办呢?可以这样写:t1.level=t2.level;这样便可以保持对象相互独立,而不是将t1和t2绑定到相同的对象。但直接操作对象内的域容易导致混乱,并且违背了良好的面向对象程序设计的原则。这不是一个小问题,所以大家从现在起就应该留意,为对象赋值可能会产生意想不到的结果。
将一个对象传递给方法时,也会产生别名问题:
class Letter{
	char c;
}
public class PassObject{
	static void f(Letter y){
		y.c='z';
	}
	public static void main(String args[]){
		Letter x=new Letter();
		x.c='a';
		System.out.println("1:x.c:"+x.c);
		f(x);
		System.out.println("2:x.c:"+x.c);
	}
}/*Output:
1:x.c:a
2:x.c:z
*///:~
        在许多编程语言中,方法f()似乎要在它的作用域内复制其参数Letter y的一个副本;但实际上只是传递了一个引用。所以代码行:y.c='z';实际改变的是f()之外的对象。
别名问题引起的问题及其解决方法是很复杂的话题。但是你应该知道他的存在,并在使用中注意这个陷阱。

二.算术操作符
    
        Java的基本算术操作符与其他大多数程序设计语言是相同的。其中包括加号(+)、减号(-)、除号(/)、乘号(*)以及取模运算符(%,它从整数除法中产生余数)。整数除法会直接去掉结果的小数位,而不是四舍五入的圆整结果。(Java也使用操作符后紧跟一个等号来表示的简化符号,如x+=4)

三.关系操作符
        关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系。如果关系是真实的,关系表达式会生成true;如果关系不真实,则生成false。关系操作符包括<,>,<=,>=,==,!=。==和!=适用于所有的基本数据类型,而其他比较符不适用于boolean类型。
        关系操作符==和!=也适用于所有对象,但这两个操作符通常会使第一次接触Java的程序员感到迷惑。下面是一个例子:
public class Equivalence{
	public static void main(String args[]){
		Integer n1=new Integer(47);
		Integer n2=new Integer(47);
		System.out.println(n1==n2);
		System.out.println(n1!=n2);
	}
}/*Output:
false
true
*///:~
        读者可能认为输出结果肯定是先是true,再是false,因为两个Integer对象都是相同的。但是尽管两个对象的内容相同,然而对象的引用却是不同的, 而==和!=比较的就是对象的引用。所以输出结果先是false,再是true。
        如果想比较两个对象的实际内容是否相同,又改如何操作呢?此时,必须使用所有对象都使用的特殊方法equals()。但这个方法不适用于“基本类型”,基本类型直接使用==和!=即可,下面举例说明如何使用:
public class EqualsMethod{
	public static void main(String args[]){
		Integer n1=new Integer(47);
		Integer n2=new Integer(47);
		System.out.println(n1.equals(n2));
	}
}/*Output:
true
*///:~!
        结果正如我们所预料的那样。但事情并不总是这么简单!假设你创建了自己的类,就像下面这样:
class Value{
	int i;
}
public class EqualsMethod2{
		public static void main(String args[]){
			Value v1=new Value();
			Value v2=new Value();
			v1.i=v2.i=100;
			System.out.println(v1.equals(v2));
		}
	}/*Output:
	false
	*///:~
        事情再次变得费解:结果又是false!这是由于equals()的默认行为是比较引用。所以除非在自己的新类中覆盖equals()方法,否则不可能表现出我们希望的行为。(这里不展开讨论)

四.逻辑操作符
  
         逻辑运算符“与”(&&)、“或”(||)、“非”(!)能根据参数的逻辑关系,生成一个boolean值(true或false)。
“与”、“或”、“非”操作只可应用于布尔值。不可将一个非布尔值当做布尔值在逻辑表达式中使用。注意,如果在应该使用String值的地方使用了布尔值,布尔值会自动转换成适当的文本形式。
    当使用逻辑运算符我们会遇到一种“短路”现象。下面是演示短路现象的例子:
public class ShortCircuit{
	static boolean test1(int val){
		System.out.println("test1("+val+")");
		System.out.println("result:"+(val<1));
		return val<1;
	}
	static boolean test2(int val){
		System.out.println("test2("+val+")");
		System.out.println("result:"+(val<2));
		return val<2;
	}
	static boolean test3(int val){
		System.out.println("test3("+val+")");
		System.out.println("result:"+(val<1));
		return val<3;
	}
	public static void main(String arg[]){
		boolean b=test1(0)&&test2(2)&&test3(2);
		System.out.println("expression is "+b);
	}
}/*Output:
test1(0)
result:true
test2(2)
result:false
expression is false
*///:~
        每个测试都会比较参数,并返回true或false。它也会打印信息告诉你正在调用测试。这些测试都作用于
test1(0)&&test2(2)&&test3(2)这个表达式,你会理所当然这三个测试都会执行。但输出结果显示却并非这样。到第二个测试产生了一个false结果,由于这意味着整个表达式肯定为false,所以没有必要去计算剩余的表达式,那样只能是“浪费”。“短路”一词的由来正源于此。如果所有的逻辑表达式都有一部分不必计算,那将获得潜在的性能提升。

五.按位操作符
      按位操作符用来操作整数基本数据类型中的单个“比特”(bite),即二进制位。按位操作符会对两个参数中对应的位执行布尔代数运算,并最终生成一个结果。
    如果两个输入位都是1,则按位“与”操作符(&)生成一个输出位1;否则生成一个输出位0。如果两个输入位里只要有一个是1,则按位“或”操作符(|)生成一个输出位1;只有两个输入位都是0的情况下,它才会生成一个输出位0。如果输入位的某一个是1,但不全是1,那么按位“异或”操作(^)生成一个输出位1。按位“非”(~),也称为取反操作符,它属于一元操作符,只对一个操作数进行操作(其他按位操作符是二元操作符),按位“非”生成与输入位相反的值。

六.移位操作符

       移位操作符操作的运算对象也是二进制的“位”。移位操作符只可用来处理整数类型(基本类型的一种)。左移位操作符(<<)能按照操作符右侧指定的位数将操作符左边的操作数向左移动(在低位补0)。“有符号”右移动操作符(>>)则按照操作符右侧指定的位数将操作符左边的操作符向右移动。“有符号”右移动操作符使用“符号扩展”:若符号为正,则在高位插入0;若符号为负,则在高位插入1。Java中增加了一种“无符号”右移位操作符(>>>),它使用“零扩展”:无论正负,都在高位插入0。
        如果对char、byte、或者short类型的数值进行移位处理,那么在移位之前,它们会被转换为int类型,并且得到的结果也是一个int类型的值。只有数值右端低于5位才有用。
       “移位”可与“等号”(<<=或>>=或>>>=)组合使用。此时,操作符左边的值会移动由右边的值指定的位数,再将得到的结果赋给左边的变量。但在进行“无符号”右移位结合赋值操作时,可能会遇到一个问题:如果对byte或short进行这样的移位运算,得到的可能不是正确的结果。它们会先被转换为int类型,再进行右移操作,然后被截断,赋值给原来的类型,在这种情况下可能得到-1的结果。下面例子演示了这种情况:
public class URShift{
	public static void main(String arg[]){
		int i=-1;
		System.out.println(Integer.toBinaryString(i));
		i>>>=10;
		System.out.println(Integer.toBinaryString(i));
		long l=-1;
		System.out.println(Long.toBinaryString(l));
		l>>>=10;
		System.out.println(Long.toBinaryString(l));
		short s=-1;
		System.out.println(Integer.toBinaryString(s));
		s>>>=10;
		System.out.println(Integer.toBinaryString(s));
		byte b=-1;
		System.out.println(Integer.toBinaryString(b));
		b>>>=10;
		System.out.println(Integer.toBinaryString(b));
		b=-1;
		System.out.println(Integer.toBinaryString(b));
		System.out.println(Integer.toBinaryString(b>>>10));
}
}/*Output:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111
*///:~
        在最后一个移位运算中,结果没有赋给b而是直接打印出来了,所以结果是正确的。

七.三元操作符 

     三元操作符也称条件操作符,它显得比较特别,因为它有三个操作数;但它确实属于操作符的一种,因为它最终也会生成一个值,与普通的if-else语句是不同的,表达式采取下述形式:boolean-exp?value0:value1。如果表达式boolean-exp(布尔表达式)的结果为true,就计算value0。否则计算value1,得到的结果就成为最终产生的值。

八.字符串操作符+和+=

     这个操作符在Java中有一项特殊用途:连接不同的字符串。尽管这与+和+=的传统使用方式不太一样,但我们还是很自然地使用这些操作符来做这件事情。
public class StringOperators{
	public static void main(String arg[]){
		int x=0,y=1,z=2;
		String s="x,y,z";
		System.out.println(s+x+y+z);
		System.out.println(x+" "+s);
		s+="(summed)=";
		System.out.println(s+(x+y+z));
		System.out.println(""+x);
}
}/*Output:
x,y,z012
0 x,y,z
x,y,z(summed)=3
0
*///:~
        请注意main()中的最后一个示例:有时会看到这种一个空的String后面跟随+和一个基本类型变量,以此作为不调用更加麻烦的显式方法(在本例中应该是Integer.toString())而执行字符串转换的方式。

九.类型转换操作符

     在Java中,类型转换是一种比较安全的操作。然而,如果要执行一种名为窄化转换(narrowing conversion)的操作(也就是说,将能容纳更多信息的数据类型转换成无法容纳那么多信息的类型),就有可能面临信息丢失的危险。这是,编译器就会强制要求我们显式地进行类型转换,而对于扩展转换(widening conversion),则不必。
        Java允许我们把任何基本数据类型转换成别的基本数据类型,但布尔型除外(不允许进行任何类型的转换处理)。“类”数据类型不允许进行类型转换,为了将类转换成另一种,必须采用特殊的方法(这里不展开讨论)。
        在执行窄化转换时,必须注意截尾和舍入问题。
public class CastingNumbers{
	public static void main(String arg[]){
		double above=0.7,below=0.4;
		float fabove=0.7f,fbelow=0.4f;
		System.out.println("(int)above:"+(int)above);
		System.out.println("(int)below:"+(int)below);
		System.out.println("(int)fabove:"+(int)fabove);
		System.out.println("(int)fbelow:"+(int)fbelow);
}
}/*Output:
(int)above:0
(int)below:0
(int)fabove:0
(int)fbelow:0
*///:~
        在将float或double转型为整型值时, 总是对该数字进行截尾。如果想要得到舍入的结果,就需要使用Java.lang.Math中的round()的方法:
public class RoundingNumbers{
	public static void main(String arg[]){
		double above=0.7,below=0.4;
		float fabove=0.7f,fbelow=0.4f;
		System.out.println("Math.round(above):"+Math.round(above));
		System.out.println("Math.round(below):"+Math.round(below));
		System.out.println("Math.round(fabove):"+Math.round(fabove));
		System.out.println("Math.round(fbelow):"+Math.round(fbelow));
}
}/*Output:
Math.round(above):1
Math.round(below):0
Math.round(fabove):1
Math.round(fbelow):0
*///:~
        如果对基本数据类型执行算术运算或按位运算,大家会发现,只要类型比int小,那么在运算之前这些值会自动转换为int。通常,表达式中出现的最大的数据类型决定了表达式最终结果的数据类型。

十.Java没有sizeof

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值