第二章 Java语言基础
一、标识符和关键字
- 用来标识类名、接口名、变量名、方法名、数组名、包名的有效字符序列称为标识符。简单点说,是程序开发者在Java程序中自己起的名字。Java的标识符只能由字母、下划线、美元符号和数字组成,合法标识符的规则如下:
- 标识符由字母、下划线、美元符号和数字组成,长度不受限制;
- 标识符的第一个字符不能是数字字符,可以是字母、下划线、美元符号;
- 标识符不能是Java语言的关键字;
- 标识符不能是true、false和null等Java预定义的文字值;
- 标识符不能是Java类库中已使用的名字。
⚠ 注意: Java使用Unicode字符集,字母并不限于英文字母,
而是Unicode字符集支持的任意语言的文字,包括汉字。
1 | // 合法标识符的例子: |
- 关键字是已经被Java语言自身使用的字符序列,在Java程序中具有特定的意义与作用,不能在Java程序中将关键字用作标识符。下面这张表列出了Java语言的53个关键字:
二、基本数据类型
Java的变量和其它语言的变量一样,都是用于保存值。变量所保存的值可以被改变,所以称为变量。变量有自己的名称(变量名)和类型(数据类型)。变量在使用前必须先使用数据类型进行声明,在声明的同时可以赋给初值,如
1 | int i; |
Java有8种基本数据类型,分别是boolean、byte、short、char、int、long、float、double。这8种类型可分为布尔类型、整数类型、字符类型、浮点类型这四类,如下所示:
1. 布尔类型:boolean
用关键字boolean声明变量;只能取两个值:true,false
1 | // 例: |
⚠ 注意: Java的布尔类型只能用true表示真值,用false表示假值,不允许在数值类型和布尔类型之间转换。
2. 整数类型:byte、short、int、long
(1) int型
用关键字int声明变量; 取值可以是十进制数(如123)、八进制数(以0为前缀,如077)、十六进制数(以0x为前缀,如 0x3abc);
1 | // 例: |
⚠ 注意:Java的整数类型都是有符号的,没有无符号整数,整数在没有后缀的情况下默认是int型。
(2) byte型
用关键字byte声明变量; 取值为byte型范围内的整数,可以是十进制、八进制、十六进制。可用强制类型转换来强调一个整数是byte型,如(byte)-12, (byte)27。尽管12和(byte)-12的值相同,但在内存中的表示不一样,12是四个字节的int型,而(byte)-12是一个字节的byte型。
在byte型取值范围内的整数可以直接赋值给byte型变量,如:
1 | byte x = -12, tom = 28; |
而超出byte型取值范围的整数,必须强制转换后才能给byte型变量赋值,如:
1 | byte y = (byte)234, z = (byte)-197; |
(3) short型
用关键字short声明变量;取值为short型范围内的整数,可以是十进制、八进制、十六进制。可用强制转换来强调一个整数是short型,如(short)-12, (short)27。强制转换后,(short)-12和 (short)27变成两个字节的整型数。
与byte型类似,在short型取值范围内的整数可以直接赋值给short型变量,而超出short型取值范围的整数,必须强制转换后才能给byte型变量赋值。
(4) long型
用关键字long声明变量;用带后缀L或l的整数来表示long型整数,可以是十进制,如108L;八进制,如0712L;十六进制,如0x3aBcl。
1 | 例: |
3. 字符型:char
用关键字char声明的一个变量,分配两个字节内存,没有符号位,不存在负的char(相当于无符号整型);
取值为用英文单引号括起来的一个Unicode字符,如‘A’、‘b’、‘?’、‘9’、‘好’、‘\t’等。
⚠ 注意:一个字符的值就是该字符的Unicode编码。
一个字符的Unicode编码是该字符在Unicode表中的位置值(ASCII编码也一样),Unicode字符表的前128个字符正好是ASCII字符。
转义字符:有些字符不能通过键盘输入或者用于表示特殊的功能,就需要使用转义字符。
例:‘\n’(换行)、‘\b’(退格)、‘\t’(水平制表)、‘\’’(单引号)、‘\’’’(双引号)、‘\’(反斜线)、‘\r’(回车)、‘\f’(走纸换页)等。
十六进制转义字符:用一个字符的Unicode编码的十六进制数表示该字符,格式为:‘\u****’,如‘\u0041’表示字符A,‘\u0061’表示字符a。
可以把字符的值当作整数使用,无需强制转换,如字符‘A’的值就是整数65(它的Unicode编码值),则int m = ‘A’,相当于把整数65赋值给int型变量m。如果一个整型数是一个Unicode编码,那么这个整数可以直接作为Unicode字符使用,如char n = 65,相当于把字符‘A’赋值给cha型变量n。
4. 浮点类型:float、double
如果数值中包含小数点、指数(e),或者有字母后缀f/F或d/D,则为浮点数。
(1) float型(单精度浮点型)
用关键字float声明变量,有8位有效数字;任意数值(包括整型数) 加后缀F或f就成为float型,而且后缀不能省略;
例:453.543f,21345.5F,231.0f,100F,2e40f(科学记数法)。
(2) double型(双精度浮点型)
用关键字double声明变量,有16位有效数字;浮点数在无后缀的情况下默认是double型,任意数值(包括整型数)加后缀D或d就成为double型。
三、数据类型转换
1. 数据类型的级别从低到高依次是
byte short char int long float double
2. 数据类型的转换规则
当把低级别类型的值转换成高级别类型的值时,类型转换可以自动完成,不需要强制转换。如下例:
1 | float x = 100; //赋值后x的值变为100.0。 |
当把高级别类型的值转换成低级别类型的值时,必须使用强制类型转换。
转换格式:(类型名)要转换的值;
1 | 如: |
转换后,x、y、z的值分别为34、56、1999。
当把int型整数赋值给byte、short和char型变量时,如果不超出该类型的取值范围,可以直接赋值,如:
1 | byte a = 100; |
否则必须强制转换,如:
1 | byte a = (byte)128; //转换后a的值为-128 |
四、数据的输入与输出
1. 输出基本类型的数据
可以使用System.out.println()或System.out.print()输出字符串、变量、常量、表达式的值。前者输出后换行,后者不换行。可以使用符号“+”将变量、常量或者表达式与一个字符串连接后一起输出。例如,m和sum的值分别是10和100,则:
1 | System.out.println(m + "个数的和为" + sum); // 输出:10个数的和为100 |
⚠ 注意:只要在“+”号的前后有字符串,执行的就是连接操作,否则执行普通的加法运算。
JDK1.5新增了类似C语言printf函数的输出方法:System.out.printf("格式控制部分",表达式1,表达式2,…表达式n);
格式控制部分由格式符号%d、%c、%f、%s和普通的字符组成。普通字符原样输出,格式符号用来控制输出格式。
- %d:输出int类型数据值;
- %md:输出的int型数据占m列;
- %c:输出char型数据;
- %s:输出字符串数据。
- %f:输出浮点型数据,小数保留6位,不够的话补0;
- %m.nf:输出的浮点型数据占m列,小数保留n位。
1 | // 例如: |
- 输入基本类型的数据
使用Scanner类的对象执行输入,先创建对象:Scanner reader = new Scanner(System.in);
然后通过reader对象分别调用next()、nextLine()、nextBoolean()、 nextByte()、 nextShort()、 nextInt()、nextLong()、nextFloat()、nextDouble()等方法从终端读入一个字符串(遇到空格或回车符结束)、一行字符串(遇到回车符才结束)、一个boolean型数据、一个byte型数据、一个short型数据、一个int型数据、一个long型数据、一个float型数据、一个double()型数据。要读入多个数据,就要多次调用nextXXX()方法。
为了正确的调用nextXXX()方法,可以先调用hasNextXXX()方法判断将要读入的数据是否为期望的数据类型,然后再调用相应的nextXXX()方法读取数据。hasNextXXX()方法与nextXXX()方法相对应,如hasNextBoolean()、hasNextByte()、hasNextFloat()、hasNext()、hasNextLine()…等。
⚠ 注意:字符类型没有对应的nextXXX()方法,可以先调用next()或nextLine()方法将控制台的输入按字符串读入,则返回一个String对象,然后调用String类的char charAt(int index)方法提取指定位置的字符,如charAt(0)提取的是第一个字符。
1 | import java.util.Scanner; |
五、运算符
运算符具有优先级
和结合性
这两个属性
运算符的优先级
所谓优先级,就是当多个运算符出现在同一个表达式时,先执行哪个运算符。Java语言会比较各个运算符的优先级,按照优先级从高到低的顺序执行。例如,在表达式2+36-7中,运算符的优先级最高,因此先计算3*6,然后再执行其它运算。运算符的优先级总共有14级,从1级到14级是逐级降低的。
运算符的结合性
所谓结合性,就是当一个表达式中出现若干个连续相邻的优先级相同的运算符时,先执行哪个运算符,先执行左边的叫左结合,先执行右边的叫右结合。例如,若#是一个运算符,有表达式a#b#c,如果#是左结合的,那么该表达式被解析为(a#b)#c,如果#是右结合的,那么该表达式将被解析为a#(b#c)。比如,表达式a*b/c,和/的优先级相同,结合性都是从左到右,所以先算,然后算/。
- 单目运算符:只有一个操作数的运算符,比如x++。
- 双目运算符: 有两个操作数的运算符,比如x+y。
- 三目运算符:有三个操作数的运算符,比如a>b?1:0。
1. 算术运算符
- (1) +, -
当作为双目运算符时,+是加法运算符,-是减法运算符,结合性从左到右,优先级是4级。当作为单目运算符时,+是正号运算符,-是负号运算符,结合性从右到左,优先级是2级,正号运算符保持操作数的正负不变,负号操作符逆转操作数的正负。 - (2) *, /, %
双目运算符,*是乘法运算符,/是除法运算符,%是求余运算符,结合性从左到右,优先级是3级。两个整数的除法运算得到的结果也是整数,舍去余数。求余运算符的操作数可以是整数或浮点数,整数的运算规则:a%b=a-(a/b)b,浮点数的运算规则:a%b=a-qb, q=int(a/b); - (3) ++, –
单目运算符,++是自增运算符,将变量自身加1,–是自减运算符,将变量自身减1。操作数必须是变量,不能是常量或表达式。结合性从右向左,优先级是2级。当出现在表达式中时,有前置(x和–x )或后置(x和 x–)两种情况,前置是先执行自增或自减运算,再将该操作数用于表达式计算;后置则相反,先将该操作数用于表达式计算,再执行自增或自减运算。 - (4) 算术运算的精度
算术运算符能接受的操作数的最低类型是int型,凡是低于int型的操作数都会首先被提升到int型。此外,若两个操作数的类型不同,则会将较低的操作数类型转换为较高的操作数类型。
2. 关系运算符
- (1) >, >=, <, <=
双目运算符,将两个操作数的大小进行比较,大小关系成立的话结果为true,否则为false。>表示大于关系,>=表示大于等于关系,<表示小于关系,<=表示小于等于关系。结合性从左到右,优先级是第6级。 - (2) ==, !=
双目运算符,==运算符比较两个操作数是否相等,!=运算符比较两个操作数是否不相等,成立的话结果为true,否则为false。结合性从左到右,优先级是7级。
3. 逻辑运算符
- (1) &&, ||, &, |
双目运算符,&&和&是逻辑与运算符,前者具有短路运算功能,后者没有。||和|是逻辑或运算符,前者具有短路运算功能,后者没有。短路运算功能就是如果左边的逻辑运算已经能够判断出最终结果,则不再继续往右执行。结合性都是从左到右,&&的优先级11级,||是12级。 - (2) !
单目运算符,逻辑非运算符,结合性从右到左,优先级是2级。
4. 移位运算符
双目运算符,优先级是5级,结合性是从左到右,两个操作数都必须是整数类型(包括char型)。操作数低于int型的,会先提升到int型,若有一个操作数是long型则都提升到long型。提升规则为:正数的高位用0填充,负数用1填充。左操作数是被移位的数,右操作数是移位量,不超过左操作数的位长度,超出后则对右操作数执行取余运算:n%32(int型)或n%64(long型)。
- (1) <<
左移运算符:高位移出,低位补0。相当于乘2。如:a<<5是把a左移5位。 - (2) >>
右移运算符:低位移出,高位用符号位填充。相当于除2。如:a>>5是把a右移5位。 - (3) >>>(无符号右移)
无符号右移运算符:低位移出,高位用0填充。如:a>>5是把a无符号右移5位。
5. 位运算符
操作数低于int型的要提升至int型。
- (1) &
按位与。双目运算符,结合性从左到右,优先级是第8级。 - (2) |
按位或,双目运算符,结合性从左到右,优先级是第8级。 - (3) ^
按位异或,双目运算符,结合性从左到右,优先级是第8级。 - (4) ~
按位取反,单目运算符,结合性从右到左,优先级是2级。
⚠ 注意: &和|既可以做逻辑运算符,也可以做位运算符。
具体做什么,取决于操作数。如果操作数是布尔型,则是逻辑运算符,如果是整型,则是位运算符。
6. 条件运算符:op1?op2:op3
三目运算符,结合性从右到左,优先级13级。例:(x>0)?5:0表示,如果x>0成立则该表达式的值为5,否则为0。
7. instanceof 运算符
双目运算符,左操作数是一个对象,右操作数是一个类型。当左边的对象属于右边的类型时,结果是true,否则是false。例: A a = new A();
boolean b = a instanceof A;
8. 赋值运算符:=
双目运算符,左操作数必须是变量,结合性从右到左,优先级是14级。赋值运算符的运算结果是左操作数被赋予的值。如果赋值运算符两边的类型不同,需要进行类型转换:若左操作数的类型高于右边运算结果的类型,则转换可以自动完成,不需要强制转换,否则必须将右边运算结果的类型强制转换为左操作数的类型。
9. 复合赋值运算符: +=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、>>>=
双目运算符,左操作数必须是变量。左操作数先和右操作数执行相应的运算,然后再赋值给左操作数。结合性从右到左,优先级是14级。复合赋值运算符的运算结果同样是是左操作数被赋予的值。注意:当复合赋值运算符的左右两边的类型不同时,不需要强制类型转换。
六、表达式和语句
1. 表达式
由运算符和操作数组成的符合Java语法规则的式子称为表达式,执行指定的运算并返回确定的值,如int x = a+b*c+5。表达式必须要包含赋值运算,x++也算表达式,因为隐含了赋值运算。判断是否为表达式很简单,如果在式子后面加上分号构成合法语句,那么就是表达式。
2. 语句
- 表达式语句:一个表达式末尾加上分号“;”
- 方法调用语句:如reader.nextInt();
- 复合语句:用一对花括号“{”和“}”把若干语句括起来构成复合语句
- 控制语句:条件语句,循环语句,跳转语句
- package语句和 import语句
- 单独一个分号也是一条语句,称为空语句
上机作业:
从键盘输入任意一个英文字母,大写的转成小写的然后输出,小写的转成大写的然后输出。自己写程序实现大小写字母转换,不允许使用Java类库转换大小写字母。
提示:
(1) 大写字母和小写字母相差一个固定的值
(2) 用Character的静态方法isUpperCase(char ch)判断是否大写字母,isLowerCase(char ch)判断是否小写字母,
如:
1 | boolean a = Character. isUpperCase(‘A’); |
则a的值为true,b的值为false。
程序大致结构:
1 | import java.util.Scanner; |
七、选择结构
if条件语句
(1) if语句
形式:
1 | if(关系表达式) { |
如果圆括号内的关系表达式成立,则执行花括号内的代码块,否则不执行。
例:
1 | if(x > 0) { |
(2)if-else语句
形式:
1 | if(关系表达式) { |
如果圆括号内的关系表达式成立,则执行if分支的代码块,否则执行else分支的代码块。
例:
1 | if(x>0) { |
(3) if-else if语句
形式:
1 | if(关系表达式1) { |
从if分支开始依次判断,哪个分支的条件成立就执行哪个分支的代码块,否则接着判断下一个分支。前面分支的条件均不成立时,如果有else分支,就执行else分支的代码块。执行了任何一个分支的代码块后就退出,不再判断其它分支的条件。如果没有else分支,有可能没有一个分支会被执行。
(4) if条件语句的嵌套
在if语句的分支里面,可以嵌套if语句
1 | if(关系表达式1){ |
switch语句
1 | switch(表达式) { |
- switch语句将表达式的值依次与每个case的目标值进行比较,与哪个case的目标值匹配上就执行该case的语句块。
- 每个case语句块的最后一条语句通常是break语句,用于退出switch结构。
- 如果没有break语句,则执行完当前case的语句块后,会接着执行下一个case的语句块。
- 注意:switch表达式一旦与某个case的目标值匹配上,就不会再与其它case的目标值进行匹配,而是从当前匹配位置开始依次执行后面的语句,直到遇到-break语句或者执行完了所有的语句。
- 找不到匹配的case目标值时,会执行default语句块。
- default不是必须有的,default的位置可以是任意的,不是必须在最后。
- switch表达式的类型可以是byte、short、char、int、enum、String。
八、循环结构
1. while循环
1 | while(关系表达式){ |
先判断关系表达式是否成立,如果成立则执行花括号内的语句块,然后依此循环,直到关系表达式不成立,while循环结束。
例:
1 | while(num > 5) { //条件判断 |
2. do-while循环
1 | do{ |
先执行花括号内的语句块,然后判断圆括号内的关系表达式是否成立,成立的话继续执行,否则循环终止。
例:
1 | do{ |
3. for 循环
1 | for(初始化表达式; 条件表达式; 条件改变表达式){ |
⚠ 注意: for语句头部的三个表达式都是可选项,都可以没有。
- 第一步:执行初始化表达式;
- 第二步:判断条件表达式是否成立,如果成立执行第三步,否则退出循环;
- 第三步:执行花括号内的for语句块;
- 第四步:执行条件改变表达式,然后返回第二步;
例:
1 | for(int i = 0 i < num; i++) { //条件初始化; 条件判断; 条件改变 |
4. 循环的嵌套
循环的内部可以再嵌套循环。如:
1 | for(int i = 0 i < num; i++) { |
5. 跳转语句
break: 跳出switch语句和循环语句;
continue:跳过循环体中的剩余语句,继续往下执行。
上机作业:
用循环语句打印九九乘法表,注意行列对齐。
九、Java的编程风格与注释
十、数组
数组是相同类型的数据按顺序组成的复合数据类型,通过数组名加下标来访问数组中的数据。Java数组的下标从0开始。
1. 声明数组
声明一维数组
有下列两种格式:
1 | 数组元素的类型 数组名[]; 例如:float boy[]; |
一维数组的逻辑结构如同数学中的向量,数组的每个元素对应向量的一个分量。
声明二维数组
有下列两种格式:
1 | 数组元素的类型 数组名[][]; 例如:int mytwo[][]; |
二维数组的逻辑结构如同数学中的矩阵或者方阵,第1维是行,第2维是列。数组的每个元素对应矩阵的一个元素。
声明更高维的数组
与此类似,比如:
1 | 三维数组:int mythree[][][]; 或int [][][]mythree; |
三维数组的逻辑结构如同三阶魔方这种立方体。因为现实世界是三维的,所以更高维数组是抽象的产物。
2. 创建数组
声明数组仅仅是声明了一个数组变量(确定了数组名和数组元素的类型),在使用数组前还必须先创建数组。创建数组的语法格式如下:
数组变量(数组名) = new 数组元素的类型[数组元素的个数];
例如:
1 | float boy[]; |
声明数组和创建数组可以一起完成:
1 | float boy[] = new float[4]; |
3. 数组元素的使用
- 一维数组通过下标访问数组的元素,如boy[0],boy[1]。
- 数组的下标从0开始,最大值是数组长度减1。若数组有7个元素,则下标到6为止,超过6将会引发异常。
- 二维数组也通过下标符访问元素,如a[0][1],a[1][2]。
- 下标也是从0开始,每一维的下标不超过该维的长度减1。
- 更高维的数组依此类推,比如访问三维数组的元素a[0][2][5]。
- 每一维的下标不超过该维的长度减1。
4. 数组的初始化
在声明数组的同时可以给数组元素一个初始值,如:
float boy[] = {21.3f, 23.89f, 2.0f, 23f, 778.98f};
这种情况在初始化的同时就创建了数组,不需要显式的用new操作符创建数组。
也可以在创建数组的同时初始化,如:
float boy[] = new float[] {21.3f, 23.89f, 2.0f, 23f, 778.98f };
此时方括号中不能有值,数组的长度由初值列表的长度决定。
二维数组的初始化,可以有三种形式。如下所示,声明并初始化一个2行3列的二维数组:
1 | int a[][] = {{1, 3, 5}, {5, 9, 10}}; //{1, 3, 5}是数组第1行,而{5, 9, 10}是数组第2行 |
在创建数组时,如果没有对数组初始化,则系统会执行默认的初始化,具体规则为:
- 数值类型的数组在创建后,赋默认值0/0L(整型和char型)或0.0D/0.0F(浮点型);
- 引用类型的数组在创建之后,赋默认值null;
- boolean类型的数组默认初值为false。
5. length的使用
对于一维数组,“数组名.length”的值就是数组中元素的个数。对于多维数组,“数组名.length”是它的第一维的长度。例如:
1 | float []a = new float[12]; |
a.length的值12,b.length的值是3。想得到数组b的另一维的长度,则b[0].length、b[1].length、b[2].length的值都是6
6. 数组的引用
数组名是变量,可以赋值。例如:
1 | int [] a = {1, 2, 3}, []b = {4,5}; |
执行赋值语句a = b后,数组变量a和b都指向同一个数组{4, 5},a[0] = 3是把4的值改为3。
7. for each循环
Java除了提供普通的for循环外,还提供for each循环,语法格式如下:
1 | for(变量声明:数组){ |
圆括号里的“变量声明”用于声明一个临时变量,其类型与数组元素的类型相同。for each循环自动的从前往后依次访问数组的每个元素。每访问一个元素,就把它的值存入临时变量中,这样就可以通过临时变量获取数组各个元素的值。例如:
1 | int a [10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
8. 不规则数组
二维数组可以看作由一维数组嵌套而成,将二维数组的每一行看作一个元素,则二维数组降为一维数组。普通的二维数组每一行的长度相同,而不规则二维数组的每一行长度可以不同。普通二维数组和不规则二维数组的却别如下图所示:
1 | * * * * * * * * * * * * |
创建二维不规则数组,可以有三种方式:
1 | // 1) |
上机作业:
打印九九乘法表,先创建一个9行的二维不规则数组,然后把乘法表的每一行存入该数组的对应行中,最后用循环语句读取数组的每一行并打印。提示:数组类型是String,数组的每个元素(下标从0开始):a[i][j] = (i+1) + "*" + (j+1) + "=" + (i+1)*(j+1) + "\t";