Java宝典
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第2章 Java语言基础

本章包括

◆ Java语言的基本要素

◆ 基本数据类型以及数据类型之间的转换

◆ 运算符与表达式

◆ 常量与变量的使用

◆ 引用数据类型以及instanceof的使用方法

◆ 全局变量(成员变量)和局部变量

Java语言规范是Java语言的主要组成部分,也是学习控制语句等其他内容的基础。从本章开始,就进入到Java语言规范的学习。本章主要从Java语言的4个基本要素、如何在程序中使用常量和变量、Java语言的8种基本数据类型、引用类型的使用、Java语言的运算符和表达式以及Java程序设计中的成员变量和局部变量等方面对Java语言的基础知识进行一个较为全面的介绍。

2.1 Java语言的基本要素

Java语言的基本要素包括标识符、关键字与保留字、分隔符和代码注释。在实际应用中经常会用到这些基本要素的知识,它也是学习Java程序设计的基础。本节将对Java语言的基本要素进行介绍。

2.1.1 标识符

Java中的标识符是指作为方法、变量、常量、数组、文件等的名称标记的有效字符序列。Java语言的标识符使用的是Unicode标准字符集表,该字符集表可以表示多国语言的文字,最多可以表示65,535个字符。它的前128个字符与ASCII码表中的字符相对应。Java语言中对标识符的命名规则如下:

◆ 标识符由字母、下画线、美元符号($)和数字组成。

◆ 标识符的第一个字符必须是字母、下画线或者美元符号,不能是数字。

同时符合这两条规则的标识符才是合法的标识符。例如,以下几个标识符都是合法的标识符:dalian, _password, key_word, $docmodel, executeUpdate2和_123。

如果定义的标识符以数字或者其他的符号(例如空格、“#”等)开头,那么这样的标识符就是不合法的标识符。例如,以下几个标识符都是不合法的标识符:2number, key%word, #mail和key word。

在使用标识符的时候,还需要注意以下几个问题:

◆ 标识符是区分大小写的。例如,DaLian和dalian是两个不同的标识符,在编写代码的过程中特别需要注意这个问题。

◆ 标识符的长度没有限制。虽然Java语言中并没有对标识符的长度进行限制,但是一般不建议使用过长的标识符,也不建议使用无意义的标识符为变量或者方法等命名。例如,如果要定义一个方法用来实现计算平均数的功能,那么方法名average()就比aaa()可读性强。

◆ 标识符不能是关键字或者保留字。

2.1.2 关键字与保留字

关键字与保留字是Java语言的基本要素之一。本小节将介绍Java语言所涉及的关键字和保留字,这里介绍的大部分关键字和保留字都将在以后的章节中讲到。

1.关键字

Java语言中的关键字是指那些被赋予特定含义并且具有专门用途的单词,这些关键字不能作为标识符来使用。表2-1列出了Java语言中的关键字。

表2-1 Java语言中的关键字

注:*表示的是从来没有使用过的,如const和goto。

**表示的是在JDK 1.2中新添加的,如strictfp。

***表示的是在JDK 1.4中新添加的,如assert。

****表示的是在JDK 1.5中新添加的,如enum。

在使用这些关键字的时候,还需要注意以下几个问题:

◆ Java中的关键字都是小写的。

◆ 在这些关键字中有与循环控制有关的关键字,如for, if, else, switch, break, continue;有与异常处理有关的关键字,如try, catch, throws, throw, finally;有与访问控制有关的关键字,如public, private, protected;有与数据类型有关的关键字,如boolean, byte, char, double, float, int, long, short;有与类、包和接口有关的关键字,如abstract, class, implements, import, package。

◆ Java中有些关键字虽然被保留下来了,但是却从未使用过,如const和goto。const虽然是表示常量的关键字,但是在定义常量的时候一般不使用它,而是使用public static final来定义。关键字goto虽然表示跳转,但是在实际的编程过程中是不用的。

◆ 在新添加的关键字中关键字strictfp表示精确浮点,使用它可以使浮点运算更加精确,而且不会因为不同的硬件平台使得执行后的结果不一致;关键字assert表示断言,它是一个包含布尔表达式的语句,主要用于测试和调试代码,默认情况下是禁用的;关键字enum表示枚举类型,该类型是在JDK 1.5以后新增加的一种数据类型,它是以类型安全的形式来表示的,使用枚举可以增强程序的健壮性。

2.保留字

保留字是指目前的Java版本尚未使用,但在以后的版本中可能会作为关键字使用的单词。比较常见的保留字有true, false, null, byvalue等,这些保留字也不能作为标识符使用。

2.1.3 分隔符

Java中有4种分隔符,分别是“.”(句点)、“; ”(分号)、“ ”(空格)和“{}”(花括号)。其中,“.”(句点)一般用于方法或者变量的引用。例如在第1章中使用的打印语句:

        System.out.println("This is my first Java program! ");

其中,System是一个标准的输入输出类,out是该类中一个PrintStream类型的标准输出流,println()是PrintStream类的一个方法,因此通过“.”就可以使用PrintStream类下的println()方法,达到打印输出数据的效果。

“; ”(分号)是Java语句结束的标志。在编写代码时,每一个语句的结尾都要使用分号“; ”。如果在某一个语句的结尾没有使用分号“; ”,编译器在对程序进行编译时就会报错。一般一条语句只占一行,并使用分号“; ”作为该行语句的结束标志。

把多条语句都放在一行上也是可以的,例如在第1章的FirstApplet例子当中,可以将以下3条语句放在一行:

        Font font= new Font("Dialog", Font.ITALIC,30); g. setFont(font); g.setColor(Color.red);

“ ”(空格)可以加入代码的组成部分之间而不会影响程序的编译和执行。另外,在编写表达式的时候,一般在等号两边各使用一个空格,以增强代码的可读性,例如:

        int i = 5;      //等号的两边各使用一个空格

在Java语言中,需要用花括号“{}”将一组语句包裹起来,形成一个代码段。代码段可以嵌套,嵌套的层数没有限制,例如:

        for(int i=0; i<=10; i++){
                for(int j=0; j<=10; j++){
                ……
                }
        }

但是在实际应用中,不建议嵌套的层数过多,一般嵌套应该为两三层。使用Java语言编写程序需要使用花括号“{}”进行嵌套时,还需要注意以下几个问题:

◆ 在嵌套的过程中,要保证花括号中的左、右括号是成对出现的,也就是说不允许有单独的“{”或单独的“}”出现在程序中。

◆ 为了保证花括号中的左、右括号成对出现,在需要使用花括号时应先将花括号配对,再填充花括号里边的内容。以前面那个for循环为例,在写完for(int i =0; i<=10; i++)之后,先写“{}”,然后再在这个花括号的里边写第二个for循环语句,这样就保证了花括号之间的匹配。尤其是在编写复杂逻辑结构的时候,这种方法可以有效地避免忘记匹配花括号。

◆ 注意程序的层次性。在编写程序的过程中,使用Tab键进行缩进是非常必要的,一方面缩进可以增强代码的可读性,另一方面也有利于检查花括号是否都完全匹配。

2.1.4 代码注释

在编写代码的过程中,为代码编写注释是非常必要的,尤其是在实际开发项目时,代码注释更是必不可少的。代码注释在代码编译的过程中将被忽略。Java语言提供了3种注释方法,这3种注释方法分别用于不同的场合。

1.单行注释

Java中单行注释使用双斜杠“//”来表示。这种注释一般用来对某一个语句做简单的解释,用在语句的行尾。例如在第1章的FirstApplet中使用的如下注释:

        g.setFont(font);           //设置字体显示方式

2.多行注释

Java中多行注释使用“/*……*/”来表示。这种注释方法一般在需要对代码的某一个部分做比较详细的说明时使用,例如:

      /*denshm010:指定的会计传票号、denshm020:指定的发行部门 */
      protected String[] SHONIN_SELECT_DISPLAY_ID = {"denshm010", "denshm020"};

3.文档注释

这种注释方法一般用来对类或者某个方法做详细说明,一般是通过javadoc自动生成的。Java中文档注释的方法使用下面这种形式来表示:

        /**
         *承认业务情报检索
         *
         *@param dmInfoMap  检索的承认业务信息
         *@param loginUser  用户名
         *@return HashMap  检索结果
         *@throws BizException
        */
        public HashMap confirmInfo (HashMap dmInfoMap   AbstractLoginUser loginUser, )
        throws BizException {
        ……
        }

在文档注释中@后面跟的是一个定义标签用途的关键字。@param用来表示方法的参数,上面的例子中confirmInfo()方法共需要接收两个参数,其中参数dmInfoMap表示检索的承认业务信息,loginUser表示用户名。@return表示方法的返回值,这里方法返回的是一个HashMap,用来表示检索的结果。@throws表示代码可能出现的异常,该例中confirmInfo()方法抛出了一个BizException异常。

另外,在写某一个类的时候还需要使用@author表示代码的作者,使用@version表示当前代码的版本。这里所说的注释方法是一个一般的使用方法,不同的公司对于不同的项目,在注释的要求上可能会有所不同。

2.2 常量与变量

Java程序设计语言中数据可以分为常量和变量,它们也是程序中经常需要用到的数据形式,所以正确地认识以及使用常量和变量是非常重要的。本节首先将介绍如何使用常量和变量,在了解了常量和变量的使用方法之后,给出在使用常量和变量的过程中需要注意的问题。

2.2.1 常量

常量是指在程序的执行过程中,其值始终不变的量。常量根据其类型分为整型常量(如123)、实型常量(如1-23)、字符常量(如a)和字符串常量(如const)。

在Java语言中常量并不是采用关键字const来定义的,而是使用public static final来定义的。例如可以使用如下方法声明一个整型常量和一个字符串常量:

        public static final int USER_NUMBER = 1000;
        public static final String USER_ GENKIN= "tableName";

一般情况下,如果定义了一个常量,则表示这个值在整个程序中是不需要被改变的。如果常量值被改变了,所有引用常量的值都会发生改变。例如,在上面代码中如果USER_NUMBER改成2000,那么所有使用这个常量的地方的值都会变为2000。

2.2.2 变量

变量是指在程序运行过程中,其值可以改变的量。例如可以使用下面的方法声明一个整型变量,其中,int表示的是变量的数据类型,number表示的是变量名,最后别忘了以分号结尾。

        int number;

当然,也可以在初始化一个变量的同时为它赋一个初值。下面就在初始化number后,为它赋了一个值5。在编写代码的过程中,一个好的编程习惯应该是在声明一个变量的同时对它进行初始化。

        int number = 5;

在Java语言中,定义了一个变量就会在内存中为它开辟一个内存区域,赋给该变量的值也就会存储在这个内存区域中。当使用某个变量的时候,程序就会通过这个变量名找到相应的存储单元并获取该变量的值。但是每一个存储区域只能存储某一个确定数据类型的值,程序在编译时会检查该变量是否符合相应的数据类型。例如,上面的number变量就只能存储整型的值,如果将整型的005改写成字符串类型的005,那么程序在编译的时候就会报错。变量也可以用来引用对象,例如:

        Car c =new Car();
        c.driver();

这里实例化了一个对象Car,然后调用了其中的一个方法driver()。关于对象的创建和方法的调用的内容可以参见第6章和第7章。

2.2.3 使用常量和变量需要注意的问题

在为常量命名时,常量名中的所有字母要大写,要为常量起有意义的名字,以增强代码的可读性。

在变量的使用中,应该注意以下几个问题。

◆ 变量名的第一个单词是小写的,例如前面的例子中的number。但是如果变量名是多个单词组合在一起而成的,那么第二个单词以后的每个单词的首字母要大写,例如studentNumber。

◆ 变量要遵循先声明后使用的原则。例如要想使用number这个变量,就必须先声明它。如果程序中先写了“number=005; ”,而没有声明number这个变量,程序在编译期间会报错。

◆ 变量名是大小写敏感的,例如number和Number是两个不一样的变量。

◆ 要为变量起有意义的名字,以增强代码的可读性。

2.3 基本数据类型

Java中共有8种基本数据类型,包括byte, short, int, long, float, double, char和boolean。其中,byte, short, int和long属于整数类型,float和double属于浮点类型,char属于字符类型,boolean属于逻辑类型。本节将详细介绍这8种基本数据类型的使用方法。

2.3.1 整数类型

整数类型有整型常量和整型变量两种形式。本小节主要介绍整型常量的3种表示方法,以及byte, short, int和long这4种基本数据类型的使用方法。其中byte表示字节型,short表示短整型,int表示整型,long表示长整型。

1.整型常量

Java中整型常量有3种表示方法,分别是十进制常量、八进制常量和十六进制常量。十进制常量就是以十进制的形式表示的整数,例如:

        int i10= 123;      //运行结果是123

八进制常量就是以八进制的形式表示的整数。Java中八进制数用0开头的数来表示,例如:

        int i8= 0123;      //运行结果是83

十六进制常量就是以十六进制的形式表示的整数。Java中十六进制数用0x或者0X开头的数来表示,例如:

        int i16= 0x123;     //运行结果是291

2.整型变量

Java中的整型变量包括4种基本数据类型,分别为byte, short, int和long。整数类型的所占字节数、取值范围以及默认值如表2-2所示。

表2-2 整数类型所占字节数、取值范围以及默认值

整型变量的声明方法如下:

        byte b;            //声明一个字节型变量
        short s;           //声明一个短整型变量
        int i;             //声明一个整型变量
        long x;            //声明一个长整型变量

也可以在声明变量的同时对它进行赋值,例如:

        byte b = 123;      //为一个字节型变量赋值
        short s = 5678;    //为一个短整型变量赋值
        int i = 12345;     //为一个整型变量赋值
        long x = 125800L;  //为一个长整型变量赋值

这里要注意的是,如果要定义成long类型的变量,最好在赋的值后面加上一个大写的L(小写的l也可以,但是它容易和数字1混淆,所以不建议使用),以便区分其他类型。int类型除了可以赋值为正整数外,还可以赋值为负数,例如:

        int i = -3;

负数在计算机里是如何表示的呢,大家知道计算机是用二进制来存储信息的,它用最高位(从左数第1位)作为符号位来区分正数和负数。如果最高位为0就表示为正数,最高位为1就表示为负数。正数使用原码表示,负数则使用补码表示。

例如对于整型数据来说,它占4个字节。如果i=3,那么它在内存中是这样存储的:00000000000000000000000000000011。如果i=-3,它在内存中的存储状态就是这样的:11111111111111111111111111111101。-3的补码的转换方式是先取得3的原码,然后取反,取反以后再加1。

上面讲到的是可以将int类型的变量赋值为负数。int类型的变量除了可以赋值为负数以外,还可以将其赋值为字符,例如:

        int i = ' z' ;       //正常编译,运行结果为122
        int i = '9' ;       //正常编译,运行结果为57

对于int i= ‘z',此时返回的值不是z字符本身,而是字符z在ASCII码对照表中对应的值。因为字符z在ASCII码对照表中对应的值是122,所以最后返回的值就是122。

同样对于int i= ‘9',由于9使用了单引号,所以编译器会把它作为一个字符来看,返回的值57就是字符9在ASCII码对照表中所对应的值。

int类型变量可以赋值为负数和字符,但是int类型变量不允许被赋值为带有小数点的浮点类型的数。例如,下面的赋值方法会在程序的编译期间得到一个错误提示。

        int i = 12.36;     //编译出错

在编写代码过程中,byte一般用于网络传输数据,int用来定义整型变量。除非有必要,否则不建议使用long来声明并初始化一个整型变量,因为long类型会使用更多的内存空间。

注意

在使用整型变量的时候,不能超出它们的取值范围。如果超出范围,编译器会报错。

2.3.2 浮点类型

浮点类型有浮点型常量和浮点型变量两种形式。本小节主要介绍浮点型常量的两种表示方法,以及float和double这两种基本数据类型的使用方法。其中,float表示单精度浮点类型,double表示双精度浮点类型。

1.浮点型常量

Java中浮点型常量有两种表示方法,分别是float类型的常量和double类型常量。float类型的常量后面需要跟一个f,例如3265.233f,45.98f。double类型的常量是浮点型常量的默认类型,常量后面可以不跟d,例如45.321545,3e12

2.浮点型变量

Java中浮点类型的变量包括两种基本数据类型,分别是float和double。浮点类型的所占字节数、取值范围、精确位数以及默认值如表2-3所示。

表2-3 浮点类型所占字节数、取值范围、精确位数以及默认值

其中-3.4E38是幂指数的一种书写形式,相当于-3.4×1038。可以使用关键字float和double声明一个浮点型变量,也可以在声明该变量的同时对它进行赋值,其方法如下:

        float f;                   //声明一个单精度浮点型变量
        double d;                  //声明一个双精度浮点型变量
        float f = 3.65f;           //为一个单精度浮点型变量赋值
        double d = 365.789;        //为一个双精度浮点型变量赋值

浮点型变量除了可以赋值为浮点数外,还可以赋值为整数和字符。浮点型变量可以赋值为整数,此时返回的值应该是一个浮点数,例如:

        float f = 36;              //运行结果为36.0
        double d = 65;             //运行结果为65.0

浮点型可以赋值为字符,此时返回的值是字符对应的ASCII码的浮点值,例如:

        float f = ' A' ;             //运行结果为65.0
        double d = ' a' ;            //运行结果为97.0

在使用float和double类型的变量时,需要注意的是:如果是要使用float类型的浮点值,要在该变量后面加f。如果要使用double类型的浮点值,在该变量后面可以不加d。如果在使用float类型的浮点值时不加f,编译器会把它认为是double类型的,而double类型要比float类型的取值范围大得多,所以编译器不能将double类型的数据转换成float类型。例如,下面的程序在编译的时候就会出现错误。

        double d = 36.25;          //正常编译,运行结果36.25
        float f = 36.25f;          //正常编译,运行结果36.25
        float f = 36.25;           //编译出错

但是在double类型变量的后面加上一个f是允许的,例如下面的这种方法是可以通过编译的。

        double = 36.25f;       //正常编译,返回结果36.25

注意

在使用浮点类型变量的时候,不要超出它们的取值范围,否则编译时会出错。

2.3.3 字符类型

字符类型也是Java语言的基本数据类型之一,它可以分为字符常量和字符变量。本小节主要介绍字符常量和字符变量的使用方法,最后简单介绍一下字符之间是如何进行运算的。

1.字符常量

与Unicode标准字符集表中对应的每一个字符都可以是一个常量。表示字符常量时需要使用单引号,例如‘a' , ‘! '。

2.字符变量

Java中字符采用Unicode编码方式。字符型变量用char关键字来定义,它在内存中占2个字节(16bit),它的取值范围是0~65535,默认值为‘\u0000'。声明并赋值一个字符型变量的方法如下:

        char c = ' x' ;      //声明char类型变量并赋值

注意

定义一个字符要用单引号,不能用双引号。

如果在初始化变量中需要用到单引号,在程序中应该如何处理呢?例如,在程序中需要定义如下一个字符串。

        "It' s very beautiful"

在该字符串中,单词“It' s”中需要使用一个单引号,此时就需要用到转义字符。常用的转义字符及其对应的ASCII码如表2-4所示。

表2-4 常用的转义字符及其对应的ASCll码

表2-4中提供了一些常用的转义字符及其对应的ASCII码。这样,如果需要打印上面那个字符串,就可以使用如下的方法完成。

        System.out.println("It\' s very beautiful ");

3.字符运算

字符在存储的时候,并不是存储这个字符本身,而是将字符的ASCII码存储在内存中,所以对于语句char c=‘x',实际存储在内存中的是x对应的ASCII码120。这也使得将0~65535中任意一个整数赋给char类型的变量成为可能。因此,可以像下面这样使用char类型的变量。

        char  c= 120;           //返回结果是x

此时返回的值就是ASCII码120对应的字符x。如果想知道某一个字符对应的ASCII码值是多少,例如想知道大写字符X在ASCII码中对应的值,可以像下面这样做。

        int i= ' s' ;            //返回结果115

此时返回的115就是s在ASCII码中对应的值。也可以使用字符运算来进行字符转换,例如下面这个例子。

        char c= ' s' ;
        char c1=(char)(c+3);   //返回结果是v
        char c2=(char)(c-3);   //返回结果是p

在ASCII码中,字符s对应的是115,对字符c1进行运算后,对应ASCII码变为118,其对应的字符就是v,对字符c2进行运算后,对应ASCII码变为112,其对应的字符就是p。

在使用字符变量时需要注意的是char类型的变量没有负数。因为char类型变量所占的2字节(16bit)中,最高位不是符号位,所以它只能表示0~65535之间的数。但是可以使用一个十六进制的编码来表示一个字符,例如可以用如下的表达式表示一个字符s。

        char c = ' \u0073' ;     //返回结果s

其中,u表示Unicode编码,0073对应的是ASCII码中115的十六进制数,它表示字符s,因此该表达式返回的结果就是s。

在定义一个字符变量时要使用单引号表示,如果使用双引号,那么表示的是一个字符串而不是一个字符。例如,‘a’代表的是一个字符,“a”代表的就是一个字符串。使用char定义“a”,在程序编译的时候会出现错误。

        char c = "a";          //编译错误
        String s = "a";        //正确编译

注意

由于字符型数据不能直接和整型数据进行类型转换,所以在对字符进行运算时,需要对其进行显式的类型转换。

2.3.4 逻辑类型

逻辑类型boolean的变量只有两个值true和false,它在内存中占1个字节(bit),默认值为false。它也是Java语言中的基本数据类型之一。为boolean类型的变量声明并赋值的方法如下:

        boolean b1=false;          //声明并给boolean类型的变量赋值为false
        boolean b2=true;           //声明并给boolean类型的变量赋值为true

使用boolean类型的变量时需要注意以下几个问题。

◆ boolean类型不能与其他基本数据类型相互转换,所以boolean类型的变量只能参与逻辑关系运算(参见2.5.3小节)。

◆ boolean类型的变量一般用于程序流程的判定,或者作为程序结果的返回值。

◆ boolean类型的变量在使用时都是在声明的同时赋一个初值true或者false,以便增强程序的可读性。通常情况下不直接使用它的默认值。

2.3.5 数据类型转换

基本数据类型之间(除逻辑类型以外)可以通过数据类型的转换进行混合运算。在运算过程中,Java先将不同的数据类型统一为一种数据类型,然后再进行运算。根据数据的取值范围,数据类型的级别从低到高的顺序为:(低)byte, short, char—> int —> long—> float —> double(高)。

其中,byte, short, char的级别较低,double的级别较高。根据这个顺序,数据类型转换包括了两种方式:自动类型转换和强制类型转换。

1.自动类型转换

当把取值范围小的类型转换为取值范围大的类型的时候,也就是按照上面从左到右的顺序转换的时候,进行的是自动类型转换,这个转换是由系统自动完成的。例如:

        public class  DataConvert{
            public static void main(String args[]){
                char c = ' a' ;                  //声明一个char类型的变量并赋值为a
                int i = 100;                   //声明一个int类型的变量并赋值为100
                int i1 = c;                    //将char类型的变量赋值给int类型的变量i1
                float f = i;                   //将int类型的变量赋值给float类型的变量f
                double d = f/i1;               //将运算结果的值赋给double类型的变量d
                System.out.println("i1="+i1);  //打印输出int类型的变量i1的值
                System.out.println("f="+f);    //打印输出float类型的变量f的值
                System.out.println("d="+d);    //打印输出float类型的变量d的值
            }
        }

程序中字符类型变量a在ASCII码中对应的值是97,又由于char类型的级别比int类型的级别要低,所以将char类型的变量赋值给int类型变量的时候,系统会将字符a在ASCII码中对应的值赋给i1,自动完成这两个数据类型之间的转换。将int类型的变量赋值给float类型的时候,由于int类型的级别低于float类型,所以类型转换也是由系统来完成的,系统会将int类型的值直接转换成float类型的值。在计算f/i1的值的时候,f是float类型的,i1是int类型的,它们的运算结果是float类型的,在把float类型的值赋给double类型后,最后的结果就是double类型的。上面的程序运行结果如下:

        i1 = 97
        f = 100.0
        d = 1.0309277772903442

2.强制类型转换

当把取值范围大的类型转换为取值范围小的类型的时候,也就是按照上面从右到左的顺序转换的时候,就需要进行强制类型转换。强制类型转换的方法如下:

      (数据类型名)需要转换值

如果要把一个float类型的值转换成int类型的数,就需要强制类型转换。例如下面把一个float类型的变量强制转换为int类型的变量。

        float f = 39.65f;      //声明一个float类型的变量并为其赋初值
        int i = (int)f;        //返回结果是39

但是在强制类型转换的过程中,可能会导致数据精度损失。像上面的例子,虽然可以进行类型转换,但是最后得到的值却只有小数点前的那个整数,小数点之后的数会被系统丢弃。

在强制类型转换的过程中,可能出现的另外一个问题是由于数值超出了某一类型的取值范围,导致意想不到的结果,例如:

        int i = 150;               //为int类型的i赋值并使它超出byte类型的取值范围
        byte b = (byte)i;          //运行结果是-106

从程序运行结果来看,由于150超出了它的取值范围,从而得到了一个与期望不相符的结果。读者可能会有这样一个疑问,既然超出了类型的取值范围,编译的时候为什么不报错。下面从计算机的角度来深入分析产生这样的结果的原因。

计算机是以二进制的形式来存储数据的。当定义一个int i=150的时候,系统会把150以二进制的形式存储在内存中,150的二进制表示形式为00000000000000000000000010010110。而byte类型的数占1个字节(8bit),只能表示8位二进制数,并且它的最高位(从左数第1位)是符号位。所以,byte类型的数据只会取其低8位的10010110。对于byte类型的数来说,10010110表示的是一个负数,而这个数正好是-106的补码,所以最后输出的就是-106。也就是说,如果所要转换的值超过了8位二进制数,那么系统会将低8位的二进制数传给byte,例如:

        int i = 513;               //声明一个int类型的变量并赋值为513
        byte b = (byte)i;          //运行结果是1

在这个例子中int类型的变量513的二进制数为100000001,系统会将截取的低8位00000001传给byte类型的变量b。

在进行类型转换时需要注意如下几个问题。

◆ 对于Java的基本数据类型来说,Boolean类型的变量不能进行数据类型转换。

◆ 两个不相关的类型之间不能相互转换。例如,char类型的变量就不能和String类型的变量进行相互转换。例如不能将一个字符类型的变量赋值为一个字符串类型的变量。

          char c =' a' ;        //声明一个char类型的变量并赋值为a
          String s = c;       //编译出错

虽然char类型不能直接转换成String类型,但是还是有办法从字符串中取得每一个字符的,可以参见第5章的相关内容。

◆ 在使用类型转换的时候,注意使用的数据类型不要超过它的取值范围。例如下面的例子将150赋值给byte类型的变量,此时就会产生编译错误。

          byte b = 150;       //编译错误。150超过了byte类型的取值范围

2.4 引用数据类型

Java语言中除了8种基本数据类型之外,其他的数据类型都是引用数据类型。例如数组、字符串类型或者声明的某个类、接口等,这些都属于引用类型。在Java中可以把引用理解成一种安全的指针。在声明这些类型的变量时,引用类型的数据可以认为是某个对象的引用,例如:

        String s;                      //声明了一个String类型的引用变量s
        s = new String("abc");         //让它指向了String这个类的对象

在声明引用类型的变量的时侯,系统会为该变量分配相应的空间。创建对象后,系统会为该对象分配内存空间,并且进行相关的初始化操作。可以使用对象运算符instanceof对两个对象进行类型判断,其使用方法如下:

      (需要判断的数据类型) instanceof目标类型

该方法会返回一个布尔值,true表示需要判断的数据类型属于目标类型,false表示需要判断的数据类型不属于目标类型。听起来有点拗口,看一个例子就明白了。例如判断“ ”(空字符串)和null是不是属于String类型,可以使用如下的方法:

        public class InstanceofTest{
            public static void main(String args[]){
                System.out.println("" instanceof String); //判断空字符串是不是String类型
                System.out.println(null instanceof String); //判断null是不是String类型
            }
        }

其运行结果如下:

        true
        false

从结果中可以看出,“ ”(空字符串)属于String类型,而null并不属于String类型。这种方法在判别类的类型的时候是非常有用的。

2.5 运算符与表达式

Java语言的运算符包括算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、位移运算符、对象运算符、条件运算符、字符串连接运算符以及逗号运算符等多种运算符。表达式就是将运算符和操作数组合在一起的符合Java语法规则的序列。操作数可以是常量,也可以是变量。运算符和表达式在Java程序设计中会经常用到,本节将详细介绍Java中的运算符和表达式。

2.5.1 算术运算符与算术表达式

算术运算符主要包括加、减、乘、除、求余等常规运算符和自增、自减运算符。算术表达式是将操作数用算术运算符连接起来的符合Java语法规则的式子。

1.常规的算术运算符

常规的算术运算符包括常见的加、减、乘、除和求余运算符,以及取正值和取负值的运算符。进行常规运算的算术运算符的功能和用法如表2-5所示。

表2-5 算术运算符的功能和用法

使用常规的算术运算符时需要注意以下几点。

◆ 在进行算术运算的时候,可能会出现两个操作数的数据类型不相同的情况,这时在运算的过程中,系统会自动进行数据类型的转换。转换规则按精确度由低到高为:(低)byte, short—>int—>long—>float—>double(高)。也就是说,如果int类型和float类型的两个操作数进行算术运算,最后的值是float类型的。

◆ 如果两个整数做除法操作,取得的结果为整数,小数部分将会被舍弃。如7/3返回的结果就是2。

◆ %是求余运算符,如果两个操作数是整数,取得的余数也是整数,如9%4返回的结果就是1。如果两个操作数中一个为浮点数,取得的余数也是浮点数,如9.0%4返回的结果就是1.0。

◆ 算术运算符的结合性是从左到右的,*, /, %运算符的优先级高于+和-。

2.自增、自减运算符

自增(++)、自减(--)运算符可以使变量的值加1或者减1。对于一个变量i来说,其自增和自减形式可以分成如下4种情况。

◆ ++i表示在使用i之前,先使i的值加1。

◆ --i表示在使用i之前,先使i的值减1。

◆ i++表示在使用i之后,使i的值加1。

◆ i--表示在使用i之后,使i的值减1。

++i和i++的不同之处就在于++i先执行i=i+1的操作,然后再使用这个值,i++是先使用i的值,再执行i=i+1的操作。例如:

        int i = 1;
        int j1 = i++;          //返回的结果是1
        int j2 = ++i;          //返回的结果是3

在这个例子中,首先初始化了一个变量i并为其赋初值为1。执行j1=i++的操作时,编译器是先将i=1的值赋给j1,让j1的值为1,然后i再加1。此时i的值变为了2。执行j2=++i的操作的时候,编译器先把i的值加1,因为此时i的值已经变为2了,i的值加1以后就变成3,然后把3这个值赋给j2。

在使用自增、自减运算符时,需要注意以下两点。

◆ 自增(++)、自减(--)运算符只能用于变量,不能用于常量和表达式。

◆ 自增(++)、自减(--)运算符的优先级高于常规运算符。例如下面的用法就是不合法的。

        2++;               //编译错误,常量不能进行自增、自减运算
        (-i)++;            //编译错误,表达式不能进行自增、自减运算

3.算术表达式

将操作数用算术运算符连接起来的符合Java语法规则的式子称为算术表达式。其中,操作数还可以使用字符类型的数,也可以使用括号将需要优先计算的值括起来。a+b*3, x+y-9*(a+b)/3, 5*b-‘a’等都是合法的算术表达式。

2.5.2 赋值运算符与赋值表达式

赋值运算符包括基本赋值运算符和复合赋值运算符两种。通过在基本赋值运算符前加上不同的运算符构成复合赋值运算符。赋值表达式是将变量和表达式用赋值运算符连接起来的符合Java语法规则的式子。

1.基本赋值运算符(=)

Java中基本的赋值运算符是“=”,它的左边通常是一个变量。“=”的作用是将一个数据赋给一个变量。例如,int i=12这个赋值表达式的意思是将值12赋给整型变量i。

容易和赋值运算符“=”混淆的是“==”, “==”是关系运算符的一种。它们最主要的区别在于:“==”是用来对两个值做比较的,返回的是一个Boolean类型的值true或者false,而赋值运算符“=”不存在判断的意思(参见2.5.3节的例子)。

2.复合赋值运算符

为了简化程序,提高编译的效率,“=”前边可以加上不同的运算符,构成复合赋值运算符。复合赋值运算符包括+=, -=, *=, /=, %=, &=, —=, ^=, >>=, <<=, >>>=。例如“+=”的使用方法如下:

        int i = 2;
        i +=3;      //返回的结果是5,相当于i=i+3(将i的值+3后再赋给i)

其他复合赋值运算符的使用方法和运算符“+=”的使用方法是一样的。其中,运算符>>=,<<=, >>>=是位运算符,将在2.5.5小节中介绍。

3.赋值表达式

将变量和表达式用赋值运算符连接起来的符合Java语法规则的式子称为赋值表达式。赋值表达式也可以包含复合赋值运算符。a=b+5, x+=y-=a*a, c=(b/5)+=a, c=(b/5)+(a=6)等都是合法的赋值表达式。

2.5.3 关系运算符与关系表达式

关系运算符是用来对两个值进行比较的,比较后的结果是一个Boolean类型的值true或者false。关系运算符包括==, ! =, >, <, >=, <= 6种。下面通过表2-6来看一下关系运算符的功能和用法。

表2-6 关系运算符的功能和用法

下面举例说明关系运算符的用法。

        int a=2;                   //定义并将int类型的变量a初始化为2
        int b=3;                   //定义并将int类型的变量b初始化为3
        a==b;                      //返回的结果为false
        a! =b;                      //返回的结果为true
        a>b;                       //返回的结果为false
        a<b;                       //返回的结果为true
        a>=b;                      //返回的结果为false
        a<=b;                      //返回的结果为true

其中,等号运算符“==”是用来判断两个数或者两个对象是否相等的,它返回一个逻辑值true或者false,一般用于条件或者循环的判断表达式中,例如:

        if(a == b){                //判断a是否等于b
        ……
        }

注意不要和赋值运算符“=”相混淆,这里如果写成a=b,就成了把b的值赋给a了。

将结果为数值型的变量和表达式用关系运算符连接起来的符合Java语法规则的式子称为关系表达式。其中,关系运算符连接的两个表达式可以为算术表达式、逻辑表达式、字符表达式和赋值表达式。例如c=a>b,(a+b)<(c=3), ‘a' >‘A’等都是合法的表达式。

2.5.4 逻辑运算符与逻辑表达式

逻辑运算符是对boolean类型的数据进行操作的运算符。经过逻辑运算后得到的返回值也是boolean类型的。逻辑运算符包括逻辑与(&)、逻辑或(—)、逻辑非(!)、条件与(&&)和条件或(——),后两种运算符也叫短路运算符。表2-7是逻辑运算符的功能表。

表2-7 逻辑运算符的功能表

下面通过实例来看一下逻辑运算符的用法。先定义两个int类型的数int a=2, int b=3,然后使用逻辑运算符看一下返回值的情况。

        !(a<b);            //返回值是false
        (a>b)&(b>0);       //返回值是false
        (a>b)—(b>0);       //返回值是true
        (a>b)&&(b>0);      //返回值是false
        (a>b)——(b>0);      //返回值是true

在这个例子中,使用运算符“&”与“&&”、“—”与“——”虽然返回的结果都是一样的,但是它们在进行逻辑运算的时候还是有区别的。

1.&与&&

&与&&的区别在于,如果运算符左边第一个表达式或者操作数为false,那么对于&&运算符来说,它将不会对余下的表达式或者操作数做判断,而直接就会返回false。

&&可以加快运算的速度,使得编译器在判断第一个逻辑值是false的情况下,不需要再对后面的逻辑值做出判断,但这并不意味着&运算符就没有用处。有些时候在第一个逻辑值是false的情况下,还需要计算运算符右边的表达式的结果,这时使用&&就会出现问题,例如下面这种情况。

        int a = 2;                         //定义一个整型变量并为其赋初值为2
        int b = 3;                         //定义一个整型变量并为其赋初值为3
        if((a>b)&&(++a>0)){                //对两个整型变量进行逻辑运算
            ……
        }
        System.out.println("a="+a);        //结果是a=2

这里a>b的结果是false,最终的返回值就是false,第二个语句永远也不会得到执行。if判断语句里边的代码也不会得到执行。在if判断结束以后,a的值还是2,并没有发生变化。如果使用&的话,a的值就会变为3,读者可以自己动手试一下。

2.I与II

I与II的区别在于,如果运算符左边第一个表达式或者操作数为true,那么对于——运算符来说,它将不会对余下的表达式或者操作数做判断,而直接就会返回true。把上面的例子改动一下来看—与——的用法。

        int a = 2;                         //定义一个整型变量并为其赋初值为2
        int b = 3;                         //定义一个整型变量并为其赋初值为3
        if((a<b)——(++a>0)){                //对两个整型变量进行逻辑运算
                System.out.println("a="+a); //结果是a=2
        }

这里a<b的结果是true,最终的返回值就是true,第二个语句永远也不会得到执行。所以a的值还是2,并没有发生变化。如果把——改成—, a的值就变为3了,读者可以自己试一下。

3.逻辑表达式

将结果为boolean类型的变量和表达式用逻辑运算符连接起来的符合Java语法规则的式子称为逻辑表达式。(a<b)——(c>0),15>8&a<2, a%5==0&&b/3! =0等都是合法的逻辑表达式。

2.5.5 位运算符与位运算

位运算符是用来对二进制位进行运算的,可以使用它对整数或者字符型数据进行按位操作。位运算符包括按位与(&)、按位或(—)、按位非(~)和按位异或(^)。位运算符的真值表如表2-8所示。

表2-8 位运算符的真值表

在用位运算符进行计算时,以十进制为例,要把需要计算的数据转换成二进制的数,然后再按照真值表的运算法则计算,最后再将计算得到的二进制结果转换为十进制数。

1.按位与(&)

对数据进行按位与的运算法则为:如果两个数据对应位上都是1,那么该对应位的值就为1,否则该位的值就为0。以int类型的数据6&9为例,看一下按位与是如何计算的。

首先将6转换成二进制数,这里只取它的低8位00000110。然后将9转换成二进制数,这里取它的低8位00001001。最后按照按位与的运算法则进行按位与运算,运算过程如下:

      0 0 0 0 0 1 1 0
&           0 0 0 0 1 0 0 1
(结果)      0 0 0 0 0 0 0 0

将6&9的二进制数转换为十进制后,最后得到的结果就是0。从这个结果中也可以看出按位与的一个作用,就是可以用它来清零。只要找到的两个数具备像这个例子中的两个数的特点,即如果一个数某位是1,那么另一个数与它对应的位置是0,把这两个数进行按位与(&)运算就可以达到清零的目的了。

2.按位或(—)

对数据进行按位或的运算法则为:如果两个数对应位上都是0,那么该对应位的值就为0,否则该位的值就为1。下面以int类型的数据6—9为例,看一下按位或是如何计算的。

      0 0 0 0 0 1 1 0
—           0 0 0 0 1 0 0 1
(结果)      0 0 0 0 1 1 1 1

将6—9的二进制数结果00001111转换为十进制后,最后得到的结果是15。

3.按位非(~)

按位非(~)是一个单元运算符,它的运算法则为:如果某个数对应位上是1,则该位上最后得到的结果就是0,如果其对应位上是0,则该位上最后得到的结果就是1。以int类型的数6为例(只取低8位),看一下按位非是如何计算的。

            ~       0 0 0 0 0 1 1 0
        (结果)      1 1 1 1 1 0 0 1

将结果的二进制数转换为十进制后,最后得到的结果是-7。因为byte类型的最高位是符号位,1表示负数,0表示正数,所以这个结果应该是个负数。又因为11111001是-7的补码,所以最后得到的结果就是-7。

4.按位异或(^)

对数据进行按位异或的运算法则为:如果两个数据对应位上的值相同,那么该对应位的值就为0,如果两个数据对应位上的值相反,那么该对应位的值就为1。还以6^9为例,看一下按位异或是如何计算的。

        0 0 0 0 0 1 1 0
^           0 0 0 0 1 0 0 1
(结果)      0 0 0 0 1 1 1 1

将6^9结果的二进制数转换为十进制后,最后得到的结果是15。再来看一个例子,它用来计算15^9的值。

        0 0 0 0 1 1 1 1
^           0 0 0 0 1 0 0 1
(结果)      0 0 0 0 0 1 1 0

将15^9结果的二进制数转换为十进制后,最后得到的结果是6。细心的读者可能会发现,将6^9得到的值再和9做一次异或运算后,又得到了6。这个是由异或运算法则决定的,可以利用异或运算的这个性质来实现两个数的交换。用异或运算实现两个数交换的语句如下:

        a = a ^ b;
        b = b ^ a;
        a = a ^ b;

如果原来的两个数是int a=2, int b=3,那么经过上面的转换之后输出的结果就是a=3, b=2。这种实现两个数交换的方法不需要再设置临时变量,有兴趣的读者不妨自己试一试。

2.5.6 移位运算符

移位运算符可以用来操作二进制位。Java语言中的移位运算符主要包括左移位运算符(<<)、右移位运算符(>>)和无符号右移位运算符(>>>)3种。

1.左移位运算符(<<)

左移位运算符的运算法则为:高位左移后,右侧补0。例如将int类型的数据6左移1位,即6<<1,看一下运算的过程。

6的二进制数表示为000000000000000000000000000000110,将它左移1位后变为00000000000000000000000000001100(左移1位后,最后1位补0)。

得到的这个二进制数转换为十进制后最后的结果是12。可以看出,一个数左移1位得到的值,与该数乘以2得到的值相等,左移n位就相当于乘以2n,即a<<n相当于a×2n。所以以上这个例子中,6<<1和6×21得到的值是一样的。

2.右移位运算符(>>)

右移位运算符的运算法则为:右移后左侧补充符号位,如果是正数,右移后高位补0,如果是负数,右移后高位补1。

首先看一下使用右移运算符将十进制数6右移一位的计算过程。6的二进制数表示为000000000000000000000000000000110,将它右移1位后变为000000000000000000000000000000011(右移1位后,最高位补0)。得到的这个二进制数转换为十进制后,最后的结果是3。

再来看一下使用右移位运算符将十进制数-6右移一位的计算过程。-6的二进制数表示为11111111111111111111111111111010,将它右移1位后变为11111111111111111111111111111101(右移1位后,最高位补1)。得到的这个二进制数转换为十进制后,最后的结果是-3。

与左移位的结果比较,6>>1和6/21得到的值是一样的,-6>>1和-6/21得到的值也是一样的。可以看出,一个数右移1位得到的值,与该数除以2得到的值相等,右移n位就相当于除以2n,即a>>n相当于a/2n,如果有余数会被舍弃。

3.无符号右移位运算符(>>>)

无符号右移位运算符的运算法则为:右移后,左侧补0。也就是说,一个数经过无符号右移以后,得到的数一定是大于等于0的。下面来看一下-6>>>1的计算过程。

-6的二进制数表示为11111111111111111111111111111010,将它右移1位后变为01111111111111111111111111111101(右移1位后,最高位补0)。得到的这个二进制数转换为十进制后,最后的结果是2147 483645。

移位运算符在使用中需要注意如下问题。

◆ 对于byte, short, char型的数据,系统会先将它们自动转换为int型后再移位。

◆ 对于int型整数移位a>>n,系统会将n%32的值作为要移位的位数,也就是说,对于一个int类型的数据来说,a>>33和a>>1的值是相同的。

◆ 对于long型整数移位a>>n,系统会将n%64的值作为要移位的位数。

◆ 整数不断地做右移位运算,最后得到的结果会是0,负数不断地做右移位运算,最后得到的结果会是-1。

2.5.7 条件运算符

现在考虑这样一个问题,有两个int类型的变量a和b,需要把其中值较大的那个int类型变量的值赋值给第三个int类型的变量c。像这样需要根据一个变量或者表达式的值来决定最后的输出结果的问题,就可以考虑使用Java语言中的条件运算符来完成。条件运算符(? :)是Java语言中的一个三元运算符,它含有三个操作数,其使用方法如下:

      表达式1 ? 表达式2:表达式3;

其中,表达式1是一个逻辑表达式。如果表达式1的值为true,则该条件表达式就会把表达式2的值作为最后的结果。如果表达式1的值为false,则该条件表达式就会把表达式3的值作为最后的结果。

了解了条件运算符的使用方法后,再来看一下本小节开始提到的那个问题,要想把两个int类型的变量a和b中较大的一个的值赋给c,使用条件运算符可以按如下的方法完成。

        int a = 2;                     //定义一个整型变量并为其赋初值为2
        int b = 3;                     //定义一个整型变量并为其赋初值为3
        int c = a>b? a:b;               //如果a>b为true就返回a的值,否则就返回b的值

这段代码返回的值是3。因为a>b返回的是false,所以就会把b的值赋给c。

如果表达式2和表达式3的数据类型不相同,那么最后返回的结果将是两个表达式当中精度较高的那个数据类型的。也就是说,如果表达式2是一个整型值,表达式3是一个double类型的值,那么最后返回的结果将是double类型的,例如:

        int a = 2;                     //定义一个整型变量并为其赋初值为2
        double b = 1.5                 //定义一个双精度浮点型变量并为其赋初值为1.5
        int c = a>b? a:b;               //如果a>b为true就返回a的值,否则就返回b的值

这段代码返回的值是2.0。由于b的值是double类型的,它的精度要高于int类型,所以最后的返回结果也是一个double类型的值。

注意

条件运算符作为Java语言的运算符之一,可以和其他运算符一起使用来表示复杂的表达式。它和3.1.2小节中要讲到的if…else语句有相同的执行效果。

2.5.8 字符串连接运算符

在Java语言中,“+”不仅可以作为算术运算符进行算术运算,也可以进行字符串的连接。下面来看一个使用“+”运算符进行算术运算和字符串连接的例子。

        public class StringConnectTest{
                public static void main(String args[]){
                    int a = 2;                 //定义一个整型变量并为其赋初值为2
                    int b = 3;                 //定义一个整型变量并为其赋初值为3
                    String s1 = "Java";        //定义一个字符串并为该字符串赋初值
                    String s2 = "Program"; //定义一个字符串并为该字符串赋初值
                    System.out.println(a+b);   //用“+”连接两个数值类型的值
                    System.out.println(s1+s2); //用“+”连接两个字符串类型的值
                    System.out.println(s1+a);  //用“+”连接数值类型值和字符串
              }
        }

这个例子中打印输出了3个使用加号进行计算的运行结果。其中第一个使用“+”连接两个数值类型的值,其打印输出的结果是这两个int类型的数的和。第二个使用“+”连接两个字符串类型的值,其打印输出的结果是将这两个字符串连接在一起。第三个使用“+”连接数值类型和字符串,程序首先会将int类型的数据转换成字符串类型,然后再将它和另一个字符串的值连接在一起,最后打印输出的就是它们连接后的结果。这个例子的运行结果如图2-1所示。

图2-1 “+”符号的使用

第一个加号连接的是两个int类型的值,这两个值相加得到的结果是5。第二个加号连接的是两个String类型的值,两个字符串连接得到的结果是JavaProgram。第三个加号连接的是一个数值类型和一个String类型的值,此时系统会把数值类型的值转换为String类型的值,然后再把它们连接起来。上面这个例子中,系统会把int类型的2先转换成String类型的“2”,再把它和字符串s1的值连接起来。

不仅是int类型的数据,包括其他数据类型的数据,当使用“+”和String类型的数据进行连接的时候,系统都会先将那个数据类型的数据转换成String类型,然后再进行字符串的连接操作。

2.5.9 运算符的优先级

为了了解运算符的优先级,先看一个综合使用运算符的表达式的例子。下面是一个包括了算术运算符、关系运算符、逻辑运算符等多种运算符的表达式。

        5-3>2——6+4>=9&2*7<8-1&&true—2*9<=18

对于这个表达式,要想准确地得到这个表达式的结果,就需要了解Java语言中运算符的优先级。Java语言中运算符的优先级顺序如表2-9所示。

表2-9 运算符的优先级顺序

通过表2-9中对Java语言中运算符优先级的描述,读者应该对运算符的结合性有了一个大致的了解,再回过头来看一下本小节开头提到的那个表达式。下面结合表2-9对Java语言中运算符优先级的描述,来介绍一下该表达式的运算过程。

step 1 进行5-3,6+4,2*7,8-1和2*9的运算,式子变为2>2——10>=9&14<7&true—18<=18。

step 2 进行2>2,10>=9,14<7和18<=18的运算,式子变为false——true&false&&true—true。

step 3 进行true&false运算,式子变为false——false&&true—true。

step 4 进行false—true运算,式子变为false——false&&true。

step 5 进行false&&true运算,式子变为false——false。

step 6 进行false——false运算,最后的结果为false。

虽然上述表达式能够按照运算符的优先级顺序正确地执行,但是在实际应用中,尤其是在软件项目中,不建议直接使用运算符的优先级顺序来执行含有多种运算符的表达式,原因有以下两点。

◆ 一是记忆困难。运算符的优先级顺序从最高到最低分为了十几级,其中既有左结合性的运算符又有右结合性的运算符,完全记住不是很容易。

◆ 二是不利于程序的可读性。在实际应用中,一个项目往往不是一个人就能完成的,如果一个表达式含有多种不同的运算符,即使写该表达式的程序员熟悉运算符的优先级顺序,但并不能保证读到该表达式的所有人都对运算符的优先级顺序有很深的了解。

既然直接使用运算符的优先级顺序并不是一种很好的方式,那么就需要找到一种好的方式来解决问题。如果在一个表达式中同时使用多种运算符,一种比较好的方法是在表达式中把需要优先运算的式子加上括号。因为括号在运算符的优先级顺序表中具有最高优先级。下面使用括号把本小节开头给出的那个表达式重新书写如下:

        (5-3>2)——((6+4>=9)&(2*7<8-1))&&(true—(2*9<=18))

用这种方式书写的表达式,即使不是很熟悉运算符优先级顺序的程序员也可以理解该表达式的运算过程,同时程序的可读性也增强了。

2.6 全局变量和局部变量

对于一个变量定义int a = 0来说,根据程序需要可以将它放到程序的代码段中定义,也可以放到方法体中定义,还可以把它定义在类的内部、方法体的外部。定义在不同的位置上,会有不同的作用域,例如:

        (1)public class VariableScope{
        (2) int a1 = 0;                            //类的内部、方法体的外部定义变量a1
        (3) ……
        (4)  void method{
        (5) int a2 = 0;                            //方法体中定义变量a2
        (6) ……
        (7)       {
        (8)       int a3 = 0;                     //代码段中定义的变量a3
        (9)        ……
        (10)        }
        (11)      }
        (12)}

这里a1是定义在类的内部、方法体的外部的变量,它的作用域是从第2行到第12行,a2是在方法体中定义的变量,它的作用域是从第5行到第11行,a3是在代码段中定义的变量,它的作用域是从第8行到第10行。

像a1那样定义在类的内部、方法体的外部的变量称为全局变量(也叫成员变量),像a2和a3那样定义在方法体或者程序代码段中的变量称为局部变量。全局变量(成员变量)在整个类中都可见,在类中任何地方都可以被访问,而局部变量只能在自己的方法体或者代码段中才可以被访问。当方法体或者代码段执行完之后,定义在方法体或者代码段中的变量就会失去作用。下面通过一个例子来看一下全局变量(成员变量)和局部变量的作用域。

        public class VariableScope{
            int a1 = 0;                            //定义全局变量并为其赋初值为0
            void method1(){
                    int a2 = 0;                    //定义方法体中的局部变量并为其赋初值为0
                    a2 =a2+1;                      //将局部变量的值加1
                    System.out.println("a2="+a2);  //打印输出局部变量的值
                    System.out.println("a1="+a1);  //打印输出全局变量的值
                }
                public static void main(String[] args){
                    int a3 = 0;                        //定义main()方法的局部变量a3
                    VariableScope vs = new VariableScope(); //创建类VariableScope的对象
                    vs.method1();                      //调用方法method1
                    //a3=a2;                           //编译错误
                }
        }

在该段代码中,首先定义了一个全局的整型变量a1并为其赋初值为0。method1()方法中定了一个方法的局部变量a2,并将a2的值加1后,将a1和a2的值打印输出。在main()方法中,定义一个方法的局部变量a3并通过类VariableScope的对象vs调用方法method1(),其输出结果如图2-2所示。

图2-2 成员变量和局部变量的作用域

在main()方法的最后,有一条语句“a3=a2; ”被注释掉了。因为a2是方法method1()的局部变量,不能作用于main()方法,所以如果把那一句的注释去掉,会出现编译错误,读者可以自己试一试。

对于全局变量和局部变量来说,应该尽可能地使用局部变量,尽可能地限制作用域,以便提高代码的安全性。

2.7 小结

本章主要介绍的是Java语言的基础知识。其中,Java语言中的关键字、常量和变量在Java程序设计中需要经常使用,必须熟练掌握。此外,重点要掌握Java语言中的8种基本数据类型和引用数据类型,以及程序中需要用到的各种运算符与表达式。同时,还应该了解全局变量和局部变量在程序中各自的使用范围。