Java编程思想--第三章 操作符
来源:互联网 发布:手机数据流量自动关闭 编辑:程序博客网 时间:2024/06/06 03:21
3.1 略
3.2 使用java操作符
Java操作符 + - * / ,这些基本操作符作用于操作数,生成一个新值。几乎所有的操作符都只能操作基本类型,例外的操作符是= 、== 和!=,这些操作符能操作所有的对象,除此之外,String类支持操作符+和+=。
3.3优先级
当一个表达式中存在多个操作符时,操作符的优先级就决定了各部分的计算顺序。 Java对计算顺序做了特别的规定。其中,最简单的规则就是先乘除后加减。程序员经常会忘记其他优先级规则,所以应该用括号明确规定计算顺序
public class Test { public static void main(String[] args){ int x = 1,y = 2,z = 3; int a = x+y-2/2+z; //(1) int b = x+(y-2)/(2+z); //(2) System.out.println("a = "+a+" b = "+b); }}
这两个语句看起来大体相同,但是从输出就可以看出它们具有迥然不同的含义,而这正是使用括号的结果。
System.out.prtintln()语句中包含+操作符,+意味着字符串连接,当编译器观察到一个String后面紧跟着一个+,而+后面是一个非String的元素,会尝试转为String。
3.4 赋值
赋值使用操作符=,左边必须是一个明确地,已命名的变量,右边是任何常数、变量或者表达式(只要能生成一个值就行)。
例如:
int a=4;
对基本数据类型使用a=b,就是将b的内容复制给a,接着修改a,b不会受到修改的影响。
但是为对象赋值时,我们操作的是对对象的引用,若将一个对象赋值给另一个对象,实际是将引用从一个地方复制到另一个地方,例如对对象使用c=d,那么c和d 都指向原来只有d指向的那个对象。
例如
public class Test { static class Tank { int level; } public static class Asslgnment { 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("2: t1.level: " + t1.level + ", t2.level: " + t2.level); t1.level = 27; System.out.println("3: t1.level: " + t1.level + ", t2.level: " + t2.level); } }}
从执行结果可以看出,刚开始创建了两个独立的Tank对象t1和t2,分别给level属性赋值为9和47, 如果执行t1=t2后,则t1和t2都会指向t2之前指向的对象,即t1和t2是相同的引用,t1原来指向的对象将不再被引用,会被垃圾回收器自动清理。
这种现象被称作别名问题,避免别名问题应该
t1.level=t2.level;
这样就保持了两个对象彼此对立,而不是将t1和t2绑定到相同的对象,但是这样直接操作对象内的域容易导致混乱,违背了面向对象设计的原则。
3.4.1 方法调用中的别名问题
将一个对象传递给方法时,会产生别名问题
public class Test { static class Letter{ char c; } public static 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); } }}
根据结果可以看出,传递给f()的是一个引用,代码行 y.c=’z’;实际改变的是f()之外的对象。
3.5算术操作符
Java的基本算术操作符包括+、-、*、/以及取模操作符(%,它从整数除法中产生余数)。
整数除法会直接去掉结果的小数位,而不是四舍五入地圆整结果。
Java也使用一种来自C和C++的简化符号同时进行运算与赋值操作。这用操作符后紧跟一个等号来表示,它对于Java中的所有操作符都适用,只要其有实际意义就行。
例如,要将x加4,并将结果赋回给x,可以这么写:x += 4;下面这个例子展示了各种算术操作符的用法:
public class Test { public static void main(String[] args){ Random rand = new Random(47); // Create a seeded random number generator System.out.println(rand); int i, j, k; j = rand.nextInt(100) + 1; // Choose value from 1 to 100: System.out.println("j : " + j); k = rand.nextInt(100) + 1; System.out.println("k : " + k); i = j + k; System.out.println("j + k: " + i); i = j - k; System.out.println("j - k: " + i); i = k / j; System.out.println("k / j: " + i); i = k * j; System.out.println("k * j: " + i); i = k % j; System.out.println("k % j: " + i); j %= k; System.out.println("j %= k: " + j); //Floating-point number tests; float u,v,w; //Applies to doubles,too v=rand.nextFloat(); System.out.println("v: " + v); w=rand.nextFloat(); System.out.println("w: " + w); u = v + w ; System.out.println("v + w : " + u); u = v - w ; System.out.println("v - w : " + u); u = v * w ; System.out.println("v * w : " + u); u = v / w ; System.out.println("v / w : " + u); // The following also works for char // byte ,short,int,long,and double u += v ; //加之后取得是u的小数个数 System.out.println("u += v : " + u); u -= v ; System.out.println("u -= v : " + u); u *= v ; System.out.println("u *= v : " + u); u /= v ; System.out.println("u /= v : " + u); }}
要生成数字,程序首先会创建一个Random类的对象。如果在创建过程中没有传递任何参数,那么Java就会将当前时间作为随机数生成器的种子,并由此在程序
每一次执行时都产生不同的输出。但是,在本书的示例中,示例末尾所展示的输出都尽可能一致,这一点很重要,因为这样就使得这些输出可以用外部工具来验证。
通过在创建Random对象时提供种子(用于随机数生成器的初始化值,随机数生成器对于特定的种子值总是产生相同的随机数序列),就可以在每一次执行程序时都生
成相同的随机数,因此其输出是可验证的。要想生成更多的各种不同的输出,可以随意移除本书示例中的种子。
通过Random类的对象,程序可生成许多不同类型的随机数字。做法很简单,只需调用方法nextInt()和nextFloat()即可(也可以调用nextLong()或者nextDouble())。
传递给nextInt()的参数设置了所产生的随机数的上限,而其下限为0,但是这个下限并不是我们想要的,因为它会产生除0的可能性,因此我们对结果做了加1操作。
3.5.1 一元加、减操作符
一元减号(-)和一元加号(+)与二元减号和加号都使用相同的符号。根据表达式的书写形式,编译器会自动判断出使用的是哪一种。例如语句x = -a;的含义是显然的。
编译器能正确识别下述语句:
× = a * -b;
但读者会被搞糊涂,所以有时更明确地写成:
× = a * (-b);
一元减号用于转变数据的符号,而一元加号只是为了与一元减号相对应,但是它唯一的作用仅仅是将较小类型的操作数提升为int。
注:
一元运算符就是有一个操作数参与的例如是 “!”,
二元运算符就是有两个操作数参与的是最常见的例如:”+,-,*,/”等等!
三元运算符就是有三个操作数 例如” A?B:C”
3.6 自动递增和递减
递增和递减运算是两种相当不错的快捷运算(常称为“自动递增”和“自动递减”运算)。这两个操作符各有两种使用方式,通常称为“前缀式”和“后缀式”。
前缀递增和前缀递减(如++a或 –a),会先执行运算,再生成值(先自增,再参与运算)。
后缀递增和后缀递减(如a++或a–),会先生成值,再执行运算(先参与运算,再自增)。
public class Test { public static void main(String[] args){ int i = 1; System.out.println("i : " + i); System.out.println("++i : " + ++i); System.out.println("i++ : "+ i++); System.out.println("i : " + i); System.out.println("--i : " + --i); System.out.println("i-- : " + i--); System.out.println("i : "+i); }}
3.7 关系操作符
关系操作符生成的是一个boolean(布尔)结果,它们计算的是操作数的值之间的关系。如果关系是真实的,关系表达式会生成true(真),如果关系不真实,则生成false(假)。关系操作符包括小于(<)、大于(>)、小于或等于(<=)、大于或等于(>=)、等于(==)以及不等于(!=)。等于和不等于适用于所有的基本数据类型,而其他比较符不适用于boolean类型。因为boolean值只能为true或false,“大于”和“小于”没有实际意义。
3.7.1 测试对象的等价性
关系操作符==和!=也适用于所有对象,但这两个操作符通常会使第一次接触Java的程序员感到迷惑。下面是一个例子:
public class Test {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);}}
语句System.out.println(nl==n2)将打印出括号内的比较式的布尔值结果。读者可能认为输出结果肯定先是true,再是false,因为两个Integer对象都是相同的。但是尽管对象的内容相同,然而对象的引用却是不同的,而==和!=比较的就是对象的引用。所以输出结果实际上先是false, 再是true。这自然会使第一次接触关系操作符的人感到惊奇。
如果想比较两个对象的实际内容是否相同,又该如何操作呢?此时,必须使用所有对象都适用的特殊方法equals()但这个方法不适用于“基本类型”,基本类型直接使用==和!=即可。下面举例说明如何使用:
public class Test { public static void main(String[] args) { Integer n1 = new Integer(47); Integer n2 = new Integer(47); System.out.println(n1.equals (n2)); }}
结果正如我们所预料的那样。但事情并不总是这么简单!假设你创建了自己的类,就像下面这样:
public class Test{ static class Value{ int i; } public static 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)); } }}
事情再次变得令人费解了:结果又是false!这是由于equals()的默认行为是比较引用。所以除非在自己的新类中覆盖equals()方法,否则不可能表现出我们希望的行为。
遗憾的是,我们要到第7章才学习覆盖,到第17章才学习如何恰当地定义equals()。但在这之前,请留意equals()的这种行为表现方式,这样或许能够避免一些“灾难”。
大多数Java类库都实现了equals()方法,以便用来比较对象的内容,而非比较对象的引用。
3.8 逻辑操作符
逻辑操作符“与”(&&)、“或”(||)、“非”(! )能根据参数的逻辑关系,生成一个布尔值(true或false)。下面这个例子就使用了关系操作符和逻辑操作符。
public class Test{ public static void main(String[] args) { Random rand = new Random(47); int i = rand.nextInt(100); int j = rand.nextInt(100); System.out.println("i = : " + i); System.out.println("j = : " + j); System.out.println("i > j is " + (i > j)); System.out.println("i < j is " + (i < j)); System.out.println("i >= j is " + (i >= j)); System.out.println("i <= j is " + (i <= j)); System.out.println("i == j is " + (i == j)); System.out.println("i != j is " + (i != j)); //Treating an int as a boolean is not legal Java: //! System.out.println ("i && j is" +(i && j)); //! System.out.println ("i || j is" +(i || j)); //! System.out.println ("!i is" + !i); System.out.println("(i < 10) && (j < 10) is " + ((i < 10) && (j < 10))); System.out.println("(i < 10) || (j < 10) is " + ((i < 10) || (j < 10))); }}
与”、“或”、“非”操作只可应用于布尔值。与在C及C++中不同的是:不可将一个非布尔值当作布尔值在逻辑表达式中使用。
在前面的代码中用“//!”注释掉的语句,就是错误的用法(这种注释语法使得注释能够被自动移除以方便测试)。
后面的两个表达式先使用关系比较运算,生成布尔值,然后再对产生的布尔值进行逻辑运算。
注意,如果在应该使用String值的地方使用了布尔值,布尔值会自动转换成适当的文本形式。在上述程序中,可将整数类型替换成除布尔型以外的其他任何基本数据类型。
但要注意,对浮点数的比较是非常严格的。即使一个数仅在小数部分与另一个数存在极微小的差异,仍然认为它们是“不相等”的。即使一个数只比零大一点点,
它仍然是“非零”值。
3.8.1 短路
当使用逻辑操作符时,我们会遇到一种“短路”现象。即一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下部分了。因此,整个逻辑表达式靠后的部分有可能不会被运算。下面是演示短路现象的例子:
public class Test{ 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 < 3)); return val < 3; } public static void main(String[] args) { boolean b=test1(0)&&test2(2)&&test3(2); System.out.println("expression is " + b); }}
每个测试都会比较参数,并返回true或false。它也会打印信息告诉你正在调用测试。这些测
试都作用于下面这个表达式:
test1(0) && test2(2) && test3(2)
你会很自然地认为所有这三个测试都会得以执行。但输出显示却并非这样。第一个测试生成结果true,所以表达式计算会继续下去。然而,第二个测试产生了一个false结果。由于这意味着整个表达式肯定为false,所以没必要继续计算剩余的表达式,那样只是浪费。“短路”一词的由来正源于此。事实上,如果所有的逻辑表达式都有一部分不必计算,那将获得潜在的性能提升。
3.9 直接常量
一般说来,如果在程序里使用了“直接常量”,编译器可以准确地知道要生成什么样的类型,但有时候却是模棱两可的。如果发生这种情况,必须对编译器加以适当的“指导”,用与直接量相关的某些字符来额外增加一些信息。下面这段代码向大家展示了这些字符。
public class Test{ public static void main(String[] args) { int i1 = 0x2f; //Hexadecimal (lowercase) 16进制小写 System.out.println("i1: " + Integer.toBinaryString(i1)); int i2 = 0X2F; //Hexadecimal (uppercase) 16进制大写 System.out.println("i2: " + Integer.toBinaryString(i2)); int i3 = 177; //Octal(leading zero) //八进制 System.out.println("i3: " + Integer.toBinaryString(i3)); char c = 0xffff; //max char hex value System.out.println("c: " + Integer.toBinaryString(c)); byte b = 0x7f; //max byte hex value System.out.println("b: " + Integer.toBinaryString(b)); short s = 0x7fff; //max short hex value System.out.println("s: " + Integer.toBinaryString(s)); long l1 = 200L; //long suffix long l2 = 200l; //long suffix(but can be confusing) long l3 = 200; float f1 = 1; float f2 = 1F; //float suffix float f3 = 1f; //float suffix double d1 = 1d; //double suffix double d2 = 1D; //double suffix //(Hex and Octal also work with long) }}
直接常量后面的后缀字符标志了它的类型。若为大写(或小写)的L,代表long(但是,使用小写字母l容易造成混淆,因为它看起来很像数字1)。大写(或小写)字母F,代表float;大写(或小写)字母D,则代表double。
十六进制数适用于所有整数数据类型,以前缀0x(或0X),后面跟随0-9或小写(或大写)的a-f来表示。如果试图将一个变量初始化成超出自身表示范围的值(无论这个值的数值形式如何),编译器都会向我们报告一条错误信息。注意在前面的代码中,已经给出了char、byte以及short)所能表示的最大的十六进制值。如果超出范围,编译器会将值自动转换成int型,并告诉我们需要对这次赋值进行“窄化转型”(转型将在本章稍后部分定义)。这样我们就可清楚地知道自己的操作是否越界了。
八进制数由前缀0以及后续的0-7的数字来表示。
在C、C++或者Java中,二进制数没有直接常量表示方法。但是,在使用十六进制和八进制记数法时,以二进制形式显示结果将非常有用。通过使用Integer和Long类的静态方法toBinaryString()可以很容易地实现这一点。请注意,如果将比较小的类型传递给Integer.toBinaryString()方法,则该类型将自动被转换为int。
3.9.1 指数记数法
略
3.10 按位操作符
按位操作符用来操作整数基本数据类型中的单个“比特”(bit),即二进制位。按位操作符会对两个参数中对应的位执行布尔代数运算,并最终生成一个结果。
按位操作符来源于C语言面向底层的操作,在这种操作中经常需要直接操纵硬件,设置硬件寄存器内的二进制位。Java的设计初衷是嵌入电视机机顶盒内,所以这种面向底层的操作仍被保留了下来。但是,人们可能不会过多地用到位操作符。
①如果两个输入位都是1,则按位“与”操作符(&)生成一个输出位1;否则生成一个输出位0。
②如果两个输入位里只要有一个是1,则按位“或”操作符 (|) 生成一个输出位1;只有在两个输入位都是0的情况下,它才会生成一个输出位0。
③如果输入位的某一个是1,但不全都是1,那么按位“异或”操作 (^) 生成一个输出位1。
④按位“非”(~),也称为取反操作符,它属于一元操作符,只对一个操作数进行操作(其他按位操作符是二元操作符)。按位“非”生成与输入位相反的值——若输入0,则输出1,若输入1,则输出0。
按位操作符和逻辑操作符都使用了同样的符号,因此我们能方便地记住它们的含义:由于位是非常“小”的,所以按位操作符仅使用了一个字符。
按位操作符可与等号(=)联合使用,以便合并运算和赋值:&=、|=和^=都是合法的(由于“~”是一元操作符,所以不可与“=”联合使用)。
我们将布尔类型作为一种单比特值对待,所以它多少有些独特。我们可对它执行按位“与、按位“或”和按位“异或”运算,但不能执行按位“非(大概是为了避免与逻辑NOT混淆)。对于布尔值,按位操作符具有与逻辑操作符相同的效果,只是它们不会中途“短路”。此外,针对布尔值进行的按位运算为我们新增了一个“异或”逻辑操作符,它并未包括在“逻辑”操作符的列表中。在移位表达式中,不能使用布尔运算,原因将在后面解释。
3.11 移位操作符
略
3.12 三元操作符if-else
三元操作符也称为条件操作符,它显得比较特别,因为它有三个操作数,但它确实属于操作符的一种,因为它最终也会生成一个值,这与本章下一节中介绍的普通lf-else语句是不同的。其表达式采取下述形式:
boolean-exp ? value0 : value1
如果boolean-exp(布尔表达式)的结果为true,就计算value0,而且这个计算结果也就是操作符最终产生的值。如果boolean-exp的结果为false,就计算value1,同样,它的结果也就成为了操作符最终产生的值。
当然,也可以换用普通的if-else语句(在后面介绍),但三元操作符更加简洁。尽管C(C中发明了该操作符)引以为傲的就是它是一种简练的语言,而且三元操作符的引入多半就是为了体现这种高效率的编程,但假如你打算频繁使用它,还是要多作思量,因为它很容易产生可读性极差的代码。
条件操作符与if-else完全不同,因为它会产生一个值。下面是这两者进行比较的示例:
public class Test{ static int ternary(int i){ return i < 10 ? i * 100 : i * 10; } static int standardIfElse(int i){ if (i < 10) return i * 100; else return i * 10; } public static void main(String[] args){ System.out.println(ternary(9)); System.out.println(ternary(10)); System.out.println(standardIfElse(9)); System.out.println(standardIfElse(10)); }}
可以看出,上面的ternary()中的代码与standardIfElse()中不用三元操作符的代码相比,显得更加紧凑;但standardIfElse()更易理解,而且不需要太多的录入。所以在选择使用三元操作符时,请务必仔细考虑。
3.13 字符串操作符+和+=
这个操作符在Java中有一项特殊用途:连接不同的字符串。这一点已经在前面的例子中展示过了。尽管与+和+=的传统使用方式不太一样,但我们还是很自然地使用这些操作符来做这件事情。
这项功能用在C++中似乎是个不错的主意,所以引入了操作符重载(operator overloading)机制,以便C++程序员可以为几乎所有操作符增加功能。但非常遗憾,与C++的另外一些限制结合在一起,使得操作符重载成为了一种非常复杂的特性,程序员在设计自己的类时必须对此有非常周全的考虑。与C++相比,尽管操作符重载在Java中更易实现(就像在C捂言中所展示的那样,它具有相当简单直接的操作符重载机制),但仍然过于复杂。所以Java程序员不能像C++和C#程序员那样实现自己的重载操作符。
字符串操作符有一些很有趣的行为。如果表达式以一个字符串起头,那么后续所有操作数都必须是字符串型(请记住,编译器会把双引号内的字符序列自动转成字符串):
public class Test{ public static void main(String[] args) { int x = 0, y = 1, z = 2; String s = "x,y,z "; System.out.println(s + x + y + z); System.out.println(x + " " + s);//converts x to a String s += "(summed)=";//concatenation operator System.out.println(s + (x + y + z)); System.out.println("" + x);//shorthand for Integer.toString() }}
请注意,第一个打印语句的输出是012而不是3,而3正是将这些整数求和之后应该得到的结果,之所以出现这种情况,是因为Java编译器会将x、y和z转换成它们的字符串形式,然后连接这些字符串,而不是先把它们加到一起。第二个打印语句把先导的变量转换为String,因此这个字符串转换将不依赖于第一个变量的类型。最后,可以看到使用+=操作符将一个字符串追加到了s上,并且使用了括号来控制表达式的赋值顺序,以使得int类型的变量在显示之前确实进行了求和操作。
请注意main()中的最后一个示例:有时会看到这种一个空的String后面跟随+和一个基本类型变量,以此作为不调用更加麻烦的显式方法(在本例中应该是Integer.toString())而执行字符串转换的方式。
3.14 使用操作符时常犯的错误
使用操作符时一个常犯的错误就是,即使对表达式如何计算有点不确定,也不愿意使用括号。这个问题在Java中仍然存在。
在C和C++中,一个特别常见的错误如下:
while(x=y){
//……
}
程序员很明显是想测试是否“相等”(==),而不是进行赋值操作。在C和C++中,如果y是一个非零值,那么这种赋值的结果肯定是true.而这样便会得到一个无穷循环。在Java中,这个表达式的结果并不是布尔值,而编译器期望的是一个布尔值。由于Java不会自动地将int数值转换成布尔值,所以在编译时会抛出一个编译时错误,从而阻止我们进一步去运行程序。所以这种错误在Java中永远不会出现(唯一不会得到编译时错误的情况是x和y都为布尔值。在这种情况下,x=y属于合法表达式。而在前面的例子中,则可能是一个错误)。
Java中有一个与C和C++中类似的问题,即使用按位“与”和 按位“或”代替逻辑“与”和 逻辑“或”。按位“与”和 按位“或”使用单字符(&或|),而逻辑“与”和逻辑“或”使用双字符(&&或||)。就像“=”和“==”一样,键入一个字符当然要比键入两个简单。Java编译器可防止这个错误发生,因为它不允许我们随便把一种类型当作另一种类型来用。
3.15 类型转换操作符
类型转换(cast)的原意是“模型铸造”。在适当的时候,Java会将一种数据类型自动转换成另一种。例如,假设我们为某浮点变量赋以一个整数值,编译器会将int自动转换成float。类型转换运算允许我们显式地进行这种类型的转换,或者在不能自动进行转换的时候强制进行类犁转换。
要想执行类型转换,需要将希望得到的数据类型置于圆括号内,放在要进行类型转换的值的左边,可以在下面的示例中看到它:
public class Test{ public static void main(String[] args) { int i = 200; long lng = (long) i; lng = i; //"Widening." so cast not really required long lng2 = (long) 200; lng2 = 200; //A "narrowing conversion": i = (int) lng2;//Cast required }}
正如所看到的,既可对数值进行类型转换,亦可对变量进行类型转换。请注意,这里可能会引入“多余的”转型,例如,编译器在必要的时候会自动进行int值到long值的提升。但是你仍然可以做这样“多余的”事,以提醒自己需要留意,也可以使代码更清楚。在其他情况下,可能只有先进行类型转换,代码编译才能通过。
在C和C++中,类型转换有时会让人头痛。但是在Java中,类型转换则是一种比较安全的操作。然而,如果要执行一种名为窄化转换(narrowing conversion)的操作(也就是说,将能容纳更多信息的数据类型转换成无法容纳那么多信息的类型),就有可能面临信息丢失的
危险。此时,编译器会强制我们进行类型转换,这实际上是说:“这可能是一件危险的事情,如果无论如何要这么做,必须显式地进行类型转换。”而对于扩展转换(widelung conversion).则不必显式地进行类型转换,因为新类型肯定能容纳原来类型的信息,不会造成任何信息的丢失。
Java允许我们把任何基本数据类型转换成别的基本数据类型,但布尔型除外,后者根本不允许进行任何类型的转换处理。“类”数据类型不允许进行类型转换。为了将一种类转换成另一种,必须采用特殊的方法(本书后面会讲到,对象可以在其所属类型的类族之间可以进行类型转换,例如,“橡树”可转型为“树”,反之亦然。但不能把它转换成类族以外的类型,如“岩石”)。
3.15.1 截尾和舍入
在执行窄化转换时,必须注意截尾与舍入问题。例如,如果将一个浮点值转换为整型值,Java会如何处理呢?例如,将29.7转换为int,结果是30还是29?在下面的示例中可以找到答案:
public class Test{ public static void main(String[] args){ 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); }}
因此答案是在将float或double转型为整型值时,总是对该数字执行截尾。如果想要得到舍入的结果,就需要使用java.lang.Math中的round()方法:
public class Test{ public static void main(String[] args){ 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)); }}
由于round()是java.lang的一部分,因此在使用它时不需要额外地导入。
3.15.2 提升
如果对基本数据类型执行算术运算或按位运算,大家会发现,只要类型比int小(即char、byte或者short),那么在运算之前,这些值会自动转换成int。这样一来,最终生成的结果就是int类型。如果想把结果赋值给较小的类型,就必须使用类型转换(既然把结果赋给了较小的类型,就可能出现信息丢失)。
通常,表达式中出现的最大的数据类型决定了表达式最终结果的数据类型。如果将二个float值与一个double值相乘,结果就是double,如果将一个int和一个long值相加,则结果为long。
3.16 Java没有sizeof
在C和C++中,sizeof()操作符可以告诉你为数据项分配的字节数。在C和C++中,需要使用sizeof()的最大原因是为了“移植”。不同的数据类型在不同的机器上可能有不同的大小,所以在进行一些与存储空间有关的运算时,程序员必须获悉那些类型具体有多大。例如,一台计算机可用32位来保存整数,而另一台只用16位保存。显然,在第一台机器中,程序可保存更大的值。可以想像,移植是令C和C++程序员颇为头痛的一个问题。
Java不需要sizeof()操作符来满足这方面的需要,因为所有数据类型在所有机器中的大小都是相同的。我们不必考虑移植问题——它已经被设计在语言中了。
public class Test{ //To accept the results of a boolean test: void f(boolean b){ } void boolTest(boolean x, boolean y){ //Arithmetic operators: //! x=x*y; //! x=x/y; //! x=x%y; //! x=x+y; //! x=x-y; //! x++; //! x--; //! x=+y; //! x=-y; //Relational and logical: //! f(x>y); //! f(x>=y); //! f(x<y); //! f(x<=y); f(x == y); f(x != y); f(!y); x = x && y; x = x || y; //Bitwise operators: //! x=~y; x = x & y; x = x | y; x = x ^ y; //! x=x<<1; //! x=x>>1; //! x=x>>>1; //Compound assignment: //! x+=y; //! x-=y; //! x*=y; //! x/=y; //! x%=y; //! x<<=1; //! x>>=1; //! x>>>=1; x &= y; x ^= y; x |= y; //Casting: //! char c=(char)x; //! byte b=(byte)x; //! short s=(short)x; //! int i=(int)x; //! long l=(long)x; //! double d=(double)x; } void charTest(char x, char y){ //Arithmetic operators: x = (char) (x * y); x = (char) (x / y); x = (char) (x % y); x = (char) (x + y); x = (char) (x - y); x++; x--; x = (char) +y; x = (char) -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: x = (char) ~y; x = (char) (x & y); x = (char) (x | y); x = (char) (x ^ y); x = (char) (x << 1); x = (char) (x >> 1); x = (char) (x >>> 1); //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; //Casting: //! boolean b1=(boolean)x; byte b = (byte) x; short s = (short) x; int i = (int) x; long l = (long) x; float f = (float) x; double d = (double) x; } void byteTest(byte x, byte y){ //Arithmetic operators: x = (byte) (x * y); x = (byte) (x / y); x = (byte) (x % y); x = (byte) (x + y); x = (byte) (x - y); x++; x--; x = (byte) +y; x = (byte) -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: x = (byte) ~y; x = (byte) (x & y); x = (byte) (x | y); x = (byte) (x ^ y); x = (byte) (x << 1); x = (byte) (x >> 1); x = (byte) (x >>> 1); //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; //Casting: //! boolean b1=(boolean)x; char c = (char) x; short s = (short) x; int i = (int) x; long l = (long) x; float f = (float) x; double d = (double) x; } void shortTest(short x, short y){ //Arithmetic operators: x = (short) (x * y); x = (short) (x / y); x = (short) (x % y); x = (short) (x + y); x = (short) (x - y); x++; x--; x = (short) +y; x = (short) -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: x = (short) ~y; x = (short) (x & y); x = (short) (x | y); x = (short) (x ^ y); x = (short) (x << 1); x = (short) (x >> 1); x = (short) (x >>> 1); //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; //Casting: //! boolean b1=(boolean)x; char c = (char) x; byte b = (byte) x; int i = (int) x; long l = (long) x; float f = (float) x; double d = (double) x; } void intTest(int x, int y){ //Arithmetic operators: x = x * y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: x = ~y; x = x & y; x = x | y; x = x ^ y; x = x << 1; x = x >> 1; x = x >>> 1; //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; //Casting: //! boolean b1=(boolean)x; char c = (char) x; byte b = (byte) x; short s = (short) x; long l = (long) x; float f = (float) x; double d = (double) x; } void longTest(long x, long y){ //Arithmetic operators: x = x * y; x = x / y; x = x % y; x = x + y; x = x - y; x++; x--; x = +y; x = -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: x = ~y; x = x & y; x = x | y; x = x ^ y; x = x << 1; x = x >> 1; x = x >>> 1; //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; x <<= 1; x >>= 1; x >>>= 1; x &= y; x ^= y; x |= y; //Casting: //! boolean b1=(boolean)x; char c = (char) x; byte b = (byte) x; short s = (short) x; int i = (int) x; float f = (float) x; double d = (double) x; } void floatTest(float x, float y){ //Arithmetic operators: x = (x * y); x = (x / y); x = (x % y); x = (x + y); x = (x - y); x++; x--; x = +y; x = -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: //! x = (float) ~y; //! x = (float) (x & y); //! x = (float) (x | y); //! x = (float) (x ^ y); //! x = (float) (x << 1); //! x = (float) (x >> 1); //! x = (float) (x >>> 1); //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; //! x &= y; //! x ^= y; //! x |= y; //Casting: //! boolean b1=(boolean)x; char c = (char) x; byte b = (byte) x; short s = (short) x; int i = (int) x; long l = (long) x; double d = (double) x; } void doubleTest(double x, double y){ //Arithmetic operators: x = (x * y); x = (x / y); x = (x % y); x = (x + y); x = (x - y); x++; x--; x = +y; x = -y; //Relational and logical f(x > y); f(x >= y); f(x < y); f(x <= y); f(x == y); f(x != y); //! f(!x); //! f(x&&y); //! f(x||y); //Bitwise operators: //! x = (float) ~y; //! x = (float) (x & y); //! x = (float) (x | y); //! x = (float) (x ^ y); //! x = (float) (x << 1); //! x = (float) (x >> 1); //! x = (float) (x >>> 1); //Compound assignment: x += y; x -= y; x *= y; x /= y; x %= y; //! x <<= 1; //! x >>= 1; //! x >>>= 1; //! x &= y; //! x ^= y; //! x |= y; //Casting: //! boolean b1=(boolean)x; char c = (char) x; byte b = (byte) x; short s = (short) x; int i = (int) x; long l = (long) x; }}
注意,能够对布尔型值进行的运算非常有限。我们只能赋予它true和false值,并测试它为真还是为假,而不能将布尔值相加,或对布尔值进行其他任何运算。
在char、byte和short中,我们可看到使用算术操怍符中数据类型提升的效果。对这些类型的任何一个进行算术运算,都会获得一个int结果,必须将其显式地类型转换回原来的类型(窄化转换可能会造成信息的丢失),以将值赋给原本的类型。但对于int值,却不必进行类型转化,因为所有数据都已经属于int类型。但不要放松警惕,认为一切事情都是安全的,如果对两个足够大的int值执行乘法运算,结果就会溢出。下面这个例子向大家展示了这一点:
public class Test{
public static void main(String[] args)
{
int big = Integer.MAX_VALUE;
System.out.println(“big = ” + big);
int bigger = big * 4;
System.out.println(“bigger = ” + bigger);
}
}
你不会从编译器那里收到出错或警告信息,运行时也不会出现异常。这说明Java虽然是好东西,但也没有那么好!
对于char、byte或者short,复合赋值并不需要类型转换。尽管它们执行类型提升,但也会获得与直接算术运算相同的结果。而在另一方面,省略类型转换可使代码更加简练。
可以看到,除boolean以外,任何一种基本类型都可通过类型转换变为其他基本类型。当类型转换成一种较小的类型时.必须留意“窄化转换”的结果,否则会在类型转化过程中不知不觉地丢失了信息。
- 《Java 编程思想》--第三章:操作符
- JAVA编程思想-第三章 操作符
- 《Java编程思想》第三章 操作符
- 《java编程思想》第三章:操作符
- Java编程思想--第三章 操作符
- java编程思想第三章总结--操作符
- Java编程思想个人见解 第三章 操作符
- Java 编程思想——第三章 操作符总结
- java编程思想第三章 操作符 笔记
- Java编程思想笔记——第三章 操作符
- java编程思想---第三章(操作符)
- JAVA编程思想学习第三篇の操作符
- Java编程思想第四版读书笔记——第三章 操作符
- Java编程思想(第三章)
- java编程思想读书笔记--第三章
- Java编程思想---第三章笔记
- java编程思想笔记第三、四章
- java 编程思想 第三章 练习14
- 加密解密技术基础
- Scala容器库(Scala’s Collections Library)
- python lambda表达式
- tomcat连接数线程数
- java web urlrewritefilter(伪静态)配置
- Java编程思想--第三章 操作符
- 写给自己,勿忘初心
- Oracle存储过程学习
- HTML代码规范
- 51nod-1383 整数分解为2的幂
- 【Android 开发问题】ListView 嵌套 GridView导致ListView的item不可点击
- java递归读取目录下的所有文件
- C语言设计1.2代码
- java socket通信03