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以外,任何一种基本类型都可通过类型转换变为其他基本类型。当类型转换成一种较小的类型时.必须留意“窄化转换”的结果,否则会在类型转化过程中不知不觉地丢失了信息。

0 0
原创粉丝点击