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 | switch (expression){ |
switch分支语句的执行是先对expression求值,然后依次匹配case,如果没有与之匹配的,则执行default
分支嵌套
如果把一个分支结构放进另一个分支结构中,这种情况叫做分支嵌套,分支嵌套可以是if中嵌套switch,switch中嵌套if,也可以是if互相嵌套或者switch自身嵌套。例如,计算一个年份是否是闰年。
1 | public static void main(String[] args) { |
循环结构
循环结构是指在满足某个循环条件的情况下,反复执行同一段代码,直到不满足循环条件为止。被循环执行的代码叫做循环体。当反复执行循环体时,要在适当的时机修改循环条件,从而结束循环,否则循环会一直进行下去,形成死盾环。一个恰当的盾坏结构应该包含以下4个组成部分:
1.初始化语句:一个或多个语句,这些语句用来完成一些初始化工作,在循环开始之前执行
2.循环条件:循环条件是一个布尔表达式,该表达式决定是否执行循环体
3.循环体:这部分是循环的主题,如果循环条件允许,该代码块将被反复执行,如果这个代码块只有一条语句,则代码块的花括号可以管略。
4.迭代语句:这部分在一次循环体执行结束后执行,在循环条件求值前执行,通常用于控制循环条件中的变量,使得循环在合适的时候结束。
for循环
1 | for([init_statment];[test_exression];[iteration_statement]){ |
初始化语句只在循环条件开始前执行依次,对于for循环而言,循环条件总比循环体要多执行一次,因为最后一次执行循环条件返回false,将不再执行循环体。
注意:除非特殊情况尽量不要在循环体内修改循环变量的值。
for循环圆括号中只有两个分号是必须的,初始化语句、循环条件、迭代语句部分都是可以省略的,如果省略了循环条件,则这个循环条件默认为true,将产生死循环。例如下面的代码:
1 | public class EndlessForLoop { |
while循环
1 | [init_statement] |
while循环每次执,行循环体,之前,先对test_expression循环条件求值,如果循环条件为true,则执行循环体部分。从上述伪代码中来看,iteration_statment位于循环体的最后,因此只有当循环体成功执行完成时,while循环才会执行iteration_statmenti语句。
do while循环
do while循环与while循环的区别在于:while循环是先判断循环条件,如果条件为真则执行循环体;而do while循环则新执行循环体,然后才判断循环条件,如果循环条件为真,则执行下一次循环,否则终止循环。do while循环语法格式如下:
1 | [init_statement] |
注意:do while循环的循环条件必须有一个分号,该分号代表该循环结构结束。
1 | public static void main(String[] args) { |
++i=1执行后的值为1,此时该表达式结果为false,不执行循环体,而do while循环中循环条件直接是false,但是此时仍l旧执行了循环体,k的值成为了1。
控制循环结构
Java提供了continue和break控制循环结构
使用break结束循环
break:用于完全结束一个循环,跳出循环体,不管哪种循环,一旦循环体遇到break,系统将完全结束该循环,开始执行循环结构以后的代码。
1 | public static void main(String[] args) { |
当i=7时,直接跳出循环
使用continue跳过此次循环
continue的功和break有点类似,区别是continue只是跳出此次循环,继续执行剩下的循环,并不是完全终止循环。
1 | public static void main(String[] args) { |
循环嵌套
如果把一个循环放进另一个循环体内,那么就可以形成循环嵌套,循环嵌套可以是上述3种循环的任意嵌套,例如:for循环和while循环的相互嵌套,while循环和do while循环的相互嵌套,也可以是while循环和do while循环的相互嵌套。
当程序遇到循环嵌套是,如果外层循环的循环条件允许,则开始执行外层循环的循环体,而内层循环将被外层循环的循环体来执行,只是内层循环需要反复执行自己的循环体而已。当内层循环执行结束,且完成循环的循环体执行结束时,在再次计算外层循环的循环条件,决定是否再次执行外层循环的循环体。
示例:打印等腰三角形
1 | public static void main(String[] args) { |
示例:冒泡排序
1 | public static void main(String[] args) { |
示例:二分查找
1 | public static void main(String[] args) { |
循环嵌套中的break
break语句仅结束其所在的循环,例如在双层循环嵌套中,只结束内层循环,而不能结束外层盾环。
1 | public static void main(String[] args) { |
运行上述代码,我们可以发现break只是结束了内层自己所在的循环,外层循环并没有被结束,仍旧在继续循环。那么,如何让break结束外层循环呢,此时就要在break后面紧跟一个标签了,这个标签用于表示一个外层盾环。Java中的标签就是一个紧跟着冒号的标识符,与其他语言不同的是,Java中的标签只有放在循环语句前才有作用。
1 | public static void main(String[] args) { |
程序从外层循环进入内层循环,当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 | type[] arrayName; |
int类型是一个基本类型,而int[]则是一个引用类型。
数组初始化一旦完成,数组在内存中所占的空间将被固定下来,长度就不会改变,若长度改变,则一定
数组是一种引用类型的变量,当仅仅是定义了数组后,仅仅是定义了一个引用变量,这个变量还未指向任何有效内存,此时整个数组还不能使用,只有对数组初始化后才能使用。
数组的初始化
Java中数组必须先初始化,然后才可以使用。所谓初始化,就是为数组的元素分配内存空间,并为每个元素赋初始值。
数组的初始化有两种方式:
1.静态初始化:初始化时由程序员指定每个元素的初始值。由系统觉定数组的长度
2.动态初始化:初始化是程序员指定数组长度,由系统为元素分配初始值。
静态初始化语法格式如下:
1 | arrayName = new type[]{e1,e2,e3} |
动态初始化只指定数组的长度,由系统为每个元素指定初始值。动态初始化的语法格式如下:
arrayName new type[length];
在上面的语法中,需要指定一个int类型的length参数,该参数指定了数组的长度,也就是可以容纳数组元素的个数,与静态初始化类型,此处的
type必须与定义数组时使用的type类型相同。动态初始化语法格式如下:
1 | int[] ary = new int[3]; |
执行动态初始化时,开发者只需指定数组的长度,即为每个元素指定所需的内存空间,系统将负责为这些数组元素分配初始值。指定初始值时,系统按如下规则分配初始值:
·数组元素的类型是基本类型中的整型(byte,short,int,Iong),则数组元素的初始值为0;
·数组元素的类型是基本类型中的浮点型(float,double),则数组元素的值是0.0;
·数组元素的类型是基本类型中的字符型(char),则数组元素的值是’\u0000’:
·数组元素的类型是基本类型中的布尔型(boolean),则数组元素的默认值是false。
·数组元素的类型是基本类型中的引用类型,则数组元素的默认值是null。
使用数组
1 | //定义一个整型数组,并对其动态初始化,此时数组中的元素都是默认值 |
遇到数组索引时,需要考虑java.lang.ArraryIndexOutOfBoundsException(数组越界异常)。
for循环遍历数组
1 | public static void main(String[] args) { |
foreach遍历数组
Java5以后,Java提供了更简洁遍历数组的方式:foreach循环。使用foreach遍历数组和集合元素时,无需获得数组和集合长度,无需根据索引来访问数组元素。
foreach我们可以理解为增强for循环,不需要开发者根据索引去数组中取值,系统会自动从数组a中取到每一个元素,并赋值给i,只需要打印i即可,这种方式也有效的避免了数组下标越界异常。
1 | public static void main(String[] args) { |
深入数组
内存中的数组
数组引用变量只是一个引用,这个引用变量可以指向任何有效内存,只有当该引用指向有效内存后,才可以通过该数组变量访问数组。
1 | int [] a; |
此时只是定义了一个数组类型的变量,该变量没有指向任何有效内存,如果此时访问数组元素时,将会引发空指针异常。
与所有引用变量相同的是,引用变量是访问真实对象的根本方式,也就是说,如果希望在程序中访问数组对象本身,只能通过数组的引用变量来访问。
实际的数组对象被存储在堆内存中,如果引用该数组对象的数组引用变量是一个局部变量,那么它将被存储在栈内存中。数组在内存中的示意图如下图所示:
数组的长度不能被改变
1 | public static void main(String[] args) { |
运行上述代码,发现数组a的长度发生了变化
当代码执行完第10行时,此时数组a和数组b的内存示意图如下:
当代码执行第11行时,此时内存中发生如下变化:
从图示中可以看出,此时a并不指向原来的内存空间,而是和b一样指向了相同的内存空间,而原来a指向的内存还在内存中,也就是说变的仅仅是变量a指向的内存空间,而不是数组长度,a原来指向的数组长度还是4,并没有发生变化
数组一旦被初始化后长度不可变,如果发生了变化,一定是指向了新的数组。
多维数组
Java语言里提供了多维数组的支持,但是实际上并不存在多维数组,例如二维数组,完全可以认为是数组类型的数组:因为Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实数组的内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,此时就产生了多维数组,换句话说,多维数组本质都是一维数组。我们可以把数组当成一种数据类型,可以理解为数组类型的数组,二维数组的语法如下:
1 | type[] [] a = new type[length1][length2] |
下面通过实例来定义二维数组,并对其进行遍历
1 | public static void main(String[] args) { |
通过上述代码我们可以理解为数组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 | public static void main(String[] args) { |
实例二:复制数组2
1 | public static void main(String[] args) { |
复制的元素时包含了form和to索引处的元素。需要注意的是,如果该方法的第三个参数超出了原数组长度,此时并不会抛出异常,会将原数组中所有的元素进行复制,空余的位置以默认值。