本章重点
- 顺序结构
- if分支语句
- switch分支语句
- while循环
- do while循环
- for循环
- 嵌套循环
- 控制循环结构
- 理解数组
- 数组的定义和初始化
- 使用数组元素
- 数组作为引用类型的运行机制
- 多维数组的实质
- 操作数组的工具类
- 数组的实际应用场景
4.1 顺序结构
任何编程语言最常见的程序结构就是顺序结构。如果没有流程控制,Java方法里的语句是一个顺序执行流,从上向下依次执行每条语句。
4.2 分支结构
Java提供了两种常见的分支控制结构:if语句和switch语句。
4.2.1 if条件语句
if语句使用布尔表达式或布尔值作为分支条件来进行控制。if语句有三种形式:
第一种形式:
if(逻辑表达式)
{ 执行语句... }
第二种形式:
if(逻辑表达式)
{ 执行语句... }
else
{ 执行语句... }
第三种形式:
if(逻辑表达式)
{执行语句...}
else if(逻辑表达式)
{执行语句...}...//可以有0个或者多个else if语句
else //最后的else语句也可以省略
{
执行语句...
}
【注意】
(1)if、else、else if后的条件执行体要么是一个花括号括起来的代码块,则这个代码块整体作为条件执行体;要么是以分号为结束符的一行语句,甚至可能是一条空语句,那么就只是这条语句作为条件执行体。如果省略了if条件后条件执行体的花括号,那么if条件只控制到紧跟该条件语句的第一个
分号处。
(2)使用if…else语句时,一定要先处理包含范围更小的情况。
4.2.2 Java 7增强后的switch分支语句
语法格式:
switch(表达式)
{
case condition1:
{
statement(s)
break;
}
case condition2:
{
statement(s)
break;
}
...
case conditionN:
{
statement(s)
break;
}
default :
{
statement(s)
}
}
switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种类型,枚举类型和java.lang.String类型(从Java7才允许),不能是Boolean类型。
4.3 循环结构
循环语句一般包括4个部分:
- 初始化语句(init_statement):一条或多条,在循环开始之前执行。
- 循环条件(test_expression):这是一个boolean表达式。决定是否执行循环体。
- 循环体(body_statement):训话的主体。
- 迭代语句(iteration_statement):
4.3.1 while循环语句
语法格式:
[init_statement]
while(test_expression)
{
statement;
[iteration_statement]
}
4.3.2 do while循环语句
do while循环与while循环的区别在于:while循环是先判断循环条件,如果条件为真则执行循环体;do while循环则先执行循环体,后判断循环条件,如果循环条件为真,则执行下一次循环,否则终止循环。并且,循环条件后必须有一个分号(;)。
语法格式如下:
[init_statement]
do
{
statement;
[iteration_statement]
} while [test_expression];
4.3.3 for循环
语法格式:
for([init_statement];[test_staement;[iteration_statement]])
{
statement
}
- for循环语句和while、do while循环语句的区别:由于while、do while循环的循环迭代语句紧跟循环体,因此如果循环体不能完全执行,如使用continue语句来结束本次循环,则循环迭代语句不会被执行。但for循环的循环迭代语句并没有与循环体放在一起,因此不管是否使用continue语句来结束本次循环,循环迭代语句一样会被执行。
- 建议不要在循环体内改变循环变量(也叫循环计数器)的值。
- for循环圆括号中只有两个分号(;)是必须的,初始化语句、循环条件、迭代语句部分都是可以省略的,如果省略了循环条件,则这个循环条件默认为true,将产生一个死循环。
- 如果把初始化条件定义在循环体之外,循环迭代语句放在循环体内,则类似于while循环,并且还扩大了初始化条件中所定义的变量的作用域。
- 选择循环变量时,习惯选择i、j、k作为循环变量。
4.3.4 嵌套循环
4.4 控制循环结构
4.4.1 使用break结束循环
break用于完全结束一个循环,跳出循环体。break语句不仅可以结束其所在的循环,还可以直接结束其外层循环。此时,需要在break后紧跟一个标签(标签后紧跟英文冒号:),用于标识一个外层循环。Java中的标签只有放在循环语句之前才有作用。如下代码:
public class BreakTest
{
public static void main(String [] args)
{
//外层循环,outer作为标识符
outer:
for(int i=0;i<5;i++)
{
//内层循环
for(int j=0;j<3;j++)
{
System("i的值为:"+i+"j的值为:"+j);
if(j==1)
{
//跳出outer标签所标识的循环
break;
}
}
}
}
}
4.4.2 使用continue忽略本次循环剩下的语句
continue与break的区别:continue只是忽略本次循环剩下的语句,接着开始下一次循环,并不会终止循环;break则是完全终止循环本身。
4.4.3 使用return结束方法
return(其后可以跟变量、常量和表达式)的功能是结束一个方法。
4.5 数组类型
4.5.1 理解数组:数组也是一种类型
Java的数组既可以存储基本类型的数据,也可以存储引用类型的数据,只要所有的数组元素具有相同的类型即可。
如:int是一个基本类型,但int[](这是定义数组的一种方式)就是一种引用类型。创建int[]类型的对象也就是创建数组,需要使用创建数组的方法。
4.5.2 定义数组
Java语言支持两种语法格式定义数组:
type [] arrayName;
type arrayName[];
注意:定义数组时不能指定数组的长度。
4.5.3 数组的初始化
——初始化,就是为数组的数组元素分配内存空间,并为每个数组元素赋初始值。
数组的初始化有两种方式:
1、静态初始化:
语法格式如下:
arrayName=new type[]{element1,element2,element3...};
例如:
int[] intArr;
intArr=new int[] {5,6,8,20};
Object[] objArr;
objArr=new String[] {“Java”,“李刚”};
int[] a={5,6,7,9};
只有在定义数组的同时执行数组初始化才支持使用简化的静态初始化。
2、动态初始化
arrayName=new type[length];
//数组的定义和初始化同时完成,使用动态初始化语法
int[] prices = new int[5];
//数组的定义和初始化同时完成,初始化数组时元素的类型是定义数组时元素类型的子类
Objec[] books = new String[4];
注意:不要同时使用静态初始化和动态初始化,也就是说,不要在进行数组初始化时,既指定数组的长度,也为每个数组元素分配初始值。
4.5.4 使用数组
数组最常用的用法就是访问数组元素,包括:对数组元素进行赋值、取出数组元素的值。
例如:
//对动态初始化后的数组元素进行赋值
books[0]="疯狂Java讲义";
books[1]="轻量级Java EE企业应用实战";
//使用循环输出books数组的每个数组元素的值
for(int i=0;i<books.length;i++)
{
System.out.println(books[i]);
}
4.5.5 foreach循环(从Java5之后)
使用foreach循环遍历数组和集合元素时,无需获得数组和集合长度,无需根据索引来访问数组元素和集合元素,foreach循环自动遍历数组和集合的每个元素。
语法如下:
for(type variableName : array | collection)
{
//variableName自动迭代访问每个元素...
}
例如:
public class ForEachTest
{
public static void main(String[] args)
{
String[] books = {"轻量级Java EE企业应用实战","疯狂Java讲义","疯狂Andriod讲义"};
//使用foreach循环遍历数组元素
//其中book将会自动迭代每个数组元素
for(String book:books)
{
System.out.println(book);
}
}
}
注意:使用foreach循环迭代数组元素时,并不能改变数组元素的值,因此不要对foreach循环变量进行赋值。
——foreach循环和普通循环不同之处是:它无须循环条件,无须循环迭代语句。foreach循环自动迭代数组的每个元素,当每个元素都被迭代一次后,foreach循环自动结束。
4.6 深入数组
——数组是一种引用数据类型,数组引用变量只是一个引用,数组元素和数组变量在内存里是分开存放的。
4.6.1 内存中的数组
——数组引用变量只是一个引用,如果希望在程序中访问数组对象本身,则只能通过这个数组的引用变量来访问它。
——实际的数组对象被存储在堆(heap)内存中,如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈(stack)内存中。
public class ArrayInRam
{
public static void main(String[] args)
{
//定义并初始化数组,使用静态初始化
int[] a={5,7,20};
//定义并初始化数组,使用动态初始化
int[] b=new int[4];
//输出b数组的长度
System.out.println("b数组的长度为:"+b.length);
//循环输出a数组的元素
for(int i=0,len=a.length;i<len;i++)
{
System.out.println(a[i]);
}
//输出b数组的元素
for(int i=0,len=b.length;i<len;i++)
{
System.out.println(b[i]);
}
//因为a是int[]型,b也是int[]型,所以可以将a的值赋值给b
//也就是让b引用指向a引用指向的数组
b=a;
//再次输出b数组的长度
System.out.println("b数组的长度为:"+b.length);
}
}
4.6.2 基本类型数组的初始化
对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中。
4.6.3 引用类型数组的初始化
4.6.4 没有多维数组
Java语言里提供了支持多维数组的语法,如果从数组底层的运行机制上来看,没有多维数组。
Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存。数组元素的类型也可以是引用,如果数组元素的引用再次指向真实的数组内存,这种情形看上去像多维数组。
由前面定义一维数组的语法,可以导出定义二维数组的语法:
type[][] arrName;
把二维数组当成一维数组进行初始化,语法如下:
arrName = new type[length][];
上面的语法相当于定义了length个type[]类型的变量。
下面的程序示范了如何把二维数组当成一维数组处理。
public class TwoDimensionTest
{
public static void main(String[] args) {
//定义一个二维数组
int[][] a;
//把a当成一个一维数组进行初始化,初始化a是一个程度为4的数组
//a数组的数组元素又是引用类型
a=new int[4][];
//把a数组当成一维数组,遍历a数组的每个数组元素
for (int i=0,len=a.length;i<len;i++)
{
System.out.println(a[i]);
}
//初始化a数组的第一个元素
a[0]=new int[2];
//访问a数组的第一个元素所指的第二个元素
a[0][1]=6;
//a数组的第一个元素是一个一维数组,遍历这个一维数组
for (int i=0,len=a[0].length;i<len;i++)
{
System.out.println(a[0][i]);
}
}
}
结论:二维数组是一维数组,其数组元素是一维数组;三维数组也是一维数组,其数组元素是二维数组…从这个角度来看,Java语言里没有多维数组。
4.6.5 Java 8 增强的工具类:Arrays
Java提供的Arrays类里包含的一些static修饰的方法可以直接操作数组,这个Arrays类里包含了几个static修饰的方法(static修饰的方法可以直接通过类名调用)。
(注意:Arrays类处于java.util包下,为了在程序中使用Arrays类,必须在程序中导入java.util.Arrays类。)
(1)、int binarySearch(type[] a,type key):使用二分法查询key元素值在数组a中出现的索引;如果a数组不包含key元素值,则返回负数。
调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果。
(2)、int binarySearch(type[] a,int fromIndex,int toIndex,type key):与前一个方法类似。但只搜索a数组中fromIndex到toIndex索引的元素。
调用该方法时要求数组中元素已经按升序排列,这样才能得到正确结果。
(3)、type[] copyOf(type[] original,int length):该方法将把original数组复制成一个新数组,其中length是新数组的长度。
(4)、type[] copyOfRange(type[] original,int from,int to):只复制数组original的from索引到to索引的元素。
(5)、boolean equals(type[] a1,type[] a2):如果a1数组和a2数组的长度相等,而且他们的数组元素也一一相同,该方法将返回true。
(6)、void fill(type[] a,type val):该方法将会a数组里的所有元素赋值为val。
(7)、void fill(type[] a,int fromIndex,int toIndex,type val):该方法仅仅将a数组的fromIndex到toIndex索引的数组元素赋值为val。
(8)、void sort(type[] a):该方法对数组a的数组元素进行排序。
(9)、void sort(type[] a,int fromIndex,int toIndex):该方法仅仅将a数组的fromIndex到toIndex索引的数组元素进行排序。
(10)、String toString(type[] a):该方法将一个数组转换成一个字符串。该方法按顺序把多个数组元素连缀在一起,多个数组元素使用英文逗号(,)和空格隔开。
下面的程序示范Arrays类的用法。
import java.util.Arrays;
/*程序名称:ArraysTest.java
*程序功能:示范Arrays类的用法
*日 期:2018-12-29
*/
public class ArraysTest
{
public static void main(String[] args) {
//定义一个a1数组
int[] a1=new int[]{3,4,5,6};
int[] a2=new int[]{3,4,5,6};
//数组a1和数组a2的长度相等,每个元素一次相等,将输出true
System.out.println("a1数组与a2数组是否相等:"+Arrays.equals(a1,a2));
//通过复制a1数组,生成一个新的数组b
int[] b=Arrays.copyOf(a1,6);
System.out.println("数组a1与数组b是否相等:"+Arrays.equals(a1,b));
//输出数组b的元素,将输出{3,4,5,6,0,0}
System.out.println("b数组的元素为:"+Arrays.toString(b));
//将数组b的第三个元素(包括)到第五个元素(不包括)赋值为1
Arrays.fill(b,2,4,1);
//输出数组b的元素,将输出{3,4,1,1,0,0}
System.out.println("赋值后b数组的元素为:"+Arrays.toString(b));
//对数组b进行排序,将输出{0,0,1,1,3,4}
Arrays.sort(b);
System.out.println("排序后的b数组元素为:"+Arrays.toString(b));
}
}
Java 8增强了Arrays类的功能,下面是Java 8为Arrays类增强的工具方法。
(1)、void parallelPrefix(xxx[] array,XxxBinaryOperator op):该方法使用op参数指定的计算公式计算得到的结果作为新的元素。op计算公式包括left、right两个形参,其中left代表数组中前一个索引处的元素,right代表数组中当前索引处的元素,当计算第一个新数组元素时,left的值默认为1。
(2)、void parallelPrefix(xxx[] array, int fromIndex, int toIndex ,XxxBinaryOperator op):与上一个方法的区别:该方法仅重新计算fromIndex到toIndex索引的元素。
(3)、void setAll(xxx[] array ,IntToXxxFunction generator):该方法使用指定的生成器(generator)为所有数组元素设置值,该生成器控制数组元素的值得生成算法。
(4)、void parallelSetAll(xxx[] array ,IntToXxxFunction generator):该方法与一个方法的区别:只是该方法增加了并行能力,利用多CPU并行来提高性能。
(5)、void parallelSort(xxx[] a):该方法与Array类以前就有的sort()方法相似,只是该方法增加了并行能力,利用多CPU并行来提高性能。
(6)、void parallelSort(xxx[] a, int fromIndex, int toIndex):该方法与上一个方法相似,区别是该方法只对fromIndex到toIndex索引的元素进行排序。
(7)、Spliterator.OfXxx.spliterator(xxx[] array):将该数组的所有元素转换成对应的Spliterator对象。
(8)、Spliterator.OfXxx.spliterator(xxx[] array, int startInclusive,int endExclusive):该方法与上一个方法类似,区别是该方法仅转换startInclusive到endExclusive索引的元素。
(9)、XxxStream stream(xxx[] array):该方法将数组转换为Stream,Stream是Java 8的新增的流式编程的API。
(10)、XxxStream stream(xxx[] array, int startInclusive,int endExclusive):该方法与上一个方法类似,区别是该方法仅将startInclusive到endExclusive索引的元素转换为Stream。
上面的方法中,所有以parallel开头的方法都表示该方法可利用CPU并行的能力来提高性能。xxx代表不同的数据类型,比如处理int[]型数组时应将xxx换成int。
下面的程序示范了Java 8为Arrays类新增的方法。
import java.util.Arrays;
/*程序名称:ArraysTest2.java
*程序功能:示范Arrays类的用法
*日 期:2018-12-29
*/
public class ArraysTest2{
public static void main(String[] args) {
int[] arr1=new int[] {3,-4,25,16,30,18};
//对数组arr1进行并发排序
Arrays.parallelSort(arr1);
System.out.println(Arrays.toString(arr1));
int[] arr2=new int[] {3,-4,25,16,30,18};
Arrays.parallelPrefix(arr2,new IntBinaryOperator(){
//left代表数组中前一个索引处的元素,计算第一个元素时,left为1
//right代表数组中当前索引处的元素
public int applyAsInt(int left,int right)
{
return left*right;
}
});
//输出数组arr2
System.out.println(Arrays.toString(arr2));
//定义数组arr3,并动态初始化
int[] arr3=new int[5];
Arrays.parallelSetAll(arr3,new IntUnaryOperator(){
operand代表正在计算的元素索引
public int applyAsInt(int operand)
{
return operand*5;
}
});
System.out.println(Arrays.toString(arr3));
}
}
4.6.6 数组的应用举例
以下两个例子和同系列《疯狂Python讲义》中例子相同!
1、将一个浮点数转换成人民币读法字符串。
import java.util.Arrays;
/*
*程序名称:Num2Rmb.java
*程序功能:(1)将一个浮点数分拆为整数部分和小数部分
* (2)将一个数字字符串转换为汉字字符串
*日 期:2018-12-30
*/
public class Num2Rmb
{
private String[] hanArr={"零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};
private String[] unitArr={"拾","佰","仟","万","拾万","佰万","仟万","亿","拾亿","佰亿","仟亿"};
/**
* [功能:把一个浮点数分拆成整数部分和小数部分字符串]
* @param num [需要被分拆的浮点数]
* @return 分解出来的整数部分和小数部分,第一个数组元素是整数部分,第二个数组元素是小数部分
*/
private String[] divide(double num)
{
//将一个浮点数强制类型转换为long型,即得到它的的整数部分
long zheng=(long)num;
//浮点数减去整数部分,得到小数部分,小数部分乘以100后再取整得到2位小数
long xiao=Math.round((num-zheng)*100);
//下面用两种方法把整数转换为字符串
return new String[] {zheng+"",String.valueOf(xiao)};
}
/**
* [功能:把一个四位的数字字符串变成汉字字符串]
* @param numStr [需要被转换的四位数字字符串]
* @return 四位的数字字符串被转换为汉字字符串
* 不能处理两个连零、最高位和最低位是零的情况
*/
private String toHanStr(String numStr){
String result="";
int numLen=numStr.length();
//一次遍历数字字符串的每一位数字
for (int i=0;i<numLen;i++)
{
//把char型数字转换为int型数字,因为它们的ASCII码值正好相差48
//因此把char型数字减去48得到int型数字。
int num=numStr.charAt(i)-48;
//如果不是最后一位数字,而且数字不是零,则需要添加单位(千百十)
if (i!=numLen-1 && num!=0)
{
result += hanArr[num]+unitArr[numLen-2-i];
}
//否则不要添加单位
else
{
result +=hanArr[num];
}
}
return result;
}
public static void main(String[] args)
{
Num2Rmb nr=new Num2Rmb();
//测试把浮点数分解成整数部分和小数部分
System.out.println(Arrays.toString(nr.divide(236711125.123)));
//测试把一个四位的数字字符串转换为汉字字符串
System.out.println(nr.toHanStr("61000"));
}
}
2、利用二维数组实现五子棋。
import java.io.BufferedReader;
import java.io.InputStreamReader;
/*
*程序名称:Gobang.java
*程序功能:控制台五子棋的初步功能
*编写日期:2018-12-30
*/
public class Gobang
{
//定义棋盘的大小
private static int BOARD_SIZE=15;
//定义一个二维数组充当棋盘
private String[][] board;
public void initBoard()
{
//初始化棋盘数组
board=new String[BOARD_SIZE][BOARD_SIZE];
//把每个元素赋值为"┼",用于控制台上输出棋盘
for (int i=0;i<BOARD_SIZE ;i++ )
{
for (int j=0;j<BOARD_SIZE ;j++ )
{
board[i][j]="┼";
}
}
}
//在控制台输出棋盘的方法
public void printBoard()
{
//打印每个数组元素
for (int i=0;i<BOARD_SIZE;i++)
{
for (int j=0;j<BOARD_SIZE;j++)
{
//打印数组元素后不换行
System.out.print(board[i][j]);
}
//打印完一行数组元素后输出一个换行符
System.out.print("\n");
}
}
public static void main(String[] args) throws Exception
{
Gobang gb=new Gobang(); //实例化一个Gobang类
gb.initBoard(); //初始化棋盘
gb.printBoard(); //打印15*15棋盘
//用于获取键盘输入的方法
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String inputStr=null;
//每当在键盘上输入一行内容后按回车键,刚输入的内容被br读取到
while((inputStr=br.readLine())!=null)
{
//将用户输入的字符串以(,)为分隔符,分隔成两个字符串
String[] posStrArr=inputStr.split(",");
//将两个字符串转换为用户下棋的坐标
int xPos=Integer.parseInt(posStrArr[0]);
int yPos=Integer.parseInt(posStrArr[1]);
//把对应的数组元素赋值为"●"
gb.board[yPos-1][xPos-1]="●";
/*
电脑随机生成2个整数,作为电脑下棋的坐标,赋给board数组
还涉及
1.坐标的有效性,只能是数字,不能超出棋盘范围
2.下的棋子的点,不能重复下棋
3.每次下棋后,需要扫描谁赢了
*/
gb.printBoard();
System.out.println("请输入您下棋的坐标,应以x,y的格式:");
}
}
}
以上两个程序书中都不完善,我想了很久也没有补充完整。哎!真是笨啊!
4.7 本章小结
本章主要介绍了Java的两种程序流程结构:分支结构和循环结构。本章详细讲解了Java提供的if和switch分支结构,并详细介绍了Java提供的while、do while和for循环结构,以及详细分析了三种循环结构的区别和联系。数组也是本章的重点,本章通过实例程序讲解了数组的定义、初始化和使用等基本知识,并结合示意图深入分析了数组在内存中的运行机制、数组引用变量和数组之间的关系、多维数组的实质等内容。