Java控制流程和数组

控制流程

三类程序结构:顺序结构、分支结构、循环结构

分支结构:用于实现根据条件来选择性执行某一段代码,因此分支结构也被叫做选择结构。
循环结构:则是用于实现根据循环条件重复执行某段代码。
Java提供了if和switch两种分支语句,还有while,do while和for三种盾环语句。

JDK5还提供了增强for盾环,foreach循环。以更简洁的书写方式遍历集合、数组。

顺序结构

顺序结构:代码都是自上而下,依次执行,不会出现跳行执行、逆向执行等情况。

分支结构

java提供了两种常见的分支结构:if语句和switch语句,其中if语句使用布尔表达式或者布尔值作为分支结构条件来进行分支控制。

if条件语句

if语句使用布尔表达式或者布尔值作为分支条件来进行分支控制,语句有以下三种形式。
单分支结构(一个if)、双分支结构(if..else)、多分支结构(if、else if)

switch条件语句

switch语句有一个控制表达式和多个case标签组成,和if语句不同的是,switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种类型,从Java7以后添加了枚举类型和String类型
switch语句通常需要在case标签后紧跟一个代码块,case标签作为这个代码块的标识,switch语句的语法格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
switch (expression){
case condition1:
{
statement(s)
break;
}
case condition2:
{
statement(s)
break;
}
case condition2:
{
statement(s)
break;
}
....
case condition N:
{
statement(s)
break;
}
default:{
statement (s)
}
}

switch分支语句的执行是先对expression求值,然后依次匹配case,如果没有与之匹配的,则执行default

分支嵌套

如果把一个分支结构放进另一个分支结构中,这种情况叫做分支嵌套,分支嵌套可以是if中嵌套switch,switch中嵌套if,也可以是if互相嵌套或者switch自身嵌套。例如,计算一个年份是否是闰年。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
System.out.println("请输入年份:");
Scanner scanner = new Scanner(System.in);
int year = scanner.nextInt();

if(year%4==0) {
if(year%100==0) {
if(year%400==0) {
System.out.println(year+"是闰年");
}else {
System.out.println(year+"不是闰年");
}
}else {
System.out.println(year+"是闰年");
}
}else {
System.out.println(year+"不是闰年");
}
}

循环结构

循环结构是指在满足某个循环条件的情况下,反复执行同一段代码,直到不满足循环条件为止。被循环执行的代码叫做循环体。当反复执行循环体时,要在适当的时机修改循环条件,从而结束循环,否则循环会一直进行下去,形成死盾环。一个恰当的盾坏结构应该包含以下4个组成部分:
1.初始化语句:一个或多个语句,这些语句用来完成一些初始化工作,在循环开始之前执行
2.循环条件:循环条件是一个布尔表达式,该表达式决定是否执行循环体
3.循环体:这部分是循环的主题,如果循环条件允许,该代码块将被反复执行,如果这个代码块只有一条语句,则代码块的花括号可以管略。
4.迭代语句:这部分在一次循环体执行结束后执行,在循环条件求值前执行,通常用于控制循环条件中的变量,使得循环在合适的时候结束。

for循环

1
2
3
for([init_statment];[test_exression];[iteration_statement]){
statement;
}

初始化语句只在循环条件开始前执行依次,对于for循环而言,循环条件总比循环体要多执行一次,因为最后一次执行循环条件返回false,将不再执行循环体。

注意:除非特殊情况尽量不要在循环体内修改循环变量的值。

for循环圆括号中只有两个分号是必须的,初始化语句、循环条件、迭代语句部分都是可以省略的,如果省略了循环条件,则这个循环条件默认为true,将产生死循环。例如下面的代码:

1
2
3
4
5
6
7
public class EndlessForLoop {
public static void main(String[] args) {
for (;;) {
System.out.println("-----endless-----");
}
}
}

while循环

1
2
3
4
5
[init_statement]
while(test_expression){
statement;
[iteration_statment]
}

while循环每次执,行循环体,之前,先对test_expression循环条件求值,如果循环条件为true,则执行循环体部分。从上述伪代码中来看,iteration_statment位于循环体的最后,因此只有当循环体成功执行完成时,while循环才会执行iteration_statmenti语句。

do while循环

do while循环与while循环的区别在于:while循环是先判断循环条件,如果条件为真则执行循环体;而do while循环则新执行循环体,然后才判断循环条件,如果循环条件为真,则执行下一次循环,否则终止循环。do while循环语法格式如下:

1
2
3
4
5
6
[init_statement]
do{
statement;
[iteration_statment]
}
while(test_expression);

注意:do while循环的循环条件必须有一个分号,该分号代表该循环结构结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {

int i = 0;
int k = 0;
while (++i>1) {
System.out.println("循环条件至少执行1次");
}
System.out.println(i);
do {
k++;
}while(false);

System.out.println(k);

}

++i=1执行后的值为1,此时该表达式结果为false,不执行循环体,而do while循环中循环条件直接是false,但是此时仍l旧执行了循环体,k的值成为了1。

控制循环结构

Java提供了continue和break控制循环结构

使用break结束循环

break:用于完全结束一个循环,跳出循环体,不管哪种循环,一旦循环体遇到break,系统将完全结束该循环,开始执行循环结构以后的代码。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if(i==7) {
System.out.println(i);
break;
}
}
}

当i=7时,直接跳出循环

使用continue跳过此次循环

continue的功和break有点类似,区别是continue只是跳出此次循环,继续执行剩下的循环,并不是完全终止循环。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
if(i==7) {
continue;
}
System.out.println(i);
}
}

循环嵌套

如果把一个循环放进另一个循环体内,那么就可以形成循环嵌套,循环嵌套可以是上述3种循环的任意嵌套,例如:for循环和while循环的相互嵌套,while循环和do while循环的相互嵌套,也可以是while循环和do while循环的相互嵌套。

当程序遇到循环嵌套是,如果外层循环的循环条件允许,则开始执行外层循环的循环体,而内层循环将被外层循环的循环体来执行,只是内层循环需要反复执行自己的循环体而已。当内层循环执行结束,且完成循环的循环体执行结束时,在再次计算外层循环的循环条件,决定是否再次执行外层循环的循环体。

示例:打印等腰三角形

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
for(int i = 1; i < 6; i++) {
for(int k = 5-i; k > 0; k-- ) {
System.out.print(" ");
}
for(int j = 0; j < (2*i) - 1; j++) {
System.out.print("*");
}
System.out.println();
}
}

示例:冒泡排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr = {9,8,7,6,5};

for(int i = 0; i < arr.length - 1; i++) {
System.out.println("第"+(i+1)+"趟比较:");

for(int j = 0; j < arr.length - 1 -i; j++) {
if(arr[j] > arr[j+1]) {
int temp = 0;
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
System.out.print("第"+(j+1)+"次比较:");
System.out.println(Arrays.toString(arr));
}
}

}

示例:二分查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) {
int[] a = {1,3,5,7,9,11};
int cc = Demo11.binarySearch(a, 3);
System.out.println(cc);
}

public static int binarySearch(int[] srcArray, int des) {
//定义初始最小、最大索引
int start = 0;
int end = srcArray.length - 1;//5
//确保不会出现重复查找,越界
while (start <= end) {
//计算出中间索引值
int middle = (end + start)>>>1 ;//防止溢出//3
if (des == srcArray[middle]) {
return middle;
//判断下限
} else if (des < srcArray[middle]) {
end = middle - 1;
//判断上限
} else {
start = middle + 1;
}
}
//若没有,则返回-1
return -1;
}
//类是对象的模板,对象是类的实例
}

循环嵌套中的break

break语句仅结束其所在的循环,例如在双层循环嵌套中,只结束内层循环,而不能结束外层盾环。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {

for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
if(j==2) {
break;
}
System.out.println("内存循环:"+j);
}
System.out.println("外存循环:"+i);
}
}

运行上述代码,我们可以发现break只是结束了内层自己所在的循环,外层循环并没有被结束,仍旧在继续循环。那么,如何让break结束外层循环呢,此时就要在break后面紧跟一个标签了,这个标签用于表示一个外层盾环。Java中的标签就是一个紧跟着冒号的标识符,与其他语言不同的是,Java中的标签只有放在循环语句前才有作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
outer:
for (int i = 0; i < 5; i++) {
//内层循环
for (int j = 0; j < 3; j++) {
if(j==1) {
//跳出outer所标识的循环
break outer;
}
System.out.println("i的值为:"+i+",j的值为:"+j);
}
}
}

程序从外层循环进入内层循环,当j等于1时,程序遇到一个break outer语言,该语句将会导致结束outer标签指定的循环,不是break所在的循环,而是结束break循环的外层循环。

需要注意的是,break后的标签必须是一个有效的标签,这个标签必须在break语句所在的循环之前定义,或者在所在循环的外层循环之前定义。当然,如果把这个标签放在break语句所在的循环之前定义,也就失去了标签的意义,因为break默认就是结束其所在的循环。

循环嵌套中使用continue

将上例中break换成continue,此处continue的作用不变,只是在多层循环中,continue会忽略当前循环,直接到外层循环继续进行循环

数组

数组就是:具有相同数据类型且按照一定次序排列的一组数据的集合。

在数组的概念中需要注意以下几点:
1.具有相同的数据类型,也就是说存放在一个数组的元素必须数据类型相同,不能把不同类型的元素放进同一个数组。
2.按照一定次序排列,数组中的元素是有序的,这句话包含两层含义,第一:元素是有顺序的。也可以理解为每个数组元素都有一个编号,这个编号是连续的(我们通常把这个编号叫做数组的下标或者索引);第二:元素存放的顺序
和取出的顺序是一致的,也就是说存放元素的顺序是10,20,30,40。那么取出的顺序也是10,20,30,40。
3.一组数据,一组数据是指元素的个数可以是0个,也可以是1个,也可以是多个,Java中允许数组中有0个元素,也就是我们所说的空数组。

定义数组

Java的数据类型可以分为两大类:基本类型和引用类型,其中基本类型有8个,引用类型则包括:对象,数组,接口。因此数组也是一种数据类型。数组的定义方式有两种语法:

1
2
type[] arrayName;
type arrayName[];

int类型是一个基本类型,而int[]则是一个引用类型。

数组初始化一旦完成,数组在内存中所占的空间将被固定下来,长度就不会改变,若长度改变,则一定

数组是一种引用类型的变量,当仅仅是定义了数组后,仅仅是定义了一个引用变量,这个变量还未指向任何有效内存,此时整个数组还不能使用,只有对数组初始化后才能使用。

数组的初始化

Java中数组必须先初始化,然后才可以使用。所谓初始化,就是为数组的元素分配内存空间,并为每个元素赋初始值。
数组的初始化有两种方式:
1.静态初始化:初始化时由程序员指定每个元素的初始值。由系统觉定数组的长度
2.动态初始化:初始化是程序员指定数组长度,由系统为元素分配初始值。
静态初始化语法格式如下:

1
2
arrayName = new type[]{e1,e2,e3}
arrayName = {e1,e2,e3}

动态初始化只指定数组的长度,由系统为每个元素指定初始值。动态初始化的语法格式如下:
arrayName new type[length];
在上面的语法中,需要指定一个int类型的length参数,该参数指定了数组的长度,也就是可以容纳数组元素的个数,与静态初始化类型,此处的
type必须与定义数组时使用的type类型相同。动态初始化语法格式如下:

1
2
int[] ary  = new int[3];
char[] chars = new char[5];

执行动态初始化时,开发者只需指定数组的长度,即为每个元素指定所需的内存空间,系统将负责为这些数组元素分配初始值。指定初始值时,系统按如下规则分配初始值:
·数组元素的类型是基本类型中的整型(byte,short,int,Iong),则数组元素的初始值为0;
·数组元素的类型是基本类型中的浮点型(float,double),则数组元素的值是0.0;
·数组元素的类型是基本类型中的字符型(char),则数组元素的值是’\u0000’:
·数组元素的类型是基本类型中的布尔型(boolean),则数组元素的默认值是false。
·数组元素的类型是基本类型中的引用类型,则数组元素的默认值是null。

使用数组

1
2
3
4
//定义一个整型数组,并对其动态初始化,此时数组中的元素都是默认值
int[] a = new int [5];
//对数组的第二个元素进行赋值
a[1] = 10

遇到数组索引时,需要考虑java.lang.ArraryIndexOutOfBoundsException(数组越界异常)。

for循环遍历数组

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
int[] a = new int[3];
for (int i = 0; i < a.length; i++) {
a[i] = i+100;
}
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}

foreach遍历数组

Java5以后,Java提供了更简洁遍历数组的方式:foreach循环。使用foreach遍历数组和集合元素时,无需获得数组和集合长度,无需根据索引来访问数组元素。

foreach我们可以理解为增强for循环,不需要开发者根据索引去数组中取值,系统会自动从数组a中取到每一个元素,并赋值给i,只需要打印i即可,这种方式也有效的避免了数组下标越界异常。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//定义一个整型数组,并对其静态初始化
int[] a = {1,2,3,4,5};
//使用foreach遍历
for (int i : a) {
System.out.println(i);
}
}

深入数组

内存中的数组

数组引用变量只是一个引用,这个引用变量可以指向任何有效内存,只有当该引用指向有效内存后,才可以通过该数组变量访问数组。

1
int [] a;

此时只是定义了一个数组类型的变量,该变量没有指向任何有效内存,如果此时访问数组元素时,将会引发空指针异常。

与所有引用变量相同的是,引用变量是访问真实对象的根本方式,也就是说,如果希望在程序中访问数组对象本身,只能通过数组的引用变量来访问。

实际的数组对象被存储在堆内存中,如果引用该数组对象的数组引用变量是一个局部变量,那么它将被存储在栈内存中。数组在内存中的示意图如下图所示:

数组的长度不能被改变

1
2
3
4
5
6
public static void main(String[] args) {
int[] a = new int[4];
int[] b = new int[5];
a = b;
System.out.println(a.length);
}

运行上述代码,发现数组a的长度发生了变化

当代码执行完第10行时,此时数组a和数组b的内存示意图如下:

当代码执行第11行时,此时内存中发生如下变化:

从图示中可以看出,此时a并不指向原来的内存空间,而是和b一样指向了相同的内存空间,而原来a指向的内存还在内存中,也就是说变的仅仅是变量a指向的内存空间,而不是数组长度,a原来指向的数组长度还是4,并没有发生变化

数组一旦被初始化后长度不可变,如果发生了变化,一定是指向了新的数组。

多维数组

Java语言里提供了多维数组的支持,但是实际上并不存在多维数组,例如二维数组,完全可以认为是数组类型的数组:因为Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实数组的内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,此时就产生了多维数组,换句话说,多维数组本质都是一维数组。我们可以把数组当成一种数据类型,可以理解为数组类型的数组,二维数组的语法如下:

1
type[] [] a = new type[length1][length2]

下面通过实例来定义二维数组,并对其进行遍历

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//此处可以理解为定义了一个3行4列的矩阵
//也可以理解为外层的数组长度为3,即包含了3个数组
//内存数组中每个数组有4个元素
int[] [] a = new int[3][4];
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[i].length; j++) {
a[i][j] = i*j;
}
}
System.out.println(Arrays.deepToString(a));
}

通过上述代码我们可以理解为数组a是一个二维数组,也就是说数组a是长度为3的数组,而数组a中又存放了3个长度为4的数组,为每个元素赋值,deepToString则是Arrays工具类为我们提供了打印多维数组的方法。Arrays工具类会在下一小节讲解。

Arrays工具类

Java提供了Arrays.工具类,里面包,含了一些方法,可以直接操作数组。
·int binarySearch(long[] a,long key):使用二分查找法查询key元素值在数组a中出现的索引,如果a数组不包含key元素,则返回负数,调用此方法时要求数组中的元素已经按升序排列。
T[] copyOf(T[] original,int newLength):该方法会把original数组复制成一个新数组,其中length:是新数组的长度,如果length.小于original数组的长度,则新数组就是原数组的前面length个元素,如果lenght大于original数组的长度,则新数组的前面元素就是原数组的所有元素,后面补充默认值,根据数组类型确定
·copyOfRange(T[] original,int from,int to):这个方法与前面的类以,但是这个方法只复制原数组form索引到to索引l的元素。
·boolean equals(type[] a,tyte[] a2):如果数组a和a2长度相等,并且a和a2每个元素也相等则返回true,否则返回false.。
·void fill((long[]a,I):该方法会把a数组的所有元素都赋值为val
·void sort(type[] a):该方法对a数组进行排序
·String toString(type[] a):该方法将一个数组转换成字符串,该方法按顺序吧多个元素连接在一起,元素之间用逗号隔开。
实例一:复制数组1

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
int[] a = {5,7,8};
//如果length小于a数组长度,则只复制前n个元素
int[] b = Arrays.copyOf(a, 2);
for (int i : b) {
System.out.println(i);
}
//如果length大于数组a的长度,多余元素则以默认值填充
int[] c = Arrays.copyOf(a, 5);
for (int i : c) {
System.out.println(i);
}
}

实例二:复制数组2

1
2
3
4
5
6
7
public static void main(String[] args) {
int[] a = {2,5,3,9,6};
int[] b = Arrays.copyOfRange(a, 0, 2);
for (int i : b) {
System.out.println(i);
}
}

复制的元素时包含了form和to索引处的元素。需要注意的是,如果该方法的第三个参数超出了原数组长度,此时并不会抛出异常,会将原数组中所有的元素进行复制,空余的位置以默认值。