编程语言 | Java - 04.流程控制和数组
2018-03-28 20:49
260 查看
[align=center] 流程控制一共有三种结构[/align]
1、顺序结构。从上到下依次执行,中间没有任何判断和跳转。
2、分支结构。根据条件来选择性地执行某段代码。
3、循环结构。根据循环条件重复执行某段代码。
如果main方法的多行代码之间没有任何流程控制,则程序总是从上向下依次执行,排在前面的代码先执行,排在后面的代码后执行。这意味着:如果没有流程控制,Java方法里的语句是一个顺序执行流,从上向下依次执行每条语句。
if语句使用布尔表达式或者布尔值作为分支条件来进行分支控制。
[align=center] 第一种语法格式[/align]
程序代码如下:IfTest1.java
[align=center] 第二种语法格式[/align]
程序代码如下:IfTest2.java
[align=center] 第三种语法格式[/align]
程序代码如下:IfTest3.java
注意点:
1、如果条件执行体只有一行代码,Java允许省略条件执行体的花括号。
规则:如果省略了花括号,if条件只控制到第一个分号前面。
建议:即使条件执行体只有一行语句,也用花括号。
程序代码如下:If省略花括号1.java
程序代码如下:If省略花括号2.java
2、else本身就是条件。当我们有多个else if块时,建议,先处理范围小的条件。
程序代码如下:IfError.java
[align=center] switch语句
当等于某个值时,才执行一段代码。[/align]
switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种整数类型、枚举类型和java.lang.String类型(从Java7才允许),不能是boolean类型。
语法格式:
程序代码如下:SwitchTest.java
注意点:
省略break后,执行流程时,switch表达式的值,依次与每个case块的值进行比较。
当遇到一个相等的值之后,将继续执行余下所有的可执行性代码。在直到遇到break语句之前,不会与case的值进行比较,而是直接执行代码。
建议:每次写了case之后,在写其他代码之前,立即写break。
程序代码如下:Switch省略break.java
程序代码如下:活动计划.java
循环语句可能包含如下4个部分:
1)初始化语句:一条或者多条语句,用于完成一些初始化工作,在循环开始前执行。
2)循环条件:这是一个boolean表达式,决定是否执行循环体。
3)循环体:循环的主体,条件允许则被重复执行。
4)迭代语句:在一次循环体执行后,对循环条件求值之前执行,通常控制循环条件中的变量,使得循环在合适的时候结束。
[align=center] while循环[/align]
程序代码如下:WhileTest.java
如果循环体只有一条代码,可以省略花括号。
规则:如果省略了花括号,while条件只控制到第一个分号前面。
建议:即使循环体只有一行代码,也用花括号。
使用while循环时,一定要保证循环条件有变成false的时候,否则这个循环将成为一个死循环,永远无法结束这个循环。
程序代码如下:WhileError1.java
程序代码如下:WhileError2.java
[align=center] do while循环[/align]
这个与while相比,把循环放在前面,无论真假都会先执行一次,如果条件为true,执行下一次循环。对于do while来说,至少都会执行一次。与while循环不同的是,do while循环的循环条件后必须有一个分号,才表明循环结束。
程序代码如下:DoWhileTest.java
[align=center] for 循环[/align]
程序代码如下:ForTest1.java
程序代码如下:ForTest2.java
程序代码如下:ForTest3.java
程序代码如下:ForTest4.java
“初始化语句”可以省略。如果有“初始化语句”,程序执行for循环时,先执行循环的“初始化语句”,且只在循环开始前执行一次,允许同时指定多个初始化语句;
“返回boolean值的表达式”可以省略。即循环条件语句,每次执行循环体之前,先计算该值,如果返回true,则执行循环体,如果省略,意味着他的值永远是true;
“每次循环体执行完后执行的代码”可以省略。即循环迭代语句,这里并没有与循环体放在一起,因此即使在执行循环体时遇到continue语句结束本次循环,循环迭代语句也一样会得到执行,如果不省略,将在每次循环体执行完成之后,开始下一次循环体之前,执行该代码。
只要两个分号中间的表达式返回true,程序将执行循环体。
条件表达式的计算次数总是比循环体执行次数多一次。
建议:不要在循环体内修改循环变量(也叫循环计数器)的值,否则会增加程序出错的可能性。万一程序真的需要访问、修改循环变量的值,建议重新定义一个临时变量,先将循环变量的值赋给临时变量,然后对临时变量的值进行修改。
[align=center] for each循环[/align]
循环嵌套:
在循环里面,再放置循环。把内层循环当成外层循环的一条语句即可。
程序代码如下:循环嵌套.java
完全结束一个循环本身[/align]
程序代码如下:BreakTest.java
[align=center] continue
停止当前循环体的执行(忽略continue后的语句),开始下一次循环体。[/align]
如果continue位于循环体的最后部分,他就是多余的。
程序代码如下:ContinueTest.java
[align=center] return
结束整个方法。遇到return,方法就会结束执行。[/align]
程序代码如下:ReturnTest.java
[align=center] break、continue后可以紧跟标号
带标号的break用于结束标号所标识的循环。[/align]
带标号的continue用于忽略标号所标识的循环后面剩下的语句。
程序代码如下:BreakLabelTest.java
程序代码如下:ContinueLabelTest1.java
程序代码如下:ContinueLabelTest2.java
如果程序中需要用到多个相同类型的变量,就可以考虑把它们集中定义成一个数组。每个数组元素,就相当于一个普通的变量。借助于数组,我们可以非常方便地去管理、访问每个数组元素(相当于一个变量)
数组也是一种类型,Java的数组要求所有的数组元素具有相同的数据类型。因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。
[align=center] 数组类型[/align]
Java数据类型可以分为:
基本类型 – 8个
引用类型 – 类/接口/数组
数组类型也是一种数据类型,它本身是一种引用类型。既可用于创建变量,也可用于进行强制类型转换。
int -> int[ ] -> 整型数组
double -> double [ ] -> double型数组
…
string -> string[ ] -> string型数组
int[ ] -> int[ ][ ] -> 新的数组类型。整型数组
int[ ][ ] -> int[ ][ ][ ] -> 新的数组类型。整型数组
int[ ]就是一种数据类型,与int类型、String类型相似,一样可以使用该类型来定义变量,也可以使用该类型进行类型转换等。使用int[ ]类型来定义变量、进行类型转换时与其它普通类型没有任何区别。int[ ]类型是一种引用类型,创建int[ ]类型的对象也就是创建数组,需要使用创建数组的语法。
变量分为两种:
1、直接在该变量中存放实际的变量值,这就是Java的基本类型的变量。
2、在变量中存放只是内存的地址值,这就是所谓的引用变量。
数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),只不过它里面存的值是内存地址,这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用。
定义数组:定义时不能指定数组的长度。
程序代码如下:ArrayTypeTest.java
Java数组的特征:
Java语言是强类型, 一个数组里只能存储一种数据类型的数据。
Java语言是静态的, Java的数组一旦被初始化后,它的长度是固定的。
数组的初始化:
数组变量只是一个引用,必须让它指向有效的内存之后才能使用。初始化就是为数组的数组元素分配内存空间,并为每个数组元素赋初始值。
一旦为数组的每个数组元素分配了内存空间,每个内存空间里存储的内容就是该数组元素的值,即使这个内存空间存储的内容是空,这个空也是一个值(null)。不管以哪种方式来初始化数组,只要为数组元素分配了内存空间,数组元素就具有了初始值。初始值的获得有两种形式:一种由系统自动分配;另一种由程序员指定。
静态初始化:只指定数组的元素,让系统来决定数组的长度。
动态初始化:只指定数组的长度,让系统来决定数组的元素的值。
程序代码如下:数组测试.java
如果数组的元素是基本类型(整数型/浮点型/布尔型/字符型),那么所有数组元素的值都是0 / 0.0 / false / ‘\u0000’。
如果数组的元素是引用类型(类/接口/数组),那么所有数组元素的值都是null。
不能同时用静态初始化跟动态初始化
一旦数组的初始化完成,接下来的每个数组元素就可以当成普通变量使用了。
[align=center] 使用数组[/align]
每个数组元素相当于一个变量,该变量的类型,就是数组类型去掉一个方括号。
数组有一个length属性,用于返回该数组的长度。
程序代码如下:数组测试.java
[align=center] 遍历数组[/align]
1)可以依次根据每个数组元素的索引来进行遍历。
2)可以使用foreach循环来遍历。
注意:foreach循环时。不要对循环变量赋值。
如果需要在遍历的时候对数组元素进行赋值,那就应该根据数组元素的索引来进行遍历。
程序代码如下:ForEachTest.java
数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用指向有效内存后,才可通过该数组变量来访问数组元素。
与所有引用变量相同的是,引用变量是访问真实对象的根本方式。也就是说,如果希望在程序中访问数组对象本身,则只能通过这个数组的引用变量来访问它。
实际的数组对象被存储在堆内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈内存中。
数组在内存中的存储示意图
如果需要访问上图所示的堆内存中的数组元素,则程序中只能通过p[index]的形式实现。也就是说,数组引用变量是访问堆内存中数组元素的根本方式。
程序代码如下:PriArrayTest.java
当数组对象的引用变量被销毁之后,这个数组对象并不一定会被回收(它在堆内存),它不会随着数组变量被回收。
只有当没有引用变量引用这个数组对象时,系统才会去回收数组对象。
[align=center] 为什么有栈内存和堆内存之分?[/align]
当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁。因此,所有在方法中定义的局部变量都是放在栈内存中的;在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(在方法的参数传递时很常见),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。
如果堆内存中数组不再有任何引用变量指向自己,则这个数组将变成垃圾,该数组所占的内存将会被系统的垃圾回收机制回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋为null,也就切断了数组引用变量和实际数组之间的引用关系,实际的数组也就成了垃圾。
只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作会让人产生数组的长度可变的错觉。
程序代码如下:ArrayInRam.java
运行上面代码后,将可以看到先输出b数组的长度为4,然后依次输出a数组和b数组的每个数组元素,接着会输出b数组的长度为3.看起来似乎数组的长度是可变的,但这只是一个假象。
必须牢记:定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。下面将结合示意图来说明上面程序的运行过程。
定义并初始化a、b两个数组后的存储示意图
当程序定义并初始化了a、b两个数组后,系统内存中实际上产生了4块内存区,其中栈内存中有两个两个引用变量:a和b;对内存中也有两块内存区,分别存储a和b引用所指向的数组本身。此时可以看到a引用和b引用各自所引用的数组对象,a变量所引用的数组长度为3,b变量所引用的数组长度为4.
让b引用指向a引用所指向数组后的存储示意图
当执行b = a时,系统将会把a的值赋给b,a和b都是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向a所指向的地址。当执行b = a后,堆内存中的第一个数组具有了两个引用;a变量和b变量都引用了第一个数组。此时第二个数组失去了引用,变成垃圾,只有等待垃圾回收机制来回收它——但它的长度依然不会改变,知道它彻底消失。
[align=center] 基本类型数组的初始化[/align]
对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中。
下面程序定义了一个int[ ]类型的数组变量,采用动态初始化的方式初始化了该数组,并显式为每个数组元素赋值。
程序代码如下:PrimitiveArrayTest.java
定义iArr数组变量后的存储示意图
执行第一行代码int[ ] iArr;时,仅定义了一个数组变量。
执行int[ ] iArr;后,仅在栈内存中定义了一个空引用(就是iArr数组变量),这个引用并未指向任何有效的内存,当然无法指定数组的长度。
动态初始化iArr数组后的存储示意图
当执行iArr = new int[5];动态初始化后,系统将负责为该数组分配内存空间,并分配默认的初始值:所有数组元素都被赋为值0,。
显式指定每个数组元素值后的存储示意图
此时iArr数组的每个数组元素的值都是0,当循环为该数组的每个数组元素依次赋值后,此时每个数组元素的值都变成程序显式指定的值。每个数组元素的值直接存储在对应的内存中。操作基本类型数组的数组元素时,实际上相当于操作基本类型的变量。
引用类型数组的初始化
. 程序代码如下:ReferenceArrayTest.java
定义一个students数组变量后的存储示意图
执行Person[ ] student;时,仅仅在栈内存定义了一个引用变量,也就是一个指针,这指针并未指向任何有效的内存区。即在栈内存中定义了一个student变量,它仅仅是一个引用,并未指向任何有效的内存。
动态初始化students数组后的存储示意图
直到执行初始化,本程序对student数组执行动态初始化,动态初始化由系统为数组元素分配默认的初始值:null,即每个数组元素的值都是null。
student数组的两个数组元素都是引用,而且引用并未指向任何有效的内存,因此每个数组元素的值都是null。这意味着依然不能直接使用student数组元素,因为每个数组元素都是null,这相当于定义了两个连续的Person变量,但这个变量还未指向任何有效的内存区,所以这两个连续的Person变量(student数组的数组元素)还不能使用。
创建两个Person实例后的存储示例图
接着的代码定义了zhang和lee两个person实例,定义这两个实例实际上分配了4块内存,在栈内存中存储了zhang和lee两个引用变量,还在堆内存中存储了两个Person实例。
为数组元素赋值后的存储示意图
此时student数组的两个数组元素依然是null,知道程序依次将zhang赋给student数组的第一个元素,把lee赋给student数组的第二个元素,student数组的两个数组元素将会指向有效的内存区。
此时,zhang和student[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhang和student[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改student[0]所指向的Person实例的实例变量,还是修改zhang变量所指向的Person实例的实例变量,所修改的其实是用一个内存区,所以必然互相影响。同理,lee和student[1]也是引用同一个Person对象,也具有相同的效果。
[align=center] 多维数组[/align]
所谓的“二维”数组:
int[ ] – > int[ ][ ]
int[ ][ ] – > int[ ][ ][ ]
三维数组的元素是二维数组。
二维数组的元素是一维数组
N维数组的元素是N-1维数组
.
程序代码如下:多维数组.java
程序代码如下:MultiDimenTest.java
程序代码如下:TwoDimensionTest.java
程序的第一行int[ ][ ] a;将在栈内存中定义一个引用变量,这个变量并未指向任何有效的内存空间,此时的堆内存还未为这行代码分配任何存储区。
程序对a数组执行初始化:a = new int[4][ ];这行代码让a变量指向一块长度为4的数组内存,这个长度为4的数组里每个数组元素都是引用类型(数组类型),系统为这些数组元素分配默认的初始值:null。
将二维数组当成一维数组初始化的存储示意图
虽然声明a是一个二维数组,但这里丝毫看不出它是一个二维数组的样子,完全是一维数组的样子。这个一维数组的长度是4,只是这4个数组元素都是引用类型,它们的默认值是null。所以程序中可以把a数组当成一维数组处理,依次遍历a数组的每个元素,将看到数组元素的值都是null。
由于a数组的元素必须是int[ ]数组,所以接下来的程序对a[0]元素执行初始化,也就是让上图右边堆内存中的第一个数组元素指向一个有效的数组内存,指向一个长度为2的int数组。因为程序采用动态初始化a[0]数组,因此系统将为a[0]所引用数组的每个元素分配默认的初始值:0,然后程序显式为a[0]数组的第二个元素赋值为6.如下图,接着迭代输出a[0]数组的每个数组元素,将看到输出0和6.
初始化a[0]后的存储示意图
定义一个b数组变量,这个数组变量指向一个长度为3的数组,这个数组的每个数组元素又是一个数组类型,它们各指向对应的长度为4的int[]数组,每个数组元素的值为0.如下图:
同时初始化二维数组的两个维数后的存储示意图
还可以使用静态初始化方式来初始化二维数组。使用静态初始化方式来初始化二维数组时,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始化值。如下图:
采用静态初始化语法初始化二维数组的存储示意图
[align=center] Array工具类[/align]
Arrays类处于java.util包下,为了在程序中使用Arrays类,必须在程序中导入java.util.Arrays类。
程序代码如下:Arrays工具类.java
程序代码如下:ArraysTest.java
程序代码如下:ArraysTest2.java
[align=center] 数组的应用举例[/align]
程序代码如下:数字转中文.java
程序代码如下:Gobang.java
1、顺序结构。从上到下依次执行,中间没有任何判断和跳转。
2、分支结构。根据条件来选择性地执行某段代码。
3、循环结构。根据循环条件重复执行某段代码。
1.顺序结构
[align=center] 从上到下依次执行,中间没有任何判断和跳转。[/align]如果main方法的多行代码之间没有任何流程控制,则程序总是从上向下依次执行,排在前面的代码先执行,排在后面的代码后执行。这意味着:如果没有流程控制,Java方法里的语句是一个顺序执行流,从上向下依次执行每条语句。
2.分支结构
[align=center] if语句[/align]if语句使用布尔表达式或者布尔值作为分支条件来进行分支控制。
[align=center] 第一种语法格式[/align]
if(返回boolean类型的表达式) { //要做的事情。 }
程序代码如下:IfTest1.java
public class IfTest1 { public static void main(String[] args) { double money = 3200; System.out.println("我有" + money + "人民币"); //第一种语法格式 if(money > 5000) { System.out.println("我有超过5000人民币"); } } }
[align=center] 第二种语法格式[/align]
if(返回boolean类型的表达式) { //要做的事情。 }
else
{
//要做的事情。
}
程序代码如下:IfTest2.java
public class IfTest2 { public static void main(String[] args) { double money = 3200; System.out.println("我有" + money + "人民币"); //第二种语法格式 if(money > 5000) { System.out.println("我有超过5000人民币"); } else { System.out.println("但是我的钱不够5000人民币"); } } }
[align=center] 第三种语法格式[/align]
if(返回boolean类型的表达式) { //要做的事情。 } else if(返回boolean类型的表达式) { //要做的事情。 } … //else if块可以出现N次 else //else块可以出现0或者1次,即可省略 { //要做的事情。 }
程序代码如下:IfTest3.java
public class IfTest3 { public static void main(String[] args) { double money = 3200; System.out.println("我有" + money + "人民币"); //第三种语法格式 if(money > 5000) { System.out.println("我有超过5000人民币"); } else if(money > 4000) { System.out.println("我有超过4000人民币"); } else if(money > 3000) { System.out.println("我有超过3000人民币"); } else if(money > 2000) { System.out.println("我有超过2000人民币"); } else if(money > 1000) { System.out.println("我有超过2000人民币"); } else { System.out.println("但是我的钱不够5000人民币"); } } }
注意点:
1、如果条件执行体只有一行代码,Java允许省略条件执行体的花括号。
规则:如果省略了花括号,if条件只控制到第一个分号前面。
建议:即使条件执行体只有一行语句,也用花括号。
程序代码如下:If省略花括号1.java
public class If省略花括号1 { public static void main(String[] args) { double money = 3200; System.out.println("我有" + money + "人民币"); System.out.println(); if(money > 5000); // 这里有分号,执行完这一句就跳出循环 System.out.println("这里已经不在循环里面了"); System.out.println("我的钱超过5000人民币"); System.out.println(); if(money > 4000) System.out.println("我的钱超过4000人民币"); // 这里有分号,执行完这一句就跳出循环 System.out.println("这里已经不在循环里面了,循环在上一个分号结束,因为不满足条件,所以不能输出"); System.out.println(); if(money > 3000) System.out.println("我的钱超过3000人民币");// 这里有分号,执行完这一句就跳出循环 System.out.println("这里已经不在循环里面了,循环在上一个分号结束,因为满足条件,所以有输出"); } }
程序代码如下:If省略花括号2.java
public class If省略花括号2 { public static void main(String[] args) { double money = 3200; System.out.println("我有" + money + "人民币"); System.out.println(); if(money > 5000) System.out.println("我的钱超过5000人民币"); // 这里有分号,执行完这一句就结束循环。 System.out.println("这里已经不在循环里面了"); else //由于上面的if只能控制到第一个分号前面,所以此处的else是孤独的,因此程序错误。 System.out.println("我的钱不超过5000人民币"); } }
2、else本身就是条件。当我们有多个else if块时,建议,先处理范围小的条件。
程序代码如下:IfError.java
public class IfError { public static void main(String[] args) { int age = 65; System.out.println("int age = 60;"); System.out.println(); if(age > 20) { System.out.println("岁数大于20"); } else if(age > 40) // 相当于这句是else if((!(age > 20)) && (age > 40));也就是else if((age <= 20) && (age > 40)); { System.out.println("岁数大于40"); } else if(age > 60) { System.out.println("岁数大于60"); } else { System.out.println("岁数小于20"); } System.out.println(); //先处理范围小的条件。 if(age > 50) { System.out.println("岁数大于50"); } else if(age > 40) { System.out.println("岁数大于40"); } else if(age > 20) { System.out.println("岁数大于20"); } else { System.out.println("岁数小于20"); } System.out.println(); //先处理范围小的条件。 if(age < 20) { System.out.println("岁数大于20"); } else if(age < 40) { System.out.println("岁数大于40"); } else if(age < 60) { System.out.println("岁数大于60"); } else { System.out.println("岁数大于60"); } System.out.println(); } }
[align=center] switch语句
当等于某个值时,才执行一段代码。[/align]
switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种整数类型、枚举类型和java.lang.String类型(从Java7才允许),不能是boolean类型。
语法格式:
switch(返回值是byte、short、char、int、string或枚举的表达式) { case 值1 : //要做的事情 break; //可以省略 case 值2 : //要做的事情 break; //可以省略 case 值3 : //要做的事情 break; //可以省略 default : //当表达式的值与前面的case块的值都不相等时,才会执行 //要做的事情 break; //可以省略 }
程序代码如下:SwitchTest.java
public class SwitchTest { public static void main(String[] args) { char score = 'B'; switch (score) { case 'A': System.out.println("分数是A"); break; case 'B': System.out.println("分数是B"); break; case 'C': System.out.println("分数是C"); break; case 'D': System.out.println("分数是D"); break; default: System.out.println("分数不是A、B、C、D"); break; } } }
注意点:
省略break后,执行流程时,switch表达式的值,依次与每个case块的值进行比较。
当遇到一个相等的值之后,将继续执行余下所有的可执行性代码。在直到遇到break语句之前,不会与case的值进行比较,而是直接执行代码。
建议:每次写了case之后,在写其他代码之前,立即写break。
程序代码如下:Switch省略break.java
public class Switch省略break { public static void main(String[] args) { char score = 'B'; switch (score) { case 'A': System.out.println("分数是A"); case 'B': System.out.println("分数是B"); case 'C': System.out.println("分数是C"); case 'D': System.out.println("分数是D"); default: System.out.println("分数不是A、B、C、D"); } System.out.println("---------------------"); switch (score) { case 'A': System.out.println("分数是A"); case 'B': System.out.println("分数是B"); case 'C': System.out.println("分数是C"); case 'D': System.out.println("分数是D"); break; default: System.out.println("分数不是A、B、C、D"); } } }
程序代码如下:活动计划.java
import java.util.*; import java.text.*; public class 活动计划 { public static void main(String[] args) { //获取系统的当前时间 Date date = new Date(); System.out.println("系统当前日期是:"+ date);// 输出英文格式的时间 System.out.println(); //创建一个DateFormat实例,DateFormat作用就是把日期转换成中文格式的时间的字符串。 DateFormat df = DateFormat.getDateInstance(DateFormat.FULL); String str = df.format(date); System.out.println("系统当前日期是:"+ str); // 输出中文格式的时间 int spacePos = str.indexOf(" "); //获取" "在前面字符串中的位置。 //spacePos代表空格的位置,spacePos + 1代表空格后面的字符 String day = str.substring(spacePos + 1); // 输出中文格式的时间 System.out.println(); switch(day) { case "星期一": System.out.println("今天是星期一"); break; case "星期二": System.out.println("今天是星期二"); break; case "星期三": System.out.println("今天是星期三"); break; case "星期四": System.out.println("今天是星期四"); break; case "星期五": System.out.println("今天是星期五"); break; case "星期六": System.out.println("今天是星期六"); break; case "星期日": System.out.println("今天是星期日"); break; default: System.out.println("今天不知道是星期几"); break; } } }
4.循环结构
循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段被重复执行的代码被称为循环体。当反复执行进行这个循环体时,需要在合适的时候把循环条件改为假,从而结束循环,否则循环将一直执行下去,形成死循环。循环语句可能包含如下4个部分:
1)初始化语句:一条或者多条语句,用于完成一些初始化工作,在循环开始前执行。
2)循环条件:这是一个boolean表达式,决定是否执行循环体。
3)循环体:循环的主体,条件允许则被重复执行。
4)迭代语句:在一次循环体执行后,对循环条件求值之前执行,通常控制循环条件中的变量,使得循环在合适的时候结束。
[align=center] while循环[/align]
while(返回boolean值的表达式) { //需要重复做的事情。 }
程序代码如下:WhileTest.java
public class WhileTest { public static void main(String[] args) { int i = 0; while(i < 10) { System.out.println("i的值为:"+ i); i ++; } } }
如果循环体只有一条代码,可以省略花括号。
规则:如果省略了花括号,while条件只控制到第一个分号前面。
建议:即使循环体只有一行代码,也用花括号。
使用while循环时,一定要保证循环条件有变成false的时候,否则这个循环将成为一个死循环,永远无法结束这个循环。
程序代码如下:WhileError1.java
public class WhileError1 { public static void main(String[] args) { int i = 0; while(i < 10) System.out.println("i的值为:"+ i++);// 这里有分号,执行完这一句就重复循环到不满足条件就结束循环。 System.out.println("这里不在循环内"); } }
程序代码如下:WhileError2.java
public class WhileError2 { public static void main(String[] args) { int i = 0; // while(i < 10); //前一行代码为死循环,永远为true while(i++ < 10); { i ++; System.out.println("i的值为:"+ i); } } }
[align=center] do while循环[/align]
do { //代码段 } while(返回boolean值的表达式);
这个与while相比,把循环放在前面,无论真假都会先执行一次,如果条件为true,执行下一次循环。对于do while来说,至少都会执行一次。与while循环不同的是,do while循环的循环条件后必须有一个分号,才表明循环结束。
程序代码如下:DoWhileTest.java
public class DoWhileTest { public static void main(String[] args) { int i = 0; do { System.out.println("i的值为:"+ i); } while (i < -20); } }
[align=center] for 循环[/align]
for( 初始化语句; 返回boolean值的表达式; 每次循环体执行完后执行的代码) { //循环体 }
程序代码如下:ForTest1.java
public class ForTest1 { public static void main(String[] args) { for( ; ; ) { System.out.println("这是死循环"); } } }
程序代码如下:ForTest2.java
public class ForTest2 { public static void main(String[] args) { int i = 0; for( i = 0 ; i < 10 ; i++ ) { System.out.println("此时i的值为:"+ i); } System.out.println(); System.out.println("结束循环后,i的值为:"+ i); } }
程序代码如下:ForTest3.java
public class ForTest3 { public static void main(String[] args) { int i = 0; for(i = 0 ; i < 10 ; i++ ) { //System.out.println("此时i的值为:"+ (i *= 0.2)); //死循环,i = (int)(i * 0.2) //第一次循环i = (int)(0 * 0.2) = 0; //第二次循环i = (int)(1 * 0.2) = 0; //第三次循环i = (int)(0 * 0.2) = 0; //第四次循环i = (int)(1 * 0.2) = 0; } System.out.println("结束循环后i的值为:"+ i); System.out.println(); for(i = 0 ; i < 10 ; i++ ) { System.out.println("此时i的值为:"+ (i *= 1.4)); //死循环,i = (int)(i * 1.4) //第一次循环i = (int)(0 * 1.4) = 0; //第二次循环i = (int)(1 * 1.4) = 1; //第三次循环i = (int)(2 * 1.4) = 2; //第四次循环i = (int)(3 * 1.4) = 4; //第五次循环i = (int)(5 * 1.4) = 7; } //结束循环后继续运算一次 i++ = 12; System.out.println("结束循环后i的值为:"+ i); } }
程序代码如下:ForTest4.java
public class ForTest4 { public static void main(String[] args) { for (int i = 2,j = 3 ,k = 2; i + j > k; ) { //第一次循环i = 2;j = 3;k = 4; //第二次循环i = 3;j = 4;k = 8; //第三次循环i = 4;j = 5;k = 16; //第三次循环i = 5;j = 6;k = 32; 此时5 + 6 < 16,循环结束 System.out.println("i的值为"+ i++); System.out.println("j的值为"+ j++); System.out.println("k的值为"+ (k *= 2)); System.out.println(); } } }
“初始化语句”可以省略。如果有“初始化语句”,程序执行for循环时,先执行循环的“初始化语句”,且只在循环开始前执行一次,允许同时指定多个初始化语句;
“返回boolean值的表达式”可以省略。即循环条件语句,每次执行循环体之前,先计算该值,如果返回true,则执行循环体,如果省略,意味着他的值永远是true;
“每次循环体执行完后执行的代码”可以省略。即循环迭代语句,这里并没有与循环体放在一起,因此即使在执行循环体时遇到continue语句结束本次循环,循环迭代语句也一样会得到执行,如果不省略,将在每次循环体执行完成之后,开始下一次循环体之前,执行该代码。
只要两个分号中间的表达式返回true,程序将执行循环体。
条件表达式的计算次数总是比循环体执行次数多一次。
建议:不要在循环体内修改循环变量(也叫循环计数器)的值,否则会增加程序出错的可能性。万一程序真的需要访问、修改循环变量的值,建议重新定义一个临时变量,先将循环变量的值赋给临时变量,然后对临时变量的值进行修改。
[align=center] for each循环[/align]
循环嵌套:
在循环里面,再放置循环。把内层循环当成外层循环的一条语句即可。
程序代码如下:循环嵌套.java
public class 循环嵌套 { public static void main(String[] args) { int i = 0; while(++i < 10) { System.out.println("进入外层循环;此时i="+ i); for(int j = 0; j < 10; j++ ) { System.out.println("i的值为:"+ i + ",j的值为::"+ j); } System.out.println("外层循环的最后一行;此时i="+ i); } System.out.println(); System.out.println("一共循环 "+ (i-1)*10 +" 次"); System.out.println("最后i="+ i); } }
4.控制循环结构
[align=center] break完全结束一个循环本身[/align]
程序代码如下:BreakTest.java
public class BreakTest { public static void main(String[] args) { int i = 0; for( ; ; ) { System.out.println("i的值是 "+ i++); if(i > 12) { break; //完全停止一个循环 } } } }
[align=center] continue
停止当前循环体的执行(忽略continue后的语句),开始下一次循环体。[/align]
如果continue位于循环体的最后部分,他就是多余的。
程序代码如下:ContinueTest.java
public class ContinueTest { public static void main(String[] args) { int i = 0; for(i = 0; i < 7; i++) { System.out.println("i的值是 "+ i); if(i > 3) { break; //完全停止一个循环 } } System.out.println(); for(i = 0; i < 7; i++) { System.out.println("i的值是 "+ i); if(i > 3) { continue; //完全停止一个循环 } } System.out.println(); for(i = 0; i < 7; i++) { System.out.println("i的值是 "+ i); if(i > 3) { continue; //完全停止一个循环 } System.out.println("----i的值是 "+ i); } System.out.println(); for(i = 0; i < 7; i++) { System.out.println("i的值是 "+ i); if(i > 3) { return; //完全停止一个循环 } System.out.println("----i的值是 "+ i); } } }
[align=center] return
结束整个方法。遇到return,方法就会结束执行。[/align]
程序代码如下:ReturnTest.java
public class ReturnTest { public static void main(String[] args) { int i = 0; while(++i < 10) { System.out.println("进入外层循环"); for (int j = 0; j < 10 ; j++ ) { for (int k = 0; k < 20 ; k++) { System.out.println("i的值为:"+ i +",j的值为:"+ j +",k的值为:"+ k ); if (k > 3) { return; } System.out.println("---i的值为:"+ i +",j的值为:"+ j +",k的值为:"+ k ); } } System.out.println("外层循环的最后一行"); } } }
[align=center] break、continue后可以紧跟标号
带标号的break用于结束标号所标识的循环。[/align]
带标号的continue用于忽略标号所标识的循环后面剩下的语句。
程序代码如下:BreakLabelTest.java
public class BreakLabelTest { public static void main(String[] args) { outer: for (int i = 0; i < 10; i ++) { for (int j = 0; j < 10; j++) { System.out.println("i的值为:"+ i +",j的值为:"+ j); if (j > 3) { break outer;//带标号的循环用于结束标号所在的循环 } System.out.println("---i的值为:"+ i +",j的值为:"+ j); } System.out.println(); } } }
程序代码如下:ContinueLabelTest1.java
public class ContinueLabelTest1 { public static void main(String[] args) { outer: for (int i = 0; i < 10; i ++) { System.out.println(); for (int j = 0; j < 10; j++) { System.out.println("i的值为:"+ i +",j的值为:"+ j); if (j > 3) { continue outer;//带标号的循环用于结束标号所在的循环 } System.out.println("---i的值为:"+ i +",j的值为:"+ j); } } } }
程序代码如下:ContinueLabelTest2.java
public class ContinueLabelTest2 { public static void main(String[] args) { outer: for (int i = 0; i < 10; i ++) { for (int j = 0; j < 10; j++) { System.out.println("i的值为:"+ i +",j的值为:"+ j); if (i > 3) { continue outer;//带标号的循环用于结束标号所在的循环 } System.out.println("---i的值为:"+ i +",j的值为:"+ j); } System.out.println(); } } }
5.数组类型
为什么要用数组?如果程序中需要用到多个相同类型的变量,就可以考虑把它们集中定义成一个数组。每个数组元素,就相当于一个普通的变量。借助于数组,我们可以非常方便地去管理、访问每个数组元素(相当于一个变量)
数组也是一种类型,Java的数组要求所有的数组元素具有相同的数据类型。因此,在一个数组中,数组元素的类型是唯一的,即一个数组里只能存储一种数据类型的数据,而不能存储多种数据类型的数据。
[align=center] 数组类型[/align]
Java数据类型可以分为:
基本类型 – 8个
引用类型 – 类/接口/数组
数组类型也是一种数据类型,它本身是一种引用类型。既可用于创建变量,也可用于进行强制类型转换。
int -> int[ ] -> 整型数组
double -> double [ ] -> double型数组
…
string -> string[ ] -> string型数组
int[ ] -> int[ ][ ] -> 新的数组类型。整型数组
int[ ][ ] -> int[ ][ ][ ] -> 新的数组类型。整型数组
int[ ]就是一种数据类型,与int类型、String类型相似,一样可以使用该类型来定义变量,也可以使用该类型进行类型转换等。使用int[ ]类型来定义变量、进行类型转换时与其它普通类型没有任何区别。int[ ]类型是一种引用类型,创建int[ ]类型的对象也就是创建数组,需要使用创建数组的语法。
变量分为两种:
1、直接在该变量中存放实际的变量值,这就是Java的基本类型的变量。
2、在变量中存放只是内存的地址值,这就是所谓的引用变量。
数组是一种引用类型的变量,因此使用它定义一个变量时,仅仅表示定义了一个引用变量(也就是定义了一个指针),只不过它里面存的值是内存地址,这个引用变量还未指向任何有效的内存,因此定义数组时不能指定数组的长度。而且由于定义数组只是定义了一个引用变量,并未指向任何有效的内存空间,所以还没有内存空间来存储数组元素,因此这个数组也不能使用,只有对数组进行初始化后才可以使用。
定义数组:定义时不能指定数组的长度。
程序代码如下:ArrayTypeTest.java
public class ArrayTypeTest { public static void main(String[] args) { //类型 变量名 int a[]; //不好的写法 //看起来好像定义了一个类型为int的变量,变量名为a[],与真实的含义相距胜远。 //类型是int[],变量名是a int[] a; //好的写法 //第二种写法很容易理解只是定义一个变量,其中变量名是a,而变量类型是int[]。 //实际上int[]确实是一种新类型,也符合定义变量的语法。 System.out.println("Hello World!"); } }
Java数组的特征:
Java语言是强类型, 一个数组里只能存储一种数据类型的数据。
Java语言是静态的, Java的数组一旦被初始化后,它的长度是固定的。
数组的初始化:
数组变量只是一个引用,必须让它指向有效的内存之后才能使用。初始化就是为数组的数组元素分配内存空间,并为每个数组元素赋初始值。
一旦为数组的每个数组元素分配了内存空间,每个内存空间里存储的内容就是该数组元素的值,即使这个内存空间存储的内容是空,这个空也是一个值(null)。不管以哪种方式来初始化数组,只要为数组元素分配了内存空间,数组元素就具有了初始值。初始值的获得有两种形式:一种由系统自动分配;另一种由程序员指定。
静态初始化:只指定数组的元素,让系统来决定数组的长度。
new < type > [ ] { <ele1>, <ele2>, <ele3> …} ;
动态初始化:只指定数组的长度,让系统来决定数组的元素的值。
new < type > [<length>] { } ;
程序代码如下:数组测试.java
public class 数组测试 { public static void main(String[] args) { int[] iArr; //声明了一个数组变量,引用类型变量,他存放的是内存地址。 iArr = new int[]{ 20, 1, 12, 31}; //iArr是一个引用类型的变量,也就是iArr存放了后面创建的数组的地址。 System.out.println("int[] iArr; iArr = new int[]{ 20, 1, 12, 31};"); System.out.println("iArr[0] = "+ iArr[0] ); System.out.println(); iArr[0] = 100; System.out.println("当:iArr[0] = 100;"); System.out.println("iArr[0] = "+ iArr[0] ); System.out.println(); iArr[0] = 'c';//'a'本来是char,但由于char可以自动转换为int类型 System.out.println("当:iArr[0] = 'c';"); System.out.println("iArr[0] = "+ iArr[0] ); System.out.println(); //动态初始化 String[] strArr = new String[5]; //第一个数组元素的索引是0 //最后一个数组元素的索引是length-1 //遍历数组元素 for (int i = 0; i < strArr.length; i++ ) { System.out.println("strArr[" + i + "] = "+ strArr[i] ); } System.out.println(); //把数组的最后一个元素赋值为"邬森平" strArr[strArr.length-1] = "邬森平"; //遍历数组元素 for (int i = 0; i < strArr.length; i++ ) { System.out.println("strArr[" + i + "] = "+ strArr[i] ); } System.out.println(); //当我们访问数组元素的索引小于0或者大于等于length,将引发“数组索引越界”异常。 System.out.println("strArr[" + strArr.length + "] = "+ strArr[strArr.length] ); } }
如果数组的元素是基本类型(整数型/浮点型/布尔型/字符型),那么所有数组元素的值都是0 / 0.0 / false / ‘\u0000’。
如果数组的元素是引用类型(类/接口/数组),那么所有数组元素的值都是null。
不能同时用静态初始化跟动态初始化
一旦数组的初始化完成,接下来的每个数组元素就可以当成普通变量使用了。
[align=center] 使用数组[/align]
每个数组元素相当于一个变量,该变量的类型,就是数组类型去掉一个方括号。
数组有一个length属性,用于返回该数组的长度。
程序代码如下:数组测试.java
public class 数组测试 { public static void main(String[] args) { int[] iArr; //声明了一个数组变量,引用类型变量,他存放的是内存地址。 iArr = new int[]{ 20, 1, 12, 31}; //iArr是一个引用类型的变量,也就是iArr存放了后面创建的数组的地址。 System.out.println("int[] iArr; iArr = new int[]{ 20, 1, 12, 31};"); System.out.println("iArr[0] = "+ iArr[0] ); System.out.println(); iArr[0] = 100; System.out.println("当:iArr[0] = 100;"); System.out.println("iArr[0] = "+ iArr[0] ); System.out.println(); iArr[0] = 'c';//'a'本来是char,但由于char可以自动转换为int类型 System.out.println("当:iArr[0] = 'c';"); System.out.println("iArr[0] = "+ iArr[0] ); System.out.println(); //动态初始化 String[] strArr = new String[5]; //第一个数组元素的索引是0 //最后一个数组元素的索引是length-1 //遍历数组元素 for (int i = 0; i < strArr.length; i++ ) { System.out.println("strArr[" + i + "] = "+ strArr[i] ); } System.out.println(); //把数组的最后一个元素赋值为"邬森平" strArr[strArr.length-1] = "邬森平"; //遍历数组元素 for (int i = 0; i < strArr.length; i++ ) { System.out.println("strArr[" + i + "] = "+ strArr[i] ); } System.out.println(); //当我们访问数组元素的索引小于0或者大于等于length,将引发“数组索引越界”异常。 System.out.println("strArr[" + strArr.length + "] = "+ strArr[strArr.length] ); } }
[align=center] 遍历数组[/align]
1)可以依次根据每个数组元素的索引来进行遍历。
2)可以使用foreach循环来遍历。
for(数组或者集合的数据类型 变量名:数组/集合) { //此处即可通过“变量名”依次访问每个数组/集合的元素。 }
注意:foreach循环时。不要对循环变量赋值。
如果需要在遍历的时候对数组元素进行赋值,那就应该根据数组元素的索引来进行遍历。
程序代码如下:ForEachTest.java
public class ForEachTest { public static void main(String[] args) { //静态初始化 double[] duArr = new double[]{3.2,20.0,100.1}; //简化写法:double[] duArr = {3.2,20.0,100.1}; for (double ele : duArr) { System.out.println("ForEach遍历数组元素:"+ ele); } System.out.println(); for (double ele : duArr) { ele = 100.0; //foreach循环中,对循环变量赋值没有太大的意义,因此数组元素不会受到影响 System.out.println("ForEach遍历数组元素:"+ ele); } System.out.println(); //遍历数组元素 for (int i = 0; i < duArr.length; i++ ) { System.out.println("duArr[" + i + "] = "+ duArr[i] ); } System.out.println(); //遍历数组元素 + 赋值 for (int i = 0; i < duArr.length; i++ ) { duArr[i] = 100.0; System.out.println("duArr[" + i + "] = "+ duArr[i] ); } System.out.println(); } }
6.深入数组
[align=center] 内存中的数组[/align]数组引用变量只是一个引用,这个引用变量可以指向任何有效的内存,只有当该引用指向有效内存后,才可通过该数组变量来访问数组元素。
与所有引用变量相同的是,引用变量是访问真实对象的根本方式。也就是说,如果希望在程序中访问数组对象本身,则只能通过这个数组的引用变量来访问它。
实际的数组对象被存储在堆内存中;如果引用该数组对象的数组引用变量是一个局部变量,那么它被存储在栈内存中。
数组在内存中的存储示意图
如果需要访问上图所示的堆内存中的数组元素,则程序中只能通过p[index]的形式实现。也就是说,数组引用变量是访问堆内存中数组元素的根本方式。
程序代码如下:PriArrayTest.java
public class PriArrayTest { public static void main(String[] args) { //所有在方法里声明的变量,都放在相应的方法栈 /* * 每个方法运行时,系统都会为之建立一个方法栈 * 栈内存是临时性的内存,当方法结束时,方法栈会被立即释放 * 所以栈内存不适合存放长期有效的数据 * 堆内存:每个Java虚拟机JVM只有一个。 * 只要JVM不退出,堆内存一直存在。 */ double d = 23.9; //d是基本类型 int[] iarr = null; //它只是数组变量,引用变量,此时这个iarr不能使用。 //NullPointerException异常,空指针异常。 //当程序访问一个null对象的实例方法或实例属性 //程序就会引发NullPointerException异常 //System.out.println("int[] iarr; iarr[0]"+ iarr[0]); iarr = new int[4]; //所有的对象都放在"堆"内存。每个Java虚拟机JVM只有一个"堆"内存。 //在Java程序中,只要访问“引用”的方法、属性时,系统会自动切换到访问它实际的对象。 //堆内存中的对象,只能通过引用变量来访问。 iarr[2] = 34; // 34是基本类型,但实际上存在堆内存中。 System.out.println("iarr.length = "+ iarr.length); //静态初始化的简化语法 int[] ba = {12, 20, 103}; } }
当数组对象的引用变量被销毁之后,这个数组对象并不一定会被回收(它在堆内存),它不会随着数组变量被回收。
只有当没有引用变量引用这个数组对象时,系统才会去回收数组对象。
[align=center] 为什么有栈内存和堆内存之分?[/align]
当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁。因此,所有在方法中定义的局部变量都是放在栈内存中的;在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(在方法的参数传递时很常见),则这个对象依然不会被销毁。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收器才会在合适的时候回收它。
如果堆内存中数组不再有任何引用变量指向自己,则这个数组将变成垃圾,该数组所占的内存将会被系统的垃圾回收机制回收。因此,为了让垃圾回收机制回收一个数组所占的内存空间,可以将该数组变量赋为null,也就切断了数组引用变量和实际数组之间的引用关系,实际的数组也就成了垃圾。
只要类型相互兼容,就可以让一个数组变量指向另一个实际的数组,这种操作会让人产生数组的长度可变的错觉。
程序代码如下:ArrayInRam.java
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); } }
运行上面代码后,将可以看到先输出b数组的长度为4,然后依次输出a数组和b数组的每个数组元素,接着会输出b数组的长度为3.看起来似乎数组的长度是可变的,但这只是一个假象。
必须牢记:定义并初始化一个数组后,在内存中分配了两个空间,一个用于存放数组的引用变量,另一个用于存放数组本身。下面将结合示意图来说明上面程序的运行过程。
定义并初始化a、b两个数组后的存储示意图
当程序定义并初始化了a、b两个数组后,系统内存中实际上产生了4块内存区,其中栈内存中有两个两个引用变量:a和b;对内存中也有两块内存区,分别存储a和b引用所指向的数组本身。此时可以看到a引用和b引用各自所引用的数组对象,a变量所引用的数组长度为3,b变量所引用的数组长度为4.
让b引用指向a引用所指向数组后的存储示意图
当执行b = a时,系统将会把a的值赋给b,a和b都是引用类型变量,存储的是地址。因此把a的值赋给b后,就是让b指向a所指向的地址。当执行b = a后,堆内存中的第一个数组具有了两个引用;a变量和b变量都引用了第一个数组。此时第二个数组失去了引用,变成垃圾,只有等待垃圾回收机制来回收它——但它的长度依然不会改变,知道它彻底消失。
[align=center] 基本类型数组的初始化[/align]
对于基本类型数组而言,数组元素的值直接存储在对应的数组元素中,因此,初始化数组时,先为该数组分配内存空间,然后直接将数组元素的值存入对应数组元素中。
下面程序定义了一个int[ ]类型的数组变量,采用动态初始化的方式初始化了该数组,并显式为每个数组元素赋值。
程序代码如下:PrimitiveArrayTest.java
public class PrimitiveArrayTest { public static void main(String[] args) { // 定义一个int[]类型的数组变量 int[] iArr; // 动态初始化数组,数组长度为5 iArr = new int[5]; // 采用循环方式为每个数组元素赋值。 for (int i = 0; i <iArr.length ; i++ ) { iArr[i] = i + 10; } } }
定义iArr数组变量后的存储示意图
执行第一行代码int[ ] iArr;时,仅定义了一个数组变量。
执行int[ ] iArr;后,仅在栈内存中定义了一个空引用(就是iArr数组变量),这个引用并未指向任何有效的内存,当然无法指定数组的长度。
动态初始化iArr数组后的存储示意图
当执行iArr = new int[5];动态初始化后,系统将负责为该数组分配内存空间,并分配默认的初始值:所有数组元素都被赋为值0,。
显式指定每个数组元素值后的存储示意图
此时iArr数组的每个数组元素的值都是0,当循环为该数组的每个数组元素依次赋值后,此时每个数组元素的值都变成程序显式指定的值。每个数组元素的值直接存储在对应的内存中。操作基本类型数组的数组元素时,实际上相当于操作基本类型的变量。
引用类型数组的初始化
. 程序代码如下:ReferenceArrayTest.java
class Person { public int age; // 年龄 public double height; // 身高 // 定义一个info方法 public void info() { System.out.println("我的年龄是:" + age + ",我的身高是:" + height); } } public class ReferenceArrayTest { public static void main(String[] args) { // 定义一个students数组变量,其类型是Person[] Person[] students; // 执行动态初始化 students = new Person[2]; // 创建一个Person实例,并将这个Person实例赋给zhang变量 Person zhang = new Person(); // 为zhang所引用的Person对象的age、height赋值 zhang.age = 15; zhang.height = 158; // 创建一个Person实例,并将这个Person实例赋给lee变量 Person lee = new Person(); // 为lee所引用的Person对象的age、height赋值 lee.age = 16; lee.height = 161; // 将zhang变量的值赋给第一个数组元素 students[0] = zhang; // 将lee变量的值赋给第二个数组元素 students[1] = lee; // 下面两行代码的结果完全一样,因为lee // 和students[1]指向的是同一个Person实例。 lee.info(); students[1].info(); } }
定义一个students数组变量后的存储示意图
执行Person[ ] student;时,仅仅在栈内存定义了一个引用变量,也就是一个指针,这指针并未指向任何有效的内存区。即在栈内存中定义了一个student变量,它仅仅是一个引用,并未指向任何有效的内存。
动态初始化students数组后的存储示意图
直到执行初始化,本程序对student数组执行动态初始化,动态初始化由系统为数组元素分配默认的初始值:null,即每个数组元素的值都是null。
student数组的两个数组元素都是引用,而且引用并未指向任何有效的内存,因此每个数组元素的值都是null。这意味着依然不能直接使用student数组元素,因为每个数组元素都是null,这相当于定义了两个连续的Person变量,但这个变量还未指向任何有效的内存区,所以这两个连续的Person变量(student数组的数组元素)还不能使用。
创建两个Person实例后的存储示例图
接着的代码定义了zhang和lee两个person实例,定义这两个实例实际上分配了4块内存,在栈内存中存储了zhang和lee两个引用变量,还在堆内存中存储了两个Person实例。
为数组元素赋值后的存储示意图
此时student数组的两个数组元素依然是null,知道程序依次将zhang赋给student数组的第一个元素,把lee赋给student数组的第二个元素,student数组的两个数组元素将会指向有效的内存区。
此时,zhang和student[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhang和student[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改student[0]所指向的Person实例的实例变量,还是修改zhang变量所指向的Person实例的实例变量,所修改的其实是用一个内存区,所以必然互相影响。同理,lee和student[1]也是引用同一个Person对象,也具有相同的效果。
[align=center] 多维数组[/align]
所谓的“二维”数组:
int[ ] – > int[ ][ ]
int[ ][ ] – > int[ ][ ][ ]
三维数组的元素是二维数组。
二维数组的元素是一维数组
N维数组的元素是N-1维数组
.
程序代码如下:多维数组.java
public class 多维数组 { public static void main(String[] args) { int[][] a = new int[2][3]; System.out.println("a的长度:"+ a.length); System.out.println(); //a的length是2 for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { System.out.print(a[i][j] +"\t"); } System.out.println(); } System.out.println(); a[1] = new int[]{ 13, 23, 10, 24}; //a的length是2 for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { System.out.print(a[i][j] +"\t"); } System.out.println(); } } }
程序代码如下:MultiDimenTest.java
public class MultiDimenTest { public static void main(String[] args) { int[][] arr; //只是一个引用变量 //动态初始化,由系统为数组元素赋值 arr = new int[4][]; //Java允许初始化数组时只初始化左边的维数 System.out.println("arr[1] = " + arr[1]);//arr[1]相当于一个int[]变量 System.out.println(); arr[1] = new int[]{12,15}; System.out.println("arr[1] = " + arr[1]); System.out.println(); // arr[1][2] = 30; //引发数组索引越界异常 // arr[2][0] = 13; //由于arr[2]相当于int[]类型的变量(即引用变量),此时它还未指向有效的内存。因此会引发NullPointerException int[] a = new int[3]; //动态初始化 arr[2] = a; //把a变量存放的地址赋给arr[2] a[2] = 102; for (int i = 0; i < arr[2].length; i++) { System.out.println("arr[2]["+ i +"] = " + arr[2][i]); } System.out.println(); for (int i = 0; i < arr[2].length; i++) { System.out.println("arr[2]["+ i +"] = " + arr[2][i]); if (i == 1) { arr[2][i] = 23; } System.out.println("----arr[2]["+ i +"] = " + arr[2][i]); } System.out.println(); for (int i = 0; i < a.length; i++) { System.out.println("a["+ i +"] = " + a[i]); } } }
程序代码如下:TwoDimensionTest.java
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]); } // 同时初始化二维数组的2个维数 int[][] b = new int[3][4]; // 使用静态初始化的语法来初始化一个二维数组 String[][] str1 = new String[][]{new String[3] , new String[]{"hello"}}; // 使用简化的静态初始化语法来初始化二维数组 String[][] str2 = {new String[3] , new String[]{"hello"}}; System.out.println(str1[1][0]); System.out.println(str2[1][0]); } }
程序的第一行int[ ][ ] a;将在栈内存中定义一个引用变量,这个变量并未指向任何有效的内存空间,此时的堆内存还未为这行代码分配任何存储区。
程序对a数组执行初始化:a = new int[4][ ];这行代码让a变量指向一块长度为4的数组内存,这个长度为4的数组里每个数组元素都是引用类型(数组类型),系统为这些数组元素分配默认的初始值:null。
将二维数组当成一维数组初始化的存储示意图
虽然声明a是一个二维数组,但这里丝毫看不出它是一个二维数组的样子,完全是一维数组的样子。这个一维数组的长度是4,只是这4个数组元素都是引用类型,它们的默认值是null。所以程序中可以把a数组当成一维数组处理,依次遍历a数组的每个元素,将看到数组元素的值都是null。
由于a数组的元素必须是int[ ]数组,所以接下来的程序对a[0]元素执行初始化,也就是让上图右边堆内存中的第一个数组元素指向一个有效的数组内存,指向一个长度为2的int数组。因为程序采用动态初始化a[0]数组,因此系统将为a[0]所引用数组的每个元素分配默认的初始值:0,然后程序显式为a[0]数组的第二个元素赋值为6.如下图,接着迭代输出a[0]数组的每个数组元素,将看到输出0和6.
初始化a[0]后的存储示意图
// 同时初始化二维数组的2个维数 int[][] b = new int[3][4];
定义一个b数组变量,这个数组变量指向一个长度为3的数组,这个数组的每个数组元素又是一个数组类型,它们各指向对应的长度为4的int[]数组,每个数组元素的值为0.如下图:
同时初始化二维数组的两个维数后的存储示意图
// 使用静态初始化的语法来初始化一个二维数组 String[][] str1 = new String[][]{new String[3],new String[]{"hello"}}; // 使用简化的静态初始化语法来初始化二维数组 String[][] str2 = {new String[3],new String[]{"hello"}}; System.out.println(str1[1][0]); System.out.println(str2[1][0]);
还可以使用静态初始化方式来初始化二维数组。使用静态初始化方式来初始化二维数组时,二维数组的每个数组元素都是一维数组,因此必须指定多个一维数组作为二维数组的初始化值。如下图:
采用静态初始化语法初始化二维数组的存储示意图
[align=center] Array工具类[/align]
Arrays类处于java.util包下,为了在程序中使用Arrays类,必须在程序中导入java.util.Arrays类。
程序代码如下:Arrays工具类.java
import java.util.*; public class { public static void main(String[] args) { int[] a = new int[] { 12, 23, 34, 37, 41, 46, 54, 67}; int index1 = Arrays.binarySearch( a, 46); System.out.println("index1 = "+ index1); System.out.println(); int[] b = new int[] { 12, 23, 34, 37, 41, 54, 67}; int index2 = Arrays.binarySearch( b, 46); System.out.println("index2 = "+ index2); System.out.println(); //将b数组的从索引为1的元素~索引为5(不包括5)的元素,都填充成20 Arrays.fill( b, 1, 5, 20); System.out.println("b = "+ b); //直接打印数组,看到 [ 数组类型的首字母大写 @ 地址值 System.out.println(); //Arrays的toString可以直接看到数组元素的值 System.out.println("Arrays.toString(b) = "+ Arrays.toString(b)); System.out.println(); } }
程序代码如下:ArraysTest.java
import java.util.Arrays; public class ArraysTest { public static void main(String[] args) { // 定义一个a数组 int[] a = new int[]{3, 4 , 5, 6}; // 定义一个a2数组 int[] a2 = new int[]{3, 4 , 5, 6}; // a数组和a2数组的长度相等,每个元素依次相等,将输出true System.out.println("a数组和a2数组是否相等:" + Arrays.equals(a , a2)); // 通过复制a数组,生成一个新的b数组 int[] b = Arrays.copyOf(a, 6); System.out.println("a数组和b数组是否相等:" + Arrays.equals(a , b)); // 输出b数组的元素,将输出[3, 4, 5, 6, 0, 0] System.out.println("b数组的元素为:" + Arrays.toString(b)); // 将b数组的第3个元素(包括)到第5个元素(不包括)赋为1 Arrays.fill(b , 2, 4 , 1); // 输出b数组的元素,将输出[3, 4, 1, 1, 0, 0] System.out.println("b数组的元素为:" + Arrays.toString(b)); // 对b数组进行排序 Arrays.sort(b); // 输出b数组的元素,将输出[0, 0, 1, 1, 3, 4] System.out.println("b数组的元素为:" + Arrays.toString(b)); } }
程序代码如下:ArraysTest2.java
import java.util.Arrays; import java.util.function.*; 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; } }); System.out.println(Arrays.toString(arr2)); 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)); } }
[align=center] 数组的应用举例[/align]
程序代码如下:数字转中文.java
//未完成 public class 数字转中文 { public static void main(String[] args) { String[] cnChars = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"}; String[] cnUnits = { " ","十","百","千","万"}; int a = 0103; String str = a + ""; // 数值转换为字符串最简单的方法 // 直接加一个空字符串即可 String result = ""; //str.length()返回str的字符的个数 //循环就从str的字符的个数-1 ~ 0 for (int i = str.length() - 1 , j = 0; i > -1 ; i-- ) { char numChar = str.charAt(i); // 返回str字符串中第i个位置的字符 /* * '0' 当成int用,相当于48 * ... * '9' 当成int用,相当于57 */ //如果当前数字为0时,无需添加单位 if (numChar == '0') { result = cnChars[numChar - 48] + result; } else { result = (cnChars[numChar - 48] + cnUnits[j]) + result; } j++; } System.out.println("转换后为:"+ result); } }
程序代码如下:Gobang.java
//当前只能判断列方向的输赢 //需要补充完整横向以及斜对角方向 import java.io.*; import java.util.*; public class Gobang { //定义了一个常亮 static final int GOBANG_SIZE = 11; static final String NO_CHESS = "口"; static final String BLACK_CHESS = "●"; static final String WHILE_CHESS = "○"; //定义一个二维数组 //此时所有的数组元素,由系统执行默认初始化,默认为null static String[][] status = new String[GOBANG_SIZE][GOBANG_SIZE]; public static void main(String[] args) throws Exception { //程序从这里开始 System.out.println("五子棋!"); //该方法把二维数组的每个元素都赋值为[] init(); //输出棋盘 printStatus(); //System.in代表键盘,把System.in做了一个包装 //br可以理解成包装后的键盘 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); //定义了一个字符串 String line = null; //读取用户输入的一行,并将这行字符串赋给line变量 //而且如果所读取的用户输入的一行不为null,执行循环 while ( (line = br.readLine()) != null ) { //line就是用户输入一行字符串 //这行字符串应该包含X,Y坐标,而且要满足X,Y的格式 //如果用户输入不满足X,Y的格式,退出游戏。 //此时需要将line字符串用逗号分割成前后两个部分 String[] xyStr = line.split(","); //获取x,y字符串位于逗号之前的部分 String xStr = xyStr[0]; //获取x,y字符串位于逗号之后的部分 String yStr = xyStr[1]; //Integer.parseInt(xStr)负责把String转换int值 int xPos = Integer.parseInt(xStr); //Integer.parseInt(yStr)负责把String转换int值 int yPos = Integer.parseInt(yStr); //如果用户希望下棋的坐标不为“口”,表明该下棋位置已经有棋子了 if (!status[xPos][yPos].equals(NO_CHESS)) { System.out.println("该位置已经有棋子了,请重新下棋"); continue; } //所谓的用户下棋,本质就是把该下棋的字符串重新赋值 status[xPos][yPos] = BLACK_CHESS; //此时先做一个“白痴”电脑,下棋是随机的 //所谓的随机下棋,生成两个0(包括)~GOBANG_SIZE(不包括)的随机数 //Random对象是一个随机数生成器 while (true) { //生成两个0(包括)~GOBANG_SIZE(不包括)的随机数 int pcX = new Random().nextInt(GOBANG_SIZE); int pcY = new Random().nextInt(GOBANG_SIZE); //如果电脑试图下棋的位置,没有棋子,电脑正常下棋 if (!status[xPos][yPos].equals(NO_CHESS)) { //所谓的用户下棋,本质就是把该下棋的字符串重新赋值 status[pcX][pcY] = WHILE_CHESS; break; } //如果到了下面,电脑下棋的位置,有棋子 //程序开始下一次循环,重新生成电脑下棋的坐标 } printStatus(); // 输出棋盘 //每次用户、电脑下棋之后,判断一次输赢 if (judge() > 0 ) //用户赢了 { System.out.println("你赢了!"); return; //退出main方法,游戏结束 } if (judge() < 0 ) //电脑赢了 { System.out.println("你输了!"); return; //退出main方法,游戏结束 } //代码能执行到这里,表明还没有输赢 } } //依次输出每个数组元素(相当于绘制棋盘,由于经常需要绘制棋盘,所以此处就执行输出) public static void printStatus() { for (int i = 0; i < GOBANG_SIZE; i++) { // 每循环完,输出GOBANG_SIZE个口 ,没有换行 for (int j = 0; j < GOBANG_SIZE; j++) { System.out.print(status[i][j]); } System.out.println(); } } //定义成一个判断输赢的方法 //让用户每次下棋后,或者电脑每次下棋,进行一次判断。 public static int judge() { //一共有4种方向出现5个连着 //横、竖、对斜线 for (int i = 0; i < GOBANG_SIZE; i++) { String line = ""; //当i为0时,代表第1列;当i为1时,代表第2列 //循环完成时,将会返回字符串 for (int j = 0; j < GOBANG_SIZE; j++) { line += status[j][i]; } //判断字符串内是否包含某个子串 if (line.contains("●●●●●")) //用户赢了 { return 1 ; } if (line.contains("○○○○○")) //电脑赢了 { return -1 ; } } return 0; //没有输赢 } //初始化了一个棋盘 public static void init() { for (int i = 0; i < GOBANG_SIZE; i++) { // 依次把二维数组的每个元素都赋值为口 for (int j = 0; j < GOBANG_SIZE; j++) { status[i][j] = NO_CHESS; } } } }
相关文章推荐
- Java读书笔记04 控制流程 大数值 数组
- Java学习笔记(三):流程控制与数组(2)
- 黑马程序员——java基础---流程控制、函数、数组
- Java流程控制和数组
- Java读书笔记04 控制流程 大数值 数组
- 黑马程序员 JAVA初级-编程基础2 流程控制语句 函数 数组
- java流程控制与数组
- 黑马程序员——Java语言基础:程序流程控制、函数、数组
- Java流程控制与数组
- java基础(4)--流程控制与数组
- java之流程控制与数组
- Java基础之控制流程及数组
- 尚硅谷第四课0722班 java-特殊流程控制 -数组元素的默认初始化-数组操作常见问题-Java内存的结构
- java基础语法day04(流程控制语句switch、数组)
- java流程控制、数组入门(J2SE入门3)
- Java上路04-流程控制语句
- day 5:Java基本语法3(流程控制语句、方法、数组、内存分配)
- java基础第三章-流程控制与数组
- [疯狂Java讲义精粹] 第三章|流程控制与数组
- Java语言基础组成之:程序流程控制、函数、数组