Java基本语法格式
编写Java程序代码必须先声明一个类,然后在类中编写实现需求的业务代码。类需要使用class关键字定义,在class前面可以有一些修饰符,其语法格式如下:
[修饰符] class 类名 {
程序代码
}
在编写Java程序代码时,需要特别注意几个关键点,具体如下:
1.Java中的程序可分为结构定义语句和功能执行语句。其中,结构定义语句用于声明一个类或方法,功能执行语句用于实现具体的功能。每条功能执行语句的结尾都必须用英文分号(;)结束。如下面的语句:
System.out.println("这是第一个Java程序!");
值得注意的是,在程序中不要将英文的分号(;)误写成中文的分号(;)。如果写成中文的分号,编译器会报告“Invalid Character(无效字符)”这样的错误信息。
2.Java语言是严格区分大小写的。例如,在程序中定义一个computer的同时,还可以定义一个Computer,computer和Computer是两个完全不同的符号,在使用时务必注意。
3.在编写Java代码时,为了便于阅读,通常会使用一种良好的格式进行排版,但这并不是必须的,也可以在两个单词或符号之间任意的换行,例如下面这段代码的编排方式也是可以的:
public class HelloWorld {public static void
main(String [
] args){System.out.println("这是第一个Java程序!");}}
虽然Java没有严格要求用什么样的格式来编排程序代码,但是,出于程序可读性和美观性的考虑,应该让自己编写的程序代码整齐美观、层次清晰,通常会使用下面这种形式:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("这是第一个Java程序!");
}
}
4.Java程序中一个连续的字符串不能直接分开在两行中书写,例如,下面这条语句在编译时将会出错:
System.out.println("这是第一个
Java程序!");
如果为了便于阅读,想将一个太长的字符串分开在两行中书写,可以先将这个字符串分成两个字符串,然后用加号(+)将这两个字符串拼接起来,在加号(+)处断行,上面的语句可以修改成如下形式:
System.out.println("这是第一个" +
"Java程序!");
小提示:
关于本节介绍Java代码基本格式中涉及到的类、修饰符、编译器等专业性的词汇,读者可以先不必深究其具体含义,在本节只需要重点掌握Java代码的基本格式即可,在后续学习Java的过程中,会对这些专业词汇进行详细讲解。
Java中的注释
在编写程序时,为了使代码易于阅读,通常会在实现功能的同时为代码添加一些注释。注释是对程序的某个功能或者某行代码的解释说明,它能够让开发者在后期阅读和使用代码时能更容易理解代码的作用。
注释只在Java源文件中有效,在编译程序时编译器会忽略这些注释信息,不会将其编译到class字节码文件中。
Java中的注释有三种类型,具体如下:
1.单行注释
单行注释通常用于对程序中的某一行代码进行解释,用符号“//”表示,“//”后面为被注释的内容,具体示例如下:
int c = 10; // 定义一个整型变量c
2.多行注释
多行注释顾名思义就是可以同时为多行内容进行统一注释,它以符号“/”开头,并以符号“/”结尾,具体示例如下:
/* 定义一个整形变量x
将5赋值给变量x */
int x;
x = 5;
3.文档注释
文档注释通常是对程序中某个类或类中的方法进行的系统性的解释说明,开发人员可以使用JDK提供的javadoc工具将文档注释提取出来生成一份API帮助文档。文档注释以符号“/”开头,并以符号“/”结尾,具体示例如下:
/**
* Title:HelloWorld类
* @author srx
* @version 1.0
*/
public class HelloWorld {
/**
* 这是一个main()方法入口
* @param args 参数名
*/
public static void main(String[] args){
System.out.println("这是第一个Java程序!");
}
}
脚下留心:注释嵌套
在Java中,有的注释可以嵌套使用,有的则不可以,下面列举两种具体的情况。
1.多行注释“/…/”中可以嵌套使用单行注释“//”,具体示例如下:
/* int c = 10; // 定义一个整型的c
int x = 5; */
2.多行注释“/…/”中不能嵌套使用多行注释“/…/”,具体示例如下:
/*
/*int c = 10;*/
int x=5;
*/
上面第二种情况的代码就无法通过编译,原因在于第一个 “/”会和第一个“/”进行配对,而第二个“*/”则找不到匹配,就会编译失败。
针对在使用嵌套注释时可能出现编译异常这一问题,通常在实际开发中都会避免对代码注释进行嵌套使用,只有在特殊情况下才会在多行注释中嵌套使用单行注释。
Java中的关键字
关键字(Keyword)是编程语言里事先定义好并赋予了特殊含义的单词,也称作保留字。和其他语言一样,Java中保留了许多关键字,例如,class、public等。JDK 8中有50个关键字,这些关键字都是小写的,具体如表1所示。
表1 Java关键字
abstract | assert | boolean | break | byte |
---|---|---|---|---|
case | catch | char | class | const |
continue | default | do | double | else |
enum | extends | final | finally | float |
for | goto | if | implements | import |
instanceof | int | interface | long | native |
new | package | private | protected | public |
return | strictfp | short | static | super |
switch | synchronized | this | throw | throws |
transient | try | void | volatile | while |
上面列举的关键字中,每个关键字都有特殊的作用,例如package关键字用于包的声明,import关键字用于引入包,class关键字用于类的声明。在本书后面的章节将逐步对使用到的关键字进行讲解,在此没有必要对所有关键字进行记忆,只需要了解即可。
Java中的标识符
在编程过程中,经常需要在程序中定义一些符号来标记一些名称,如包名、类名、方法名、参数名、变量名等,这些符号被称为标识符。标识符可以由任意顺序的大小写字母、数字、下划线()和美元符号($)组成,但标识符不能以数字开头,也不能是Java中的关键字。
下面的这些标识符都是合法的:
username
username123
user_name
_userName
$username
下面的这些标识符都是不合法的:
123username // 不能以数字开头
class // 不能是关键字
Hello World // 不能包含空格特殊字符
在Java程序中,定义的标识符必须严格遵守上面列出的规范,否则程序在编译时会报错。
为了增强代码的可读性和美观性,除了要求初学者要严格按照上面列出的规范来定义标识符外,还建议初学者在定义标识符时要遵循以下几点规范:
1.包名所有字母一律小写。例如:com.itheima.example01。
2.类名和接口名每个单词的首字母都要大写。例如:ArrayList、Iterator。
3.常量名所有字母都大写,单词之间用下划线连接。例如:DAY_OF_MONTH。
4.变量名和方法名的第一个单词首字母小写,从第二个单词开始每个单词首字母大写。例如:lineNumber、getLineNumber。
5.在程序中,应该尽量使用有意义的英文单词来定义标识符,使得程序便于阅读。例如:使用userName表示用户名,password表示密码。
变量的定义
在程序运行期间,随时可能产生一些临时数据,应用程序会将这些数据保存在一些内存单元中,每个内存单元都用一个标识符来标识。这些内存单元我们称之为变量,定义的标识符就是变量名,内存单元中存储的数据就是变量的值。
定义变量的语法非常简单,只需要指定变量的类型和变量名即可,其语法格式如下:
变量类型 变量名 [= 初始值];
上述定义变量的语法中,变量类型决定了变量的数据性质、范围、存储在内存中所占的字节数以及可以进行的合法操作,变量名必须是一个合法的标识符,而[]中的内容是可选项,即在定义变量的同时,可以对该变量进行初始化赋值。
接下来,通过具体的代码来学习变量的定义:
int x = 0,y;
y = x+3;
上述代码中,第一行代码的作用是定义了两个int类型的变量x和y,也就相当于分配了两块内存单元,在定义变量的同时为变量x分配了一个初始值0,而变量y没有分配初始值,变量x和y在内存中的状态如图1所示。
图1 x、y变量在内存中的状态
第二行代码的作用是为变量y赋值,在执行第二行代码时,程序首先从内存中取出变量x的值,然后与3相加后,最后将结果赋值给变量y,此时变量x和y在内存中的状态发生了变化,如图2所示。
图2 x、y变量在内存中的状态
变量的数据类型
va是一门强类型的编程语言,它对变量的数据类型有严格的限定。在定义变量时必须先声明变量的数据类型,在为变量赋值时必须赋予和变量同一种类型的值,否则程序在编译期间就会出现类型匹配错误的问题。
在Java中变量的数据类型分为两种:基本数据类型和引用数据类型。Java中所有的数据类型如图1所示。
图1 数据类型
其中,8种基本数据类型是Java语言内嵌的,在任何操作系统中都具有相同大小和属性,而引用数据类型是在Java程序中由编程人员自己定义的数据类型。本章此处重点介绍的是Java中的基本数据类型,引用数据类型会在以后的章节中进行详细地讲解。
1.整数类型变量
整数类型变量用来存储整数数值,即没有小数部分的值。在Java中,为了给不同大小范围内的整数合理地分配存储空间,整数类型分为4种不同的类型:字节型(byte)、短整型(short)、整型(int)和长整型(long),四种类型所占存储空间的大小以及取值范围如表1所示。
表1 整数类型
类型名 | 占用空间 | 取值范围 |
---|---|---|
byte | 8位(1个字节) | -27 ~ 27-1 |
short | 16位(2个字节) | -215 ~ 215-1 |
int | 32位(4个字节) | -231 ~ 231-1 |
long | 64位(8个字节) | -263 ~ 263-1 |
表1中,列出了4种整数类型变量所占的空间大小和取值范围。其中,占用空间指的是不同类型的变量分别占用的内存大小,如一个int类型的变量会占用4个字节大小的内存空间。取值范围是变量存储的值不能超出的范围,如一个byte类型的变量存储的值必须是-27 ~ 27-1之间的整数。
在为一个long类型的变量赋值时需要注意一点,所赋值的后面要加上一个字母“L”(或小写“l”),说明赋值为long类型。如果赋的值未超出int型的取值范围,则可以省略字母“L”(或小写“l”)。具体示例如下:
long num = 2200000000L; // 所赋的值超出了int型的取值范围,后面必须加上字母L
long num = 198L; // 所赋的值未超出int型的取值范围,后面可以加上字母L
long num = 198; // 所赋的值未超出int型的取值范围,后面可以省略字母L
2.浮点数类型变量
浮点数类型变量用来存储小数数值。在Java中,浮点数类型分为两种:单精度浮点数(float)和双精度浮点数(double)。double型所表示的浮点数比float型更精确,两种浮点数所占存储空间的大小以及取值范围如表2所示。
表2 浮点类型
类型名 | 占用空间 | 取值范围 |
---|---|---|
float | 32位(4个字节) | 1.4E-45 ~ 3.4E+38,-1.4E-45 ~ -3.4E+38 |
double | 64位(8个字节) | 4.9E-324 ~ 1.7E+308,-4.9E-324 ~ -1.7E+308 |
表2中,列出了两种浮点数类型变量所占的空间大小和取值范围,在取值范围中,E表示以10为底的指数,E后面的“+”号和“-”号代表正指数和负指数,例如1.4E-45表示1.4*10-45。
在Java中,一个小数会被默认为double类型的值,因此在为一个float类型的变量赋值时,所赋值的后面一定要加上字母“F”(或者小写“f”),而为double类型的变量赋值时,可以在所赋值的后面加上字符“D”(或小写“d”),也可以不加。具体示例如下:
float f = 123.4f; // 为一个float类型的变量赋值,后面必须加上字母f或F
double d1 = 199.3d; // 为一个double类型的变量赋值,后面可以加上字母d或D
double d2 = 100.1; // 为一个double类型的变量赋值,后面可以省略字母d或D
在程序中也可以为一个浮点数类型变量赋予一个整数数值,例如下面的写法也是可以的。
float f = 100; // 声明一个float类型的变量并赋整数值
double d = 100; // 声明一个double类型的变量并赋整数值
3.字符类型变量
字符类型变量用于存储一个单一字符,在Java中用char表示。Java中每个char类型的字符变量都会占用2个字节。在给char类型的变量赋值时,需要用一对英文半角格式的单引号(' ')把字符括起来,如'a',也可以将char类型的变量赋值为0~65535范围内的整数,计算机会自动将这些整数转化为所对应的字符,如数值97对应的字符为'a'。下面的两行代码可以实现同样的效果。
char c = 'a'; // 为一个char类型的变量赋值字符'a'
char ch = 97; // 为一个char类型的变量赋值整数97,相当于赋值字符'a'
4.布尔类型变量
布尔类型变量用来存储布尔值,在Java中用boolean表示,该类型的变量只有两个值,即true和false。具体示例如下:
boolean flag = false; // 声明一个boolean类型的变量,初始值为false
flag = true; // 改变flag变量的值为true
变量的类型转换
在程序中,当把一种数据类型的值赋给另一种数据类型的变量时,需要进行数据类型转换。根据转换方式的不同,数据类型转换可分为两种:自动类型转换和强制类型转换。
1.自动类型转换
自动类型转换也叫隐式类型转换,指的是两种数据类型在转换的过程中不需要显式地进行声明。当把一个类型取值范围小的数值直接赋给另一个取值范围大的数据类型变量时,系统就会进行自动类型转换,否则需要进行强制类型转换。
Java中的自动类型转换就好比将小瓶水倒入到大瓶的换装过程。我们将小瓶水倒入到大瓶中时,由于小瓶的容量比大瓶的容量小,所以倒入的水永远不可能溢出大瓶。同样,在Java中,将取值范围小的数据类型的变量值赋值给取值范围大的数据类型的变量时,程序也不会出现任何问题。
Java中支持的不同数据类型之间的自动转换如图1所示。
图1 自动类型转换图
从图1可以看出,Java中取值范围小的byte、short、char等类型数据都可以自动转换为取值范围大的数据类型(如int类型),并最终都可以自动转换为双精度浮点数类型。例如:
byte b = 3;
int x = b; // 程序把byte类型的变量b转换成了int类型,无需特殊声明
double y = x; // 将int类型的变量x转换成double类型,无需特殊声明
上面的语句中,首先将byte类型的变量b的值赋给了int类型的变量x,然后将int类型的变量x赋值给了double类型的y。由于int(double)类型的取值范围大于byte(int)类型的取值范围,编译器在赋值过程中不会造成数据丢失,所以编译器能够自动完成这种转换,在编译时不报告任何错误。
2.强制类型转换
强制类型转换也叫显式类型转换,指的是两种数据类型之间的转换需要进行显式地声明。当两种类型彼此不兼容,或者目标类型取值范围小于源类型时,自动类型转换无法进行,这时就需要进行强制类型转换。
Java中的强制类型转换就好比将大瓶水倒入到小瓶中一样,如果大瓶中的水的容量小于小瓶的大小,那么水是可以完全倒入的;如果大瓶水过多,其容量超过了小瓶的大小,那么多出来的水就会溢出,从而造成损失。同理,将取值范围大的数据类型的变量值赋值给取值范围小的数据类型的变量时,就可能造成数据的丢失,所以系统默认不支持这种行为,只能由开发者自己决定是否进行强制类型转换。
接下来先演示一个错误类型转换的例子,如文件1所示。
文件1 Example01.java
1 public class Example01 {
2 public static void main(String[] args) {
3 int num = 4;
4 byte b = num;
5 System.out.println(b);
6 }
7 }
程序编译时报错,结果如图2所示。
图2 运行结果
在进行Java代码编写时,Eclipse开发工具会自动对已编写的代码进行检测,如果发现问题,它会以红色波浪线和红叉的形式进行提醒。将鼠标悬停在出现红色波浪线错误的位置时,会出现一个悬浮框,悬浮框内将提示错误信息以及快速解决方案。
从图2可以看出,程序编译过程中出现了类型转换异常,提示“cannot convert from int to byte(无法将int类型转换为byte类型)”。出现这样错误的原因是将一个int型的值赋给byte类型的变量b时,int类型的取值范围大于byte类型的取值范围,这样的赋值可能会导致数值溢出,也就是说一个字节的变量无法存储四个字节的整数值。
在这种情况下,就需要进行强制类型转换,其语法格式如下:
目标类型 变量名 = (目标类型)值;
将文件1中的第4行代码修改为下面的代码:
byte b = (byte) num;
修改后保存源文件,Eclipse中的程序将不再报错。程序的运行结果如图3所示。
图3 运行结果
需要注意的是,在对变量进行强制类型转换时,会发生取值范围较大的数据类型向取值范围较小的数据类型的转换情况,如将一个int类型的数转为byte类型,这样做极容易造成数据精度的丢失。接下来通过一个案例来说明,如文件2所示。
文件2 Example02.java
1 public class Example02 {
2 public static void main(String[] args) {
3 byte a; // 定义byte类型的变量a
4 int b = 298; // 定义int类型的变量b,其表现形式是十六进制
5 a = (byte) b;
6 System.out.println("b=" + b);
7 System.out.println("a=" + a);
8 }
9 }
运行结果如图4所示。
>图4 运行结果
文件2中,第5行代码进行了强制类型转换,将一个int类型的变量b强制转换成byte类型,然后再将强转后的结果赋值给byte类型的变量a。从图2-7可以看出,变量b本身的值为“298”,然而在赋值给变量a后,变量a的值却为42,这说明在强制转换过程中丢失了精度。出现这种现象的原因是,变量b为int类型,在内存中占用4个字节,而byte类型的数据在内存中占用1个字节,当将变量b的类型强转为byte类型后,前面3个高位字节的数据已经丢失,这样数值就发生了改变。int类型转byte类型的过程如图5所示。
图5 int类型变量强制转换为byte类型
多学一招:表达式类型自动提升
所谓表达式是指由变量和运算符组成的一个算式。变量在表达式中进行运算时,也有可能发生自动类型转换,这就是表达式数据类型的自动提升,如byte、short和char类型的变量在运算期间类型会自动提升为int,然后再进行运算。下面通过一个具体的案例来演示,如文件3所示。*
文件3 Example03.java
1 public class Example03 {
2 public static void main(String[] args) {
3 byte b =3;
4 short s =4;
5 char c =5;
6 //将byte、short、char类型数值相加,再赋值给byte类型
7 byte b2 = b+s+c;
8 System.out.println("b2=" + b2);
9 }
10 }
程序编译报错,如图6所示。
图6 程序编译报错
图6中,出现了和图5相同的类型转换错误,这是因为在表达式b+s+c运算期间,byte类型的b、short类型的s和char类型的c都被自动提升为int型,表达式的运算结果也就成了int型,这时如果将该结果赋给byte型的变量就会报错,此时就需要进行强制类型转换。
要解决文件3中的错误,必须要将文件3中第5行的代码修改为:
byte b2 = (byte) (b+s+c);
再次编译后,程序不会报错,运行结果如图7所示。
图7 运行结果
变量的作用域
在前面介绍过变量需要先定义后使用,但这并不意味着在变量定义之后的语句中一定可以使用该变量。变量需要在它的作用范围内才可以被使用,这个作用范围称为变量的作用域。在程序中,变量一定会被定义在某一对大括号中,该大括号所包含的代码区域便是这个变量的作用域。接下来通过一个代码片段来分析变量的作用域,具体示例如下:
上面的代码中,有两层大括号。其中,外层大括号所标识的代码区域就是变量x的作用域,内层大括号所标识的代码区域就是变量y的作用域。
变量的作用域在编程中尤为重要,接下来通过一个案例进一步熟悉变量的作用域,如文件1所示。
文件1 Example04.java
1 public class Example04 {
2 public static void main(String[] args) {
3 int x = 12; // 定义了变量x
4 {
5 int y = 96; // 定义了变量y
6 System.out.println("x =" + x); // 访问变量x
7 System.out.println("y =" + y); // 访问变量y
8 }
9 y = x; // 访问变量x,为变量y赋值
10 System.out.println("x =" + x); // 访问变量x
11 }
12 }
程序编译报错,如图1所示。
图1 运行结果
从图1可以看出,程序编译过程中出现了“y cannot be resolved to avariable”即y不能被解析为一个变量的错误信息。出错的原因在于,在给变量y赋值时超出了它的作用域。将文件1中的第9行代码去掉,再次编译程序将不再报错,运行结果如图2所示。
图2 运行结果
文件1修改后的代码中,变量x、y都在各自的作用域中,因此都可以被访问到。
Java中的常量
常量就是在程序中固定不变的值,是不能改变的数据。例如数字1、字符'a'、浮点数3.2等。在Java中,常量包括整型常量、浮点数常量、布尔常量、字符常量等。接下来就对这些常量进行详细讲解。
1.整型常量
整型常量是整数类型的数据,有二进制、八进制、十进制和十六进制4种表示形式,具体说明如下:
● 二进制:由数字0和1 组成的数字序列。在JDK 7以后,允许使用二进制字面值来表示整数,此时二进制数值前面要以0b或0B开头,目的是为了和十进制进行区分,如:0b01101100、0B10110101。
● 八进制:以0开头,并且其后由0 ~7范围(包括0和7)内的整数组成的数字序列,如:0342。
● 十进制:由数字0~9范围(包括0和9)内的整数组成的数字序列。如:198。
● 十六进制:以0x或者0X开头,并且其后由0~9、A~F(包括0和9、A和F)组成的字符序列,如:0x25AF。
需要注意的是,在程序中为了标明不同的进制,数据都有特定的标识,八进制必须以0开头,如:0711、0123;十六进制必须以0x或0X开头,如:0xaf3、0Xff;整数以十进制表示时,第一位不能是0,0本身除外。例如十进制的127,用二进制表示为01111111,用八进制表示为0177,用十六进制表示为0x7F或者0X7F。
2.浮点数常量
浮点数常量就是在数学中用到的小数,分为float单精度浮点数和double双精度浮点数两种类型。其中,单精度浮点数后面以F或f结尾,而双精度浮点数则以D或d结尾。当然,在使用浮点数时也可以在结尾处不加任何后缀,此时Java虚拟机会将浮点数默认识别为double双精度浮点数。浮点数常量还可以通过指数形式来表示。具体示例如下:
2e3f 3.6d 0f 3.84d 5.022e+23f
3.字符常量
字符常量用于表示一个字符,一个字符常量要用一对英文半角格式的单引号(' ')引起来,它可以是英文字母、数字、标点符号以及由转义序列来表示的特殊字符。具体示例如下:
'a' '1' '&' '\r'
4.字符串常量
字符串常量用于表示一串连续的字符,一个字符串常量要用一对英文半角格式的双引号(" ")引起来,具体示例如下:
"HelloWorld" "123" "Welcome \n XXX" ""
一个字符串常量可以包含一个字符或多个字符,也可以不包含任何字符,即长度为零。
5.布尔常量
布尔常量即布尔类型的两个值true和false,该常量用于区分一个条件的真假。
6.null常量
null常量只有一个值null,表示对象的引用为空。关于null常量将会在第三章中详细讲解。
而Java中的常量,其实就是特殊的变量,也是固定不变的量,有且只能进行一次赋值。Java中定义常量的语法也非常简单,只需要在定义变量的语法基础上加上一个final关键字修饰即可,其语法格式如下:
final 常量类型 常量名 [= 初始值];
上述定义Java常量的语法格式与定义Java变量的语法格式基本相同,同样,定义Java常量需要声明定义常量类型、常量名,并可以对该常量进行初始化赋值,也可以后续赋值,唯一不同的是,定义Java常量时必须用final关键字修饰,来声明是一个不可改变的量。其具体使用示例如下:
final int a=0, b; // 定义一个int类型的常量a和b,并为常量a初始化赋值0
b= 1; // 后续为常量b赋值
在字符常量中,反斜杠(\)是一个特殊的字符,被称为转义符,它的作用是用来转义紧随其后的一个字符。转义后的字符通常用于表示一个不可见的字符或具有特殊含义的字符,例如“\n”表示换行。下面列出一些常见的转义字符:
● \r 表示回车,将光标定位到当前行的开头,不会跳到下一行。
● \n 表示换行,换到下一行的开头。
● \t 表示制表符,将光标移到下一个制表符的位置,就像在文档中用Tab键一样。
● \b 表示退格符号,就像键盘上的Backspace。
以下的字符都有特殊意义,无法直接表示,所以用斜杠加上另外一个字符来表示:
● ' 表示单引号字符,Java代码中单引号表示字符的开始和结束,如果直接写单引号字符('),程序会认为前两个是一对,会报错,因此需要使用转义(')。
● " 表示双引号字符,Java代码中双引号表示字符串的开始和结束,包含在字符串中的双引号需要转义,比如"he says,"thank you"."。
●\\ 表示反斜杠转义字符,由于在Java代码中的斜杠(\)是转义符,因此需要表示字面意义上的\,就需要使用双斜杠\。
通过前面的介绍可以知道,整型常量可以分别用二进制、八进制、十进制和十六进制表示,不同的进制并不影响数据本身的大小,同一个整型常量可以在不同进制之间转换,具体转换方式如下:
1.十进制和二进制之间的转换
(1)十进制转二进制*
十进制转换成二进制就是一个除以2取余数的过程。把要转换的数,除以2,得到商和余数,将商继续除以2,直到商为0。最后将所有余数倒序排列,得到数就是转换结果。
以十进制的6转换为二进制为例进行说明,如图1所示。
图1 十进制转二进制
三次除以2计算得到余数依次是:0、1、1,将所有余数倒序排列是:110。所以十进制的6转换成二进制,结果是110。
(2)二进制转十进制
二进制转换成十进制要从右到左用二进制位上的每个数去乘以2的相应次方,例如,将最右边第一位的数乘以2的0次方,第二位的数乘以2的1次方,第n位的数乘以2的n-1次方,然后把所有乘得的结果相加,得到的结果就是转换后的十进制。
以二进制数01100100转换为十进制为例进行说明,转换方式如下:
0 * 20 + 0 * 21 + 1 * 22 + 0 * 23 + 0 * 24 + 1 * 25 + 1 * 26+ 0 * 27 = 100
由于0乘以多少都是0,所以上述表达式也可以简写为:
1 * 22 + 1 * 25 + 1 * 26 = 100
得到的结果100就是二进制数01100100转换后的十进制表现形式。
2.二进制和八进制、十六进制之间的转换
编程中之所以要用八进制和十六进制,是因为它们与二进制之间的互相转换很方便,而且它们比一串长的二进制数方便书写和记忆。接下来,就为大家详细介绍如何将二进制转换为八进制和十六进制。*
(1)二进制转八进制
二进制转八进制时,首先需要将二进制数自右向左每三位分成一段(不足三位的,在左侧补0),然后将二进制数每段的三位数转为八进制的一位,转换过程中数值的对应关系如表1所示。
表1 二进制和八进制数值对应表
二进制 | 八进制 |
---|---|
000 | 0 |
001 | 1 |
010 | 2 |
011 | 3 |
100 | 4 |
101 | 5 |
110 | 6 |
111 | 7 |
了解了二进制转八进制的规则,接下来详细讲解如何将一个二进制数00101010转为八进制,具体步骤如下:
1) 每三位分成一段,结果为:000 101 010
2)将每段的数值分别查表替换,结果如下:
000→ 0
101→ 5
010→ 2
3) 将替换的结果进行组合,转换的结果为:052(注意八进制必须以0开头)
(2)二进制转十六进制
二进制转十六进制,与转八进制类似,不同的是要将二进制数每四位分成一段(不足四位的,在左侧补0),然后通过查表将二进制数每段的四位数转换为八进制的每一位即可。二进制转十六进制过程中数值的对应关系如表2所示。
表2 二进制和十六进制数值对应表
二进制 | 十六进制 | 二进制 | 十六进制 | |
---|---|---|---|---|
0000 | 0 | 1000 | 8 | |
0001 | 1 | 1001 | 9 | |
0010 | 2 | 1010 | A | |
0011 | 3 | 1011 | B | |
0100 | 4 | 1100 | C | |
0101 | 5 | 1101 | D | |
0110 | 6 | 1110 | E | |
0111 | 7 | 1111 | F |
了解了二进制转十六进制的规则,接下来通过一个例子来学习一下。假设要将一个二进制数10100101转为十六进制,具体步骤如下:
1) 每四位分成一段,结果为:1010 0101
2) 将每段的数值分别查表替换,结果如下:
1010 → A
0101 → 5
3) 将替换的结果进行组合,转换的结果为:0xA5或0XA5(注意十六进制必须以0x或者0X开头)。
算术运算符
在数学运算中最常见的就是加减乘除,被称作四则运算。Java中的算术运算符就是用来处理四则运算的符号,这是最简单、最常用的运算符号。接下来通过一张表来展示Java中的算术运算符及其用法,如表1所示。
表1 算术运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
+ | 正号 | +3 | 3 |
- | 负号 | b=4;-b; | -4 |
+ | 加 | 5+5 | 10 |
- | 减 | 6-4 | 2 |
* | 乘 | 3*4 | 12 |
/ | 除(即算数中整除的结果) | 7/5 | 1 |
% | 取模(即算术中的求余数) | 7%5 | 2 |
++ | 自增(前) | a=2;b=++a; | a=3;b=3; |
++ | 自增(后) | a=2;b=a++; | a=3;b=2; |
-- | 自减(前) | a=2;b=--a; | a=1;b=1; |
-- | 自减(后) | a=2;b=a--; | a=1;b=2; |
算术运算符看上去都比较简单,也很容易理解,但在实际使用时还有很多需要注意的问题,接下来就针对其中比较重要的几点进行详细地讲解,具体如下:
1.在进行自增(++)和自减(--)的运算时,如果运算符(++或--)放在操作数的前面则是先进行自增或自减运算,再进行其他运算。反之,如果运算符放在操作数的后面则是先进行其他运算再进行自增或自减运算。
请仔细阅读下面的代码块,思考运行的结果。
int a = 1;
int b = 2;
int c = a + b++;
System.out.print("b=" + b);
System.out.print("c=" + c);
上面的代码块运行结果为:b=3、c=3,具体分析如下:
在上述代码中,定义了三个int类型的变量a、b、c,其中a=1、b=2。当进行“a+b++”运算时,由于运算符++写在了变量b的后面,会先运算再自增,因此变量b在参与加法运算时其值仍然为2;c的值应为3;变量b在参与运算之后会进行自增,因此b的最终值为3。
2.在进行除法运算时,当除数和被除数都为整数时,得到的结果也是一个整数;如果除法运算有小数参与,得到的结果会是一个小数。例如,2510/1000属于整数之间相除,会忽略小数部分,得到的结果是2,而2.5/10的结果为0.25。
请思考一下下面表达式的结果是多少。
3500/1000*1000
上面表达式的运行结果为3000。由于表达式的执行顺序是从左到右,所以先执行除法运算3500/1000,得到结果为3,再乘以1000,得到的结果自然就是3000了。
3.在进行取模(%)运算时,运算结果的正负取决于被模数(%左边的数)的符号,与模数(%右边的数)的符号无关。例如:(-5)%3的结果为-2,而5%(-3)的结果为2。
赋值运算符
赋值运算符的作用就是将常量、变量或表达式的值赋给某一个变量。接下来通过一张表来展示Java中的赋值运算符及其用法,如表1所示。
表1 赋值运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
= | 赋值 | a=3;b=2; | a=3;b=2; |
+= | 加等于 | a=3;b=2;a+=b; | a=5;b=2; |
-= | 减等于 | a=3;b=2;a-=b; | a=1;b=2; |
*= | 乘等于 | a=3;b=2;a*=b; | a=6;b=2; |
/= | 除等于 | a=3;b=2;a/=b; | a=1;b=2; |
%= | 模等于 | a=3;b=2;a%=b; | a=1;b=2; |
在赋值过程中,运算顺序从右往左,将右边表达式的结果赋值给左边的变量。在赋值运算符的使用中,需要注意以下几个问题。
1.在Java中可以通过一条赋值语句对多个变量进行赋值,具体示例如下:
int x, y, z;
x = y = z = 5; // 为三个变量同时赋值
在上述代码中,一条赋值语句将变量x,y,z的值同时赋值为5。需要特别注意的是,下面的这种写法在Java中是不可以的:
int x = y = z = 5; // 这样写是错误的,因为没有预先声明变量y、z的数据类型
2.在表1中,除了“=”,其他的都是特殊的赋值运算符,以“+=”为例,x += 3就相当于x = x + 3,首先会进行加法运算x+3,再将运算结果赋值给变量x。其余的-=、*=、/=、%=赋值运算符都可依此类推。
在前面小节中介绍过,在为变量赋值时,当两种类型彼此不兼容,或者目标类型取值范围小于源类型时,需要进行强制类型转换。例如,将一个int类型的值赋给一个short类型的变量,需要显式地进行强制类型转换。然而在使用+=、-=、=、/=、%= 赋值运算符时,强制类型转换会自动完成,程序不需要做任何显式地声明,如文件1所示。
文件1 Example05.java
1 public class Example05 {
2 public static void main(String[] args) {
3 short s = 3;
4 int i = 5;
5 s += i;
6 System.out.println("s = " + s);
7 }
8 }
运行结果如图1所示。
图1 运行结果
文件1中,第5行代码为赋值运算,虽然变量s和i相加的运算结果为int类型,但通过运算符+=将结果赋值给了short类型的变量s时,Java虚拟机会自动完成强制类型转换,从而得到s的值为8。
比较运算符
比较运算符用于对两个常量或变量的大小进行比较,其结果是一个布尔值,即true或false。接下来通过一张表来展示Java中的比较运算符及其用法,如表1所示。
表1 比较运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
== | 相等于 | 4 == 3 | false |
!= | 不等于 | 4 != 3 | true |
< | 小于 | 4 < 3 | false |
> | 大于 | 4 > 3 | true |
<= | 小于等于 | 4 <= 3 | false |
>= | 大于等于 | 4 >= 3 | true |
在使用比较运算符时,需要注意一个问题,不能将比较运算符“==”误写成赋值运算符“=”。
逻辑运算符
逻辑运算符用于对布尔类型的值或表达式进行操作,其结果仍是一个布尔值。接下来通过一张表来展示Java中的逻辑运算符及其用法,如表1所示。
表1 逻辑运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
& | 与 | true & true | true |
& | 与 | true & false | false |
& | 与 | false & false | false |
& | 与 | false &true | false |
| | 或 | true | true | true |
| | 或 | true | false | true |
| | 或 | false| false | false |
| | 或 | false| true | true |
^ | 异或 | true ^ true | false |
^ | 异或 | true ^ false | true |
^ | 异或 | false ^ false | false |
^ | 异或 | false ^ true | true |
! | 非 | !true | false |
! | 非 | !false | true |
&& | 短路与 | true && true | true |
&& | 短路与 | true && false | false |
&& | 短路与 | false && false | false |
&& | 短路与 | false && true | false |
|| | 短路或 | true || true | true |
|| | 短路或 | true || false | true |
|| | 短路或 | false|| false | false |
|| | 短路或 | false|| true | true |
在使用逻辑运算符的过程中,需要注意以下几个细节:
1.逻辑运算符可以针对结果为布尔值的表达式进行逻辑运算。如:5 > 3 && 1 != 0的结果为true。
2.运算符“&”和“&&”都表示与操作,当且仅当运算符两边的操作数都为true时,其结果才为true,否则结果为false。当运算符“&”和“&&”的右边为表达式时,两者在使用上还有一定的区别。在使用“&”进行运算时,不论左边为true或者false,右边的表达式都会进行运算。如果使用“&&”进行运算,当左边为false时,右边的表达式不会进行运算,因此“&&”被称作短路与。
接下来通过一个案例来深入了解一下两者的区别,如文件1所示。
文件1 Example06.java
1 public class Example06 {
2 public static void main(String[] args) {
3 int x = 0; // 定义变量x,初始值为0
4 int y = 0; // 定义变量y,初始值为0
5 int z = 0; // 定义变量z,初始值为0
6 boolean a, b; // 定义boolean变量a和b
7 a = x > 0 & y++ > 1; // 逻辑运算符&对表达式进行运算,然后将结果赋值给a
8 System.out.println("a = " + a);
9 System.out.println("y = " + y);
10 b = x > 0 && z++ > 1; // 逻辑运算符&&对表达式进行运算,然后将结果赋值给b
11 System.out.println("b = " + b);
12 System.out.println("z = " + z);
13 }
14 }
运行结果如图1所示。
图1 运行结果
在文件 1中,定义了三个整型变量x、y、z,初始值都为0,同时定义了两个布尔类型的变量a和b。文件中的第7行代码使用“&”运算符对两个表达式进行运算,左边表达式x>0的结果为false,这时无论右边表达式y++>0的比较结果是什么,整个表达式x > 0 & y++ > 1的结果都会是false。由于使用的是单个的运算符“&”,运算符两边的表达式都会进行运算,因此变量y会进行自增,整个表达式运算结束时y的值为1。文件中的第10行代码是短路与运算,运算结果和第7行代码一样为false,区别在于使用了短路与“&&”运算符,当左边为false时,右边的表达式不再进行运算,因此变量z的值仍为0。
3.运算符“|”和“||”都表示或操作,当运算符任何一边的操作数或表达式为true时,其结果为true,当两边的值都为false时,其结果才为false。同与操作类似,“||”表示短路或,当运算符“||”的左边为true时,右边的表达式不会进行运算,具体示例如下:
int x = 0;
int y = 0;
boolean b = x==0 || y++>0
上面的代码块执行完毕后,b的值为true,y的值仍为0。出现这样结果的原因是,运算符“||”的左边x==0结果为true,那么右边表达式将不会进行运算,y的值不发生任何变化。
4.运算符“^”表示异或操作,当运算符两边的布尔值相同时(都为true或都为false),其结果为false。当两边布尔值不相同时,其结果为true。
位运算符
位运算符是针对二进制数的每一位进行运算的符号,它是专门针对数字0和1进行操作的。接下来通过一张表来展示Java中的位运算符及其用法,如表1所示。
表1 位运算符
运算符 | 运算 | 范例 | 结果 |
---|---|---|---|
& | 按位与 | 0 & 0 | 0 |
& | 按位与 | 0 & 1 | 0 |
& | 按位与 | 1 & 1 | 1 |
& | 按位与 | 1 & 0 | 0 |
| | 按位或 | 0 | 0 | 0 |
| | 按位或 | 0 | 1 | 1 |
| | 按位或 | 1 | 1 | 1 |
| | 按位或 | 1 | 0 | 1 |
~ | 取反 | ~0 | 1 |
~ | 取反 | ~1 | 0 |
^ | 按位异或 | 0 ^ 0 | 0 |
^ | 按位异或 | 0 ^ 1 | 1 |
^ | 按位异或 | 1 ^ 1 | 0 |
^ | 按位异或 | 1 ^ 0 | 1 |
<< | 左移 | 00000010<<2 | 00001000 |
<< | 左移 | 10010011<<2 | 01001100 |
>> | 右移 | 01100010>>2 | 00011000 |
>> | 右移 | 11100010>>2 | 11111000 |
>>> | 无符号右移 | 01100010>>>2 | 00011000 |
>>> | 无符号右移 | 11100010>>>2 | 00111000 |
从表1可以看出,Java中的位运算符本质都是针对二进制数0和1进行运算的,所以,在使用位运算符时,都会先将操作数转换成二进制数的形式进行位运算,然后将得到的结果再转换成想要的进制数。
接下来通过一些具体示例对表2-10中描述的位运算符进行详细讲解。为了方面描述,下面的运算都是针对一个byte类型的数,也就是一个字节大小的数的运算,具体如下:
1.位运算符“&”是将参与运算的两个操作数以二进制数的形式进行“按位与”运算,如果相同位上的两个二进制位都为1,则该位的运算结果为1,否则为0。
例如将6和 11进行按位与运算,一个byte类型的数字6对应的二进制数为00000110,数字11对应的二进制数为00001011,具体演算过程如下所示:
00000110
&
00001011
—————————
00000010
运算结果为00000010,对应十进制数值2。
2.位运算符“|”是将参与运算的两个操作数以二进制数的形式进行“按位或”运算,如果相同位上的两个二进制位有一个值为1,则该位的运行结果为1,否则为0。
例如将6与11进行或运算,具体演算过程如下:
00000110
|
00001011
—————————
00001111
运算结果为00001111,对应十进制数值15。
3.位运算符“~”只针对一个操作数进行操作,它会对二进制形式的操作数每位进行取反操作。如果二进制位是0,则取反值为1;如果是1,则取反值为0。
例如将6进行取反运算,具体演算过程如下:
~ 00000110
—————————
11111001
运算结果为11111001,对应十进制数值-7。
4.位运算符“^”是将参与运算的两个操作数以二进制数的形式进行“异或”运算,如果相同位上的两个二进制数值相同,则值为0,否则为1。
例如将6与11进行异或运算,具体演算过程如下:
00000110
^
00001011
—————————
00001101
运算结果为00001101,对应十进制数值13。
5.位运算符“<<”就是将操作数二进制形式的所有二进制位向左移动指定位数。运算时,左边移走的部分舍去,右边的空位补0。
例如一个byte类型的数字11用二进制表示为00001011,将它左移一位,具体演算过程如下:
00001011 <<1
—————————
00010110
运算结果为00010110,对应十进制数值22。
6.位运算符“>>“就是将操作数二进制形式的所有进制位向右移动指定位数。运算时,左边的空位根据原数的符号位补0或者1(原来是负数就全部补1,是正数就全部补0),右边移走的部分舍去。
例如一个byte类型的数字11用二进制表示为00001011,将它右移一位,具体演算过程如下。
00001011 >>1
—————————
00000101
运算结果为00000101,对应十进制数值5。
7.位运算符“>>>”属于无符号右移,就是将操作数二进制形式的所有二进制位不考虑正负数向右移动指定位数。运算时,左边的空位全部补0(不考虑原数正负),右边移走的部分舍去。
例如一个byte类型的数字11用二进制表示为00001011,将它无符号右移一位,具体演算过程如下:
00001011 >>>1
—————————
00000101
运算结果为00000101,对应十进制数值5。
条件运算符
条件运算符,也称作三元运算符(或三目运算符),由符号“? :”组合构成,其语法格式如下:
(boolean_expr) ? true_statement : false_statement;
上述条件运算符的运算规则是:先对布尔类型的表达式boolean_expr求值,如果结果为true,就执行冒号“:”前面的表达式true_statement,否者执行后面的表达式false_statement。
条件运算符的基本使用示例如下:
int store = 5;
System.out.println(store <=0 ? "没有库存啦!" : "库存量为:"+store);
// 输出结果为 “库存量为:5”
条件运算符“? :”基本等价于选择结构语句中的“if...else”条件语句,属于是精简写法。关于 “if...else”条件语句,将会在后面小节详细讲解,这里不必深究其具体用法。
运算符的优先级
在对一些比较复杂的表达式进行运算时,要明确表达式中所有运算符参与运算的先后顺序,通常把这种顺序称作运算符的优先级。接下来通过一张表来展示Java中运算符的优先级,其中数字越小表示优先级越高,如表1所示。
表1 运算符优先级
优先级 | 运算符 |
---|---|
1 | . [] () |
2 | ++ -- ~ ! |
3 | * / % |
4 | + - |
5 | << >> >>> |
6 | < > <= >= |
7 | == != |
8 | & |
9 | ^ |
10 | | |
11 | && |
12 | || |
13 | ?: |
14 | = *= /= %= += -= <<= >>= >>>= &= ^= |= |
根据表1所示的运算符优先级,分析下面代码的运行结果。
int a =2;
int b = a + 3*a;
System.out.println(b);
运行结果为8,由于运算符“”的优先级高于运算符“+”和“=”,因此先运算3a,得到的结果是6,再将6与a相加,最后通过运算符“=”赋值给变量b,得到最后的结果8。
int a =2;
int b = (a+3) * a;
System.out.println(b);
运行结果为10,由于运算符“()”的优先级最高,因此先运算括号内的a+3,得到的结果是5,再将5与a相乘,最后通过运算符“=”赋值给变量b,得到最后的结果10。
其实没有必要去刻意记忆运算符的优先级。编写程序时,尽量使用括号“()”来实现想要的运算顺序,以免产生歧义。
if条件语句
if条件语句分为三种语法格式,每一种格式都有其自身的特点,下面分别进行介绍。
1.if语句
if语句是指如果满足某种条件,就进行某种处理。例如,如果一个人的年龄不满18周岁,那么就可以判定这个人是未成年人。这句话可以通过下面的一段伪代码来描述:
如果年龄不满18周岁
此人是未成年人
在上面的伪代码中,“如果”相当于Java中的关键字if,“年龄不满18周岁”是判断条件,需要用()括起来,“此人是未成年人!”是执行语句,需要放在{}中。在if语句中的伪代码如下:
if (年龄不满18岁) {
此人是未成年人!
}
上面的例子就描述了if语句的用法,在Java中,if语句的具体语法格式如下:
if (判断条件){
执行语句
}
上述语法格式中,判断条件是一个布尔值,当判断条件为true时,就会执行{}中的执行语句。if语句的执行流程如图1所示。
图1 if语句流程图
接下来就使用if条件语句对上面的情景描述进行案例演示,如文件1所示。
文件1 Example07.java
1 public class Example07 {
2 public static void main(String[] args) {
3 int age = 17; //假设年龄为17岁
4 if (age < 18) { // 判断年龄是否满18岁
5 System.out.println("此人是未成年人!");
6 }
7 }
8 }
运行结果如图2所示。
图2 运行结果
文件1中,定义了一个int类型的变量age(年龄),并假设属性值为17。在if语句的判断条件中判断age的值是否小于18,很明显条件成立,将会选择执行if后面{}中的执行语句,输出“此人是未成年人!”的信息。
2.if…else语句
if…else语句是指如果满足某种条件,就进行某种处理,否则就进行另一种处理。例如,要判断一个正整数的奇偶,如果该数字能被2整除则是一个偶数,否则该数字就是一个奇数。
if…else语句的语法格式如下:
if (判断条件){
执行语句1
...
}else{
执行语句2
...
}
上述语法格式中,判断条件是一个布尔值。当判断条件为true时,会执行if后面{}中的执行语句1,否则会执行else后面{}中的执行语句2。if…else语句的执行流程如图3所示。
图3 if…else语句流程图
接下来使用if…else语句来实现一个判断奇偶数的程序,如文件2所示。
文件2 Example08.java
1 public class Example08 {
2 public static void main(String[] args) {
3 int num = 19;
4 if (num % 2 == 0) {
5 // 判断条件成立,num能被2整除
6 System.out.println(num+"是一个偶数");
7 } else {
8 // 判断条件不成立,num不能被2整除
9 System.out.println(num+"是一个奇数");
10 }
11 }
12 }
运行结果如图4所示。
图4 运行结果
文件2中,变量num的值为19,19%2的结果为1,不等于0,判断条件不成立,因此会执行else后面{}中的语句,输出“19是一个奇数”的信息。
小提示:
上述案例中的if…else语句还可以通过前面所学习的三元运算来替换,替换后的输出语句如下:
System.out.println(num % 2 == 0 ? num+"是一个偶数" :num+"是一个奇数" );
3.if…else if…else语句
if…else if…else语句用于对多个条件进行分支判断,从而进行多种不同的处理。例如,对一个学生的考试成绩进行等级划分:如果分数大于80分,那么等级为优;否则,如果分数大于70分,那么等级为良;否则,如果分数大于60分,那么等级为中;否则,等级为差。
if…else if…else语句具体语法格式如下。
if (判断条件1) {
执行语句1
} else if (判断条件2) {
执行语句2
}
...
else if (判断条件n) {
执行语句n
} else {
执行语句n+1
}
上述语法格式中,判断条件都是布尔值。当判断条件1为true时,if后面{}中的执行语句1会执行;当判断条件1为false时,会继续执行判断条件2,如果为true,则执行语句2,以此类推。如果所有的判断条件都为false,则意味着所有条件均未满足,就会执行else后面{}中的执行语句n+1。
if…else if…else语句的执行流程如图5所示。
图5 if…else if…else语句的流程图
接下来使用if…else if…else语句来实现对学生考试成绩进行等级划分的程序,如文件3所示。
文件3 Example09.java
1 public class Example09 {
2 public static void main(String[] args) {
3 int grade = 75; // 定义学生成绩
4 if (grade > 80) {
5 // 满足条件 grade > 80
6 System.out.println("该成绩的等级为优");
7 } else if (grade > 70) {
8 // 不满足条件 grade > 80 ,但满足条件 grade > 70
9 System.out.println("该成绩的等级为良");
10 } else if (grade > 60) {
11 // 不满足条件 grade > 70 ,但满足条件 grade > 60
12 System.out.println("该成绩的等级为中");
13 } else {
14 // 不满足条件 grade > 60
15 System.out.println("该成绩的等级为差");
16 }
17 }
18 }
运行结果如图6所示。
图6 运行结果
文件3中,定义了学生成绩grade为75,由于它不满足第一个判断条件grade>80,因此会继续向下执行第二个判断条件;由于满足了grade>70的条件,因此会输出“该成绩的等级为良”的信息。
switch条件语句
switch 条件语句也是一种很常用的选择结构语句,它由一个switch控制表达式和多个case关键字组成。与if条件语句不同的是,switch 条件语句的控制表达式结果类型只能是byte、short、char、int、enum枚举以及String类型,而不能是boolean类型。switch条件语句的基本语法格式如下:
switch (控制表达式){
case 目标值1:
执行语句1
break;
case 目标值2:
执行语句2
break;
...
case 目标值n:
执行语句n
break;
default:
执行语句n+1
break;
}
在上面的语法格式中,switch语句将控制表达式的值与每个case中的目标值进行匹配,如果找到了匹配的值,会执行对应case后的执行语句,并通过break关键字跳出该switch语句;如果搜索完所有case中的目标值仍没找到任何匹配的值,就会执行default后的语句。switch语句中的break关键字将在后面的小节中做具体介绍,此处,读者只需要知道break的作用是跳出switch语句即可。
接下来通过一个根据月份来判断当前所属季节的案例来演示switch条件语句的使用,如文件1所示。
文件1 Example10.java
1 public class Example10{
2 public static void main(String[] args) {
3 int month = 5; //假设当前为5月份
4 switch (month) {
5 case 12:
6 case 1:
7 case 2:
8 System.out.println("当前为时间为冬季");
9 break;
10 case 3:
11 case 4:
12 case 5:
13 System.out.println("当前时间为春季");
14 break;
15 case 6:
16 case 7:
17 case 8:
18 System.out.println("当前时间为夏季");
19 break;
20 case 9:
21 case 10:
22 case 11:
23 System.out.println("当前时间为冬季");
24 break;
25 default:
26 System.out.println("输入的月份不正确!!!");
27 break;
28 }
29 }
30 }
运行结果如图1所示。
图1 运行结果
从文件1可以看出,当有多个case目标值会执行同样的执行语句时,可以将这多个case标签并列书写,并只编写一次执行语句即可。
文件1中,由于变量 month的值为5(即当前月份为5月),整个switch语句判断的结果满足第12行代码的条件,因此输出“当前时间为春季”。文件中的default语句用于处理和前面的case目标值都不匹配的情况,如果将第3行代码替换为“int month= 13;”,那么再次运行程序后,输出的结果如图2所示。
图2 运行结果
注意:
在使用switch条件语句时,需要特别注意两点:第一,switch条件语句后{}中的多个case以及default标签顺序可以随机,并不影响程序的执行结果;第二,在case和default标签中的执行语句后务必都加上break关键字,用于执行完语句后跳出当前的switch条件语句,否则,程序会接着执行后续的执行语句,直到遇到break关键字或switch语句末尾为止。
while循环语句
while循环语句和2.4节讲到的选择结构语句有些相似,都是根据条件判断来决定是否执行大括号{}内的执行语句。区别在于,while语句会循环判断循环条件是否成立,只要条件成立,{}内的执行语句就会执行,直到循环条件不成立,while循环才结束。
while循环语句的语法格式如下:
while(循环条件){
执行语句
...
}
在上面的语法结构中,{}中的执行语句被称作循环体,循环体是否执行取决于循环条件是否成立。当循环条件为true时,循环体就会执行,循环体执行完毕时,程序会继续判断循环条件是否成立,如条件仍为true则会继续执行循环体,直到循环条件为false时,整个循环过程才会结束。
while循环的执行流程如图1所示。
图1 while循环语句的流程图
接下来使用while循环语句通过一个案例来实现打印1~4之间的自然数,如文件1所示。
文件1 Example11.java
1 public class Example11 {
2 public static void main(String[] args) {
3 int x = 1; // 定义变量x,初始值为1
4 while (x <= 4) { // 判断循环条件是否成立
5 System.out.println("x = " + x); // 条件成立,打印x的值
6 x++; // 控制变量x,进行自增
7 }
8 }
9 }
运行结果如图2所示。
图2 运行结果
文件1中,x初始值为1,在满足循环条件x <= 4的情况下,循环体会重复执行,打印x的值并让x进行自增,因此打印结果中x的值分别为1、2、3、4。值得注意的是,文件中第6行代码x++用于在每次循环时改变变量x的值,从而达到最终改变循环条件的目的,如果没有这行代码,整个循环会进入无限循环的状态,永远不会结束,这也就是常说的死循环。
do...while循环语句
do…while循环语句也被称为后测试循环语句,它和while循环语句的功能类似,其语法格式如下:
do {
执行语句
...
} while(循环条件);
从上面的语法结构可以看出,do…while循环语句与while循环语句的主要区别是:do…while循环语句是先执行循环体,再进行循环条件判断,这与while循环语句的语法结构相反,并且无论循环条件是否成立,do…while循环语句中的循环体都会无条件的先执行一次,然后再根据循环条件来决定是否继续执行。
do…while循环语句的执行流程如图1所示。
图1 do…while循环语句的执行流程
接下来使用do…while循环语句求1~4自然数之和,如文件1所示。
文件1 Example12.java
1 public class Example12 {
2 public static void main(String[] args) {
3 int x = 1; // 定义变量x,初始值为1
4 do {
5 System.out.println("x = " + x); // 打印x的值
6 x++; // 控制变量x,进行自增
7 } while (x <= 4); // 判断循环条件是否成立
8 }
9 }
运行结果如图2所示。
图2 运行结果
从图2可以看出,使用while和do...while循环语句运行结果一致,这就说明do…while循环语句和while循环语句能实现同样的功能。
在某些特殊条件下,这两种循环语句还是有差别的。如果循环条件在循环语句开始时就不成立,那么while循环的循环体一次都不会执行,而do…while循环的循环体还是会执行一次。如果将while和do...while循环条件x<=4都改为x < 1,那么do...while循环会打印出“x=1”的结果,而while循环什么也不会打印,即不执行循环。
for循环语句
for循环语句是最常用的循环语句,一般用在循环次数已知的情况下,通常情况下可以代替while循环。for循环语句的基本语法格式如下:
for(初始化表达式; 循环条件; 操作表达式){
执行语句
...
}
在上面的语法结构中,for关键字后面()中包括了三部分内容:初始化表达式、循环条件和操作表达式,它们之间用英文“;”分隔,{}中的执行语句为循环体。
下面分别用①表示初始化表达式、②表示循环条件、③表示操作表达式、④表示执行语句(循环体),通过序号来具体分析for循环的执行流程。具体如下:
for(① ; ② ; ③){
④
}
第一步,执行①
第二步,执行②,如果循环条件结果为true,则执行第三步,如果结果为false,则执行第五步
第三步,执行④
第四步,执行③,然后重复执行第二步
第五步,退出循环
在for循环语句中,程序会首先执行一次初始化表达式,然后进行循环条件判断,如果循环条件结果为true,就会执行循环体,最后再执行操作表达式来控制循环条件,这样就完成了一轮for循环,直到循环条件结果为false时,才会跳出整个for循环。
for循环语句的执行流程如图1所示。
图1 for循环语句的执行流程
接下来通过一个对自然数1~4进行求和的案例来演示for循环语句的使用,如文件1所示。
文件1 Example13.java
1 public class Example13 {
2 public static void main(String[] args) {
3 int sum = 0; // 定义变量sum,用于记住累加的和
4 for (int i = 1; i <= 4; i++) { // i的值会在1~4之间变化
5 sum += i; // 实现sum与i的累加
6 }
7 System.out.println("sum = " + sum); // 打印累加的和
8 }
9 }
运行结果如图2所示。
图2 运行结果
文件1中,变量i的初始值为1,在判断条件i<=4为true的情况下,会执行循环体sum+=i,执行完毕后,会执行操作表达式i++,i的值变为2,然后继续进行条件判断,开始下一轮循环,直到i=5时,条件i<=4为false,结束循环,会执行for循环后面的输出语句代码,打印“sum=10”。
为了让初学者能熟悉整个for循环的执行过程,现将文件1运行期间每次循环后变量sum和i的值通过一张表罗列出来,如表1所示。
表1 sum和i循环中的值
循环次数 | sum | i |
---|---|---|
初始条件 | 0 | 1 |
第一次后 | 1 | 2 |
第二次后 | 3 | 3 |
第三次后 | 6 | 4 |
第四次后 | 10 | 5 |
循环嵌套
嵌套循环是指在一个循环语句的循环体中再定义一个循环语句的语法结构。while、do…while、for循环语句都可以进行循环嵌套,并且它们之间也可以互相嵌套。在实际开发时,我们最常用的是for循环嵌套,其语法格式如下:
for(初始化表达式; 循环条件; 操作表达式) {
...
for(初始化表达式; 循环条件; 操作表达式) {
执行语句
...
}
...
}
在上面的语法结构中,展示了双层for循环嵌套的基本格式。其中外层循环每执行一轮,都要执行完内层循环中的整个for循环,然后执行外层循环第二轮,接着再执行完内层循环中的整个for循环,以此类推,直至外层循环的循环条件不成立,才会跳出整个嵌套for循环。
接下来通过一个打印九九乘法表的案例来演示嵌套for循环的使用,如文件1所示。
文件1 Example14.java
1 public class Example14 {
2 public static void main(String[] args) {
3 // 外层循环,变量i控制输出1~9行
4 for (int i = 1; i <= 9; i++) {
5 // 内层循环,变量j控制输出1~9列
6 for (int j = 1; j <= i; j++) {
7 //使用字符串拼接的形式,打印每个内层循环的执行语句
8 System.out.print(j+"*"+i +"="+ j*i +"\t");
9 }
10 // 使用println()方法控制外层循环进行换行
11 System.out.println("");
12 }
13 }
14 }
运行结果如图1所示。
图1 运行结果
文件1中,定义了双层for循环,分别为外层循环和内层循环,外层循环用于控制循环的行数,内层循环用于控制循环的列数,并循环打印出第i行第j列对应的执行语句。输出语句中的“\t”就是前面小节多学一招中介绍的转义字符,表示制表符,这里用来格式化输出语句;另外输出方法print()用于向控制台输出语句,执行完后不会换行,而println()方法同样用于向控制台输出语句,但每次执行完后都会自动换行。
由于上述演示九九乘法表的嵌套循环程序比较复杂,为了使初学者能够清晰的知道程序的整个执行过程,下面分步骤进行详细地讲解,具体如下:
第1步,程序首先进入双层循环的外层循环,在第4行代码将i初始化为1,条件i <= 9为true,首次进入外层循环的循环体,也就是进入内层循环。
第2步,程序进入内层循环,在第6行代码将j初始化为1,由于此时i的值为1,条件j <= i为true,首次进入内层循环的循环体,会使用print()输出语句中的拼接字符串,打印出“1*1=1”。
第3步,程序执行完一次内层循环体后,接着执行第6行代码中内层循环的操作表达式j++,将j的值自增为2。
第4步,程序在内层循环中进行第二轮循环,执行第6行代码中的判断条件j<=i,判断结果为false,本次内层循环结束。接着执行外层循环后续的代码println()方法进行换行。
第5步,程序执行完整个外层循环内部的执行语句后,会执行第4行代码中外层循环的操作表达式i++,将i的值自增为2。
第6步,程序进入外层循环的第二轮循环,执行第4行代码中的判断条件i<=9,判断结果为true,进入外层循环的循环体,继续执行内层循环。
第7步,由于第二轮外层循环时,i的值为2,内层循环会执行两次,即在控制台第2行执行两次print()方法中的输出语句,打印出“12=2 22=4”,在内层循环结束后再次执行println()换行方法进行换行。
第8步,以此类推,直到外层循环的变量i值为10时,外层循环的判断条件i <= 9结果为false,外层循环结束,整个嵌套循环程序也就结束了。
跳转语句(break和continue)
跳转语句用于实现循环语句执行过程中程序流程的跳转,在Java中的跳转语句有break语句和continue语句。接下来分别对这两个语句进行详细地讲解。
1.break语句
在switch条件语句和循环结构语句中都可以使用break语句。当它出现在switch条件语句中时,作用是终止某个case并跳出switch结构;当它出现在循环结构语句中时,作用是跳出当前循环结构语句,执行后面的代码。接下来将对文件2-11稍作修改,来演示while循环中break语句的用法,修改后的代码如文件1所示。
文件1 Example15.java
1 public class Example15 {
2 public static void main(String[] args) {
3 int x = 1; // 定义变量x,初始值为1
4 while (x <= 4) { // 判断循环条件是否成立
5 System.out.println("x = " + x); // 条件成立,打印x的值
6 if (x == 3) {
7 break; //使用break语句,当x==3时跳出while循环
8 }
9 x++; // 控制变量x,进行自增
10 }
11 }
12 }
运行结果如图1所示。
图1 运行结果
从图1可以看出,当x的值为3时就会执行break语句,从而跳出整个循环,因此打印结果中并没有出现“x = 4”。
循环结构中的break语句默认只会跳出当前所在循环,在嵌套循环结构中,如果想要在内层循环中使用break语句跳出外层循环,则需要预先对外层循环进行标记,然后使用break语句跳出指定的外层循环。接下来对文件2-14稍作修改,控制程序打印“二二乘法表”,如文件2所示。
文件2 Example16.java
1 public class Example16 {
2 public static void main(String[] args) {
3 // 外层循环,变量i控制输出1~9行
4 outer: for (int i = 1; i <= 9; i++) {
5 // 内层循环,变量j控制输出1~9列
6 for (int j = 1; j <= i; j++) {
7 if (i >= 3) {
8 break outer; // 当循环打印第3行时,直接跳出outer外层循环
9 }
10 // 使用字符串拼接的形式,打印每个内层循环的执行语句
11 System.out.print(j + "*" + i + "=" + j * i + "\t");
12 }
13 // 使用println()方法控制外层循环进行换行
14 System.out.println("");
15 }
16 }
17 }
运行结果如图2所示。
图2 运行结果
从图2可以看出,修改后的程序打印了2行结果,只输出了“二二乘法表”。因为文件2-16中,在外层for循环前面增加了标记“outer”,当i>=3时,使用break outer;语句会跳出指定的外层循环,因此程序只打印了2行结果。
2.continue语句
continue语句用在循环语句中,它的作用是终止本次循环,执行下一次循环。接下来通过一个对1~100之内奇数求和的案例来演示continue语句的使用,如文件3所示。
文件3 Example17.java
1 public class Example17 {
2 public static void main(String[] args) {
3 int sum = 0; // 定义变量sum,用于记住和
4 for (int i = 1; i <= 100; i++) {
5 if (i % 2 == 0) { // i是一个偶数,不累加
6 continue; // 结束本次循环,进入下一次循环
7 }
8 sum += i; // 实现sum和i的累加
9 }
10 System.out.println("sum = " + sum);
11 }
12 }
运行结果如图3所示。
图3 运行结果
文件3中,使用for循环让变量i的值在1~100之间循环遍历,在循环过程中,使用i%2对变量i的值进行基偶数判断,当i的值为偶数时,将执行continue语句结束本次循环,进入下一次循环;当i的值为奇数时,sum和i进行累加,最终得到1~100之间所有奇数的和,打印“sum = 2500”。
在嵌套循环语句中,continue语句后面也可以通过使用标记的方式结束本次外层循环,用法与break语句相似,在此不再举例说明。
数组的定义
在Java中,数组的定义有三种方式,其基本语法格式如下:
数组类型[] 数组名 = new 数组类型[数组长度];
数组类型[] 数组名 = new 数组类型[]{数组元素0,数组元素1,...};
数组类型[] 数组名 = {数组元素0,数组元素1,...};
在上述三种定义数组的语法格式中,数组类型可以是前面小节中介绍的变量的数据类型,数组名就是定义的变量名,要符合标识符的规范,数组长度表示该数组中可容纳的数组元素的个数,而数组元素0、数组元素1就表示定义的数组中具体包含的数据。
以上三种定义数组语法格式的实际使用示例如下:
int[] ids = new int[100];
String[] names = new String[]{"张三","tom",...};
Object[] object = {"张三","tom",...};
第一种方式定义了一个int[]数组类型的变量ids,使用new int[100]指定了该数组的长度(也就是元素的个数)为100,但并没有为数组中的元素赋值;第二种和第三种方式分别定义了一个String[]类型和Object[]类型的数组变量names和object,并都通过“=”右边的大括号“{}”为数组完成了初始化赋值。
关于数组在内存中的存储方式,这里以上述定义数组的第一种方式为例,来进行具体说明。第一种数组定义的方式,相当于在内存中定义了100个int类型的变量,第一个变量的名称为ids[0],第二个变量的名称为ids[1],以此类推,第100个变量的名称为ids[99],这些变量的初始值都是0。为了更好地理解数组的这种定义方式,可以将上面的一句代码分成两句来写,具体如下:
int[] ids; // 声明一个int[]类型的变量
ids = new int[100]; // 创建一个长度为100的数组,并将数组地址赋值给数组类型的变量x
接下来通过两张内存图来详细地说明数组在创建过程中内存的分配情况。
第一行代码 int[] ids; 声明了一个变量ids,该变量的类型为int[],即一个int类型的数组。变量ids会占用一块内存单元,它没有被分配初始值。此时ids在内存中的状态如图1所示。
图1 内存状态图
第二行代码 ids = new int[100]; 创建了一个数组,并将数组的内存地址赋值给变量ids。在程序运行期间可以使用变量ids来引用数组,这时内存中的状态会发生变化,如图2所示。
图2 内存状态图
图2描述了变量ids引用数组的情况。该数组中有100个元素,初始值都为0。数组中的每个元素都有一个索引(也可称为角标),要想访问数组中的元素可以通过“数组名[角标]”的形式,即“ids[0]、…、ids[99]”。需要注意的是,数组中最小的索引是0,最大的索引是“数组的长度-1”。在Java中,为了方便获得数组的长度,提供了一个length属性,在程序中可以通过“数组名.length”的方式来获得数组的长度,即数组元素的个数。
接下来通过一个完整的案例来演示如何定义数组以及访问数组中的元素,如文件1所示。
文件1 Example18.java
1 public class Example18 {
2 public static void main(String[] args) {
3 // 1、第一种方式定义数组
4 int[] ids = new int[100];
5 System.out.println("ids[0]="+ids[0]); //访问数组第一个元素
6 System.out.println("ids[99]="+ids[99]);//访问数组最后一个元素
7 System.out.println("==============");
8 // 2、第二种方式定义数组
9 String[] names = new String[]{"张三","tom","jack"};
10 System.out.println("names[0]="+names[0]); //访问数组第一个元素
11 System.out.println("names[2]="+names[2]); //访问数组最后一个元素
12 System.out.println("==============");
13 // 3、第三种方式定义数组
14 Object[] object = {"张三","tom","jack"};
15 System.out.println("数组的长度为:"+object.length);//获取数组的长度
16 System.out.println("object[0]="+object[0]); //访问数组第一个元素
17 System.out.println("object[2]="+object[2]); //访问数组最后一个元素
18 }
19 }
运行结果如图3所示。
图3 运行结果
文件1中,分别声明了一个int[]类型、String[]类型和Object[]类型的数组,然后通过“数组名[角标]”的形式获取了数组的第一个和最后一个元素值,并打印到控制台上。从打印结果可以看出,第一种定义数组方式定义的int[]类型的数组中元素的初始值都为0,这是因为当数组被成功创建后,数组中元素会被自动赋予一个默认值,根据元素类型的不同,默认初始化的值也是不一样的;而第二种和第三种定义数组方式定义的数组都指定了初始值,所以打印出对应角标的元素值。
Java中不同数据类型元素的初始值如表1所示。
表1 Java不同数据类型元素的默认值
数据类型 | 默认初始化值 |
---|---|
byte、short、int、long | 0 |
float、double | 0.0 |
char | 一个空字符,即’\u0000’ |
boolean | false |
引用数据类型 | null,表示变量不引用任何对象 |
在使用数组时,如果不想使用这些默认初始值,可以显式地为这些元素赋值,当然也可以对定义数组时的指定初始值进行修改。接下来对上面的案例进行修改来学习一下如何为数组中的元素赋值和修改,如文件2所示。
文件2 Example19.java
1 public class Example19 {
2 public static void main(String[] args) {
3 // 1、第一种方式定义数组
4 int[] ids = new int[100];
5 System.out.println("ids[0]="+ids[0]); //访问数组第一个元素
6 // 为数组指定元素进行初始化赋值
7 ids[0] = 2500;
8 System.out.println("ids[0]="+ids[0]); //再次访问数组第一个元素
9 System.out.println("==============");
10 // 2、第二种方式定义数组
11 String[] names = new String[]{"张三","tom","jack"};
12 System.out.println("names[0]="+names[0]); //访问数组第一个元素
13 // 为数组定义时指定的初始值进行修改
14 names[0] = "李四";
15 System.out.println("names[0]="+names[0]); //再次访问数组第一个元素
16 System.out.println("==============");
17 // 3、第三种方式定义数组
18 Object[] object = {"张三","tom","jack"};
19 System.out.println("object[0]="+object[0]); //访问数组第一个元素
20 // 为数组定义时指定的初始值进行修改
21 object[0] = "李四";
22 System.out.println("object[0]="+object[0]); //再次访问数组第一个元素
23 }
24 }
运行结果如图4所示。
图4 运行结果
文件2中,三种定义数组的方式都使用了“数组名[角标]=元素值”的形式对各数组中指定角标的元素进行了赋值和修改。从修改的代码以及程序运行结果可以看出,对数组中的元素进行赋值或修改操作所使用的方式一样,只是在使用“数组名[角标]=元素值”的形式对数组元素进行操作时,如果数组指定角标的元素只有默认初始值,那么就是对元素进行的赋值操作;如果数组指定角标的元素已经有了初始值,那么就是对元素进行的修改操作。
脚下留心:
1.每个数组的索引都有一个范围,即0~length-1。在访问数组的元素时,索引不能超出这个范围,否则程序会报错,如文件3所示。
文件3 Example20.java
1 public class Example20 {
2 public static void main(String[] args) {
3 int[] arr = new int[4]; // 定义一个长度为4的数组
4 System.out.println("arr[4]=" + arr[4]); // 通过角标4访问数组元素
5 }
6 }
运行结果如图3所示。
图5 运行结果
从图5可以看出,程序运行后提示了“ArrayIndexOutOfBoundsException”即数组角标越界异常的错误。出现这个异常的原因是数组的长度为4,其索引范围为0~3,文件中的第4行代码使用索引4来访问元素时超出了数组的索引范围。所谓异常指程序中出现的错误,它会报告出错的异常类型、出错的行号以及出错的原因,关于异常内容在后面的章节会有详细地讲解。
2.在使用变量引用一个数组时,变量必须指向一个有效的数组对象,如果该变量的值为null,则意味着没有指向任何数组,此时通过该变量访问数组的元素会出现空指针异常,接下来通过一个案例来演示这种异常,如文件4所示。
文件4 Example21.java
1 public class Example21 {
2 public static void main(String[] args) {
3 int[] arr = new int[3]; // 定义一个长度为3的数组
4 arr[0] = 5; // 为数组的第一个元素赋值
5 System.out.println("arr[0]=" + arr[0]); // 访问数组的元素
6 arr = null; // 将变量arr置为null
7 System.out.println("arr[0]=" + arr[0]); // 再次访问数组的元素
8 }
9 }
运行结果如图6所示。
图6 运行结果
从图6可以看出,文件4中的第4、5行代码都能通过变量arr正常地操作数组,而在第6行代码将变量置为null后,在第7行代码再次访问数组时就出现了“java.lang.NullPointerException”的异常信息,即空指针异常。
数组的常见操作
在程序开发中,数组的应用非常广泛,灵活地使用数组对实际开发很重要。接下来,本节将针对数组的常见操作,如数组的遍历、最值的获取、数组的排序等,进行详细地讲解。
1.数组遍历
在操作数组时,经常需要依次访问数组中的每个元素,这种操作称作数组的遍历。接下来通过一个案例来学习一下如何使用for循环来遍历数组,如文件1所示。
文件1 Example22.java
1 public class Example22 {
2 public static void main(String[] args) {
3 int[] arr = { 1, 2, 3, 4, 5 }; // 定义数组
4 // 使用for循环遍历数组的元素
5 for (int i = 0; i < arr.length; i++) {
6 System.out.println(arr[i]); // 通过索引访问元素
7 }
8 }
9 }
运行结果如图1所示。
图1 运行结果
文件1中,定义了一个长度为5的数组arr,数组角标的取值范围为0~4。由于for循环中定义的变量i的值在循环过程中为0~4,因此可以作为索引,依次去访问数组中的元素,并将元素的值打印出来。
2.数组最值
在操作数组时,经常需要获取数组中元素的最值。接下来通过一个示例来演示如何获取数组中元素的最大值,如文件2所示。
文件2 Example23.java
1 public class Example23 {
2 public static void main(String[] args) {
3 // 1、定义一个int[]数组
4 int[] arr = { 4, 1, 6, 3, 9, 8 };
5 // 2、定义变量max用于记住最大数,首先假设第一个元素为最大值
6 int max = arr[0];
7 // 3、遍历数组,查找最大值
8 for (int i = 1; i < arr.length; i++) {
9 // 比较 arr[i]的值是否大于max
10 if (arr[i] > max) {
11 // 条件成立,将arr[i]的值赋给max
12 max = arr[i];
13 }
14 }
15 System.out.println("数组arr中的最大值为:" + max); // 打印最大值
16 }
17 }
运行结果如图2所示。
图2 运行结果
文件2中,定义了一个临时变量max,用于记住数组的最大值。首先假设数组中第一个元素arr[0]为最大值,然后使用for循环对数组进行遍历,在遍历的过程中只要遇到比max值还大的元素,就将该元素赋值给max,这样一来,变量max就能够在循环结束时记住数组中的最大值。
3.数组排序
在操作数组时,经常需要对数组中元素进行排序。接下来讲解一种比较常见的数组排序算法——冒泡排序。所谓冒泡排序,就是不断地比较数组中相邻的两个元素,较小者向上浮,较大者往下沉,整个过程和水中气泡上升的原理相似。
接下来通过几个步骤来具体分析一下冒泡排序的整个过程,具体如下:
第1步,从第一个元素开始,将相邻的两个元素依次进行比较,直到最后两个元素完成比较。如果前一个元素比后一个元素大,则交换它们的位置。整个过程完成后,数组中最后一个元素自然就是最大值,这样也就完成了第一轮比较。
第2步,除了最后一个元素,将剩余的元素继续进行两两比较,过程与第一步相似,这样就可以将数组中第二大的数放在了倒数第二个位置。
第3步,以此类推,持续对越来越少的元素重复上面的步骤,直到没有任何一对元素需要比较为止。
了解了冒泡排序的原理之后,接下来通过一个案例来实现冒泡排序,如文件3所示。
文件3 Example24.java
1 public class Example24 {
2 public static void main(String[] args) {
3 int[] arr = { 9, 8, 3, 5, 2 };
4 // 1、冒泡排序前,先循环打印数组元素
5 for (int i = 0; i < arr.length; i++) {
6 System.out.print(arr[i] + " ");
7 }
8 System.out.println(); // 用于换行
9 // 2、进行冒泡排序
10 // 2.1、外层循环定义需要比较的轮数(两数对比,要比较n-1轮)
11 for (int i= 1; i < arr.length; i++) {
12 // 2.2、内层循环定义第i轮需要比较的两个数
13 for (int j = 0; j < arr.length -i; j++) {
14 if (arr[j] > arr[j + 1]) { // 比较相邻元素
15 // 下面的三行代码用于相邻两个元素交换
16 int temp = arr[j];
17 arr[j] = arr[j + 1];
18 arr[j + 1] = temp;
19 }
20 }
21 }
22 // 3、完成冒泡排序后,再次循环打印数组元素
23 for (int i = 0; i < arr.length; i++) {
24 System.out.print(arr[i] + " ");
25 }
26 }
27 }
运行结果如图3所示。
图3 运行结果
文件3中,第2步通过一个嵌套for循环实现了冒泡排序。其中,外层循环用来控制进行多少轮比较,每一轮比较都可以确定一个元素的位置,由于最后一个元素不需要进行比较,因此外层循环的次数为arr.length-1;内层循环的循环变量用于控制每轮进行比较的相邻的两个数,它被作为角标去比较数组的元素,由于变量在循环过程中是自增的,这样就可以实现相邻元素依次进行比较,在每次比较时,如果前者小于后者,就交换两个元素的位置,具体执行过程如图4所示。
图4 冒泡排序
从图4可以看出,在第一轮比较中,第一个元素“9”为最大值,因此它在每次比较时都就会发生位置的交换,最终被放到最后一个位置;第二轮比较与第一轮过程类似,元素“8”被放到倒数第二个位置;第三轮比较中,第一次比较没有发生位置的交换,在第二次比较时才发生位置交换,元素“5”被放到倒数第三个位置;第四轮比较只针对最后两个元素,它们比较后发生了位置的交换,元素“3”被放到第二个位置。通过四轮比较,很明显,数组中的元素已经完成了排序。
值得一提的是,文件3中第16~18行代码实现数组中两个元素交换的过程。首先定义了一个临时变量temp用于记住数组元素arr[j]的值,然后将arr[j+1]的值赋给arr[j],最后再将temp的值赋给arr[j+1],这样便完成了两个元素的交换。整个交换过程如图5所示。
图5 交换步骤
多维数组
在程序中可以通过一个数组来保存某个班级学生的考试成绩,试想一下,如果要统计一个学校各个班级学生的考试成绩,又该如何实现呢?这时就需要用到多维数组,多维数组可以简单地理解为在数组中嵌套数组。在程序中,比较常见的多维数组是二维数组,接下来本小节将针对二维数组进行详细地讲解。
二维数组可以被看作是特殊的一维数组,其定义有很多方式,接下来针对几种常见的方式进行详细地讲解,具体如下:
第一种方式,指定二维数组的长度和每个数组的元素个数,其基本语法格式如下:
int[][] arr = new int[3][4];
上面的代码相当于定义了一个34的二维数组,这个二维数组的长度为3,我们可以将它看成3个int[]类型的一维数组,每个一维数组中的元素又是一个长度为4的一维数组。接下来通过一个图来表示这种情况,如图1所示。
图1 二维数组
第二种方式,只指定二维数组的长度,不确定每个数组的元素个数,其基本语法格式如下:
int[][] arr = new int[3][];
第二种方式和第一种类似,只是数组中每个元素的长度不确定,接下来通过一个图来表示这种情况,如图2所示。
图2 二维数组
第三种方式,直接使用嵌套大括号“{}”,对二维数组初始化赋值,其基本语法格式如下:
int[][] arr = {{1,2},{3,4,5,6},{7,8,9}};
上面的二维数组中定义了三个元素,这三个元素都是数组,分别为{1,2}、{3,4,5,6}、{7,8,9},接下来通过一个图来表示这种情况,如图3所示。
图3 二维数组
对二维数组中元素的操作也是通过角标的方式来完成的,具体示例代码如下:
arr[0][1]; // 获取二维数组中第一个元素组的第二个元素
arr[0][1]=1; // 对二维数组中第一个元素组的第二个元素赋值或修改
接下来通过一个统计公司三个销售小组中每个小组的总销售额,以及整个公司销售额的案例来熟悉二维数组的使用,如文件1所示。
文件1 Example25.java
1 public class Example25 {
2 public static void main(String[] args) {
3 int[][] arr = new int[3][]; // 定义一个长度为3的二维数组
4 arr[0] = new int[] { 11, 12 }; // 为数组的元素赋值
5 arr[1] = new int[] { 21, 22, 23 };
6 arr[2] = new int[] { 31, 32, 33, 34 };
7 int sum = 0; // 定义变量记录总销售额
8 for (int i = 0; i < arr.length; i++) { // 遍历数组元素
9 int groupSum = 0; // 定义变量记录小组销售总额
10 for (int j = 0; j < arr[i].length; j++) {
11 groupSum += arr[i][j];
12 }
13 sum +=groupSum; // 累加小组销售额
14 System.out.println("第" + (i + 1) + "小组销售额为:"
15 + groupSum + " 万元。");
16 }
17 System.out.println("总销售额为: " + sum + " 万元。");
18 }
19 }
运行结果如图4所示。
图4 运行结果
文件1中,第3行代码定义了一个长度为3的二维数组,并在4~6行代码中为数组的每个元素赋值。文件中还定义了两个变量sum和groupSum,其中sum用来记录公司的总销售额,groupSum用来记录每个销售小组的销售额。当通过嵌套for循环统计销售额时,外层循环对三个销售小组进行遍历,内层循环对每个小组员工的销售额进行遍历,内层循环每循环一次就相当于将一个小组员工的销售总额统计完毕,赋值给groupSum,然后把groupSum的值与sum的值相加赋值给sum。当外层循环结束时,三个销售小组的销售总额groupSum都累加到sum中,即统计出了整个公司的销售总额。
未完,持续更新中。。。
自学java教程视频+资料+笔记,企鹅49.89.138.68