龙空技术网

零基础学JAVA教程.第3章 数组与方法

JAVA架构前端技术 153

前言:

眼前朋友们对“java二维数组的输出”大体比较注意,兄弟们都需要了解一些“java二维数组的输出”的相关资讯。那么小编也在网摘上汇集了一些关于“java二维数组的输出””的相关知识,希望小伙伴们能喜欢,大家一起来了解一下吧!

第3章 数组与方法

本章学习目标

· 了解Java数组的定义

· 掌握Java数组的常用操作

· 理解Java二维数组

· 掌握Java的方法定义与调用

· 掌握Java方法重载与递归

· 理解Java数组的引用传递

当在开发过程中遇到需要定义多个相同类型的变量时,那么使用数组将会是一个很好的选择。例如,要存储80名学生的成绩,在没有数组之前,就需要定义80个变量,很明显这个定义的过程相当琐碎,耗费时间于精力,于是Java语言提供了数组来存储相同类型的数据,现在要存储80名学生的成绩,只需一个数组就可以了。而当遇到有些代码需要反复使用的情况时,可以将代码声明成一个方法,以供程序反复调用。

3.1 数组

在Java中数组是相同类型元素的集合,可以存放上千万个数据,在一个数组中,数组元素的类型是唯一的,即一个数组中只能存储同一种数据类型的数据,而不能存储多种数据类型的数据,数组一旦定义好就不可以修改长度,因为数组在内存中所占大小是固定的,所以数组的长度不能改变,如果要修改就必须重新定义一个新数组或者引用其他的数组,因此数组的灵活性较差。

3.1.1 数组的定义

数组是可以保存一组数据的一种数据结构,它本身也会占用一个内存地址,因此数组是引用类型。定义数组的语法格式如下:

数据类型[] 数组名;

对于数组的声明也可用另外一种形式,其语法格式如下:

数据类型 数组名[];

上述示例中分别用两种不同的语法格式声明数组,其中"[ ]"它是一维数组的标识,可放置在数组名前面也可以放在数组名后面,面向对象更侧重放在前面,保留放在后面是为了迎合C程序员,在这里推荐使用第一种。下面将演示不同数据类型的数组声明,具体示例如下:

int[] a;// 声明一个int类型的数组

double b[];// 声明一个double类型的数组

上述示例中分别声明了一个int类型的数组a与double类型的数组b,其数组名是用来统一这组相同数据类型的元素名称,数组中数组名的命名规则和变量相同。

3.1.2 数组的初始化

在Java程序开发中,使用数组之前都会对其进行初始化,这是因为数组是引用类型,声明数组只是声明一个引用类型的变量,并不是数组对象本身,只要让数组变量指向有效的数组对象,程序中就可使用该数组变量来访问数组元素。所谓数组初始化就是让数组名指向数组对象的过程,该过程主要分为两个步骤,一是对数组对象进行初始化,即为数组中的元素分配内存空间和赋值,二是对数组名进行初始化,即为数组名赋值为数组对象的引用。

通过两种方式可对数组进行初始化,即静态初始化和动态初始化,下面将演示两种方式的具体语法。

1.静态初始化

静态初始化是指由程序员在初始化数组时为数组每个元素赋值,由系统决定数组的长度。

数组的静态初始化有2种方式,具体示例如下:

array = new int[]{1,2,3,4,5};

int[] array = {1,2,3,4,5};

上面的2种方式都可以实现数组的静态初始化,其中花括号包含数组元素值,元素值之间用逗号","分隔。此处注意只有在定义数组的同时执行数组初始化才支持使用简化的静态初始化。为了简便,建议采用第2种方式。

2.动态初始化

动态初始化是指由程序员在初始化数组时指定数组的长度,由系统为数组元素分配初始值。

数组动态初始化,具体示例如下:

int[] array = new int[10];// 动态初始化数组

上述示例中的格式会在数组声明的同时分配一块内存空间供该数组使用,其中数组长度是10,由于每个元素都为int型数据类型,因此上例中数组占用的内存共有10*4=40个字节。此外,动态初始化数组时,其元素会根据它的数据类型被设置为默认的初始值。本例数组中每个元素的默认值为0,其他常见的数据类型默认值如表3.1所示。

表3.1 数据类型默认值表

3.1.3 数组的常用操作

1.访问数组

在Java中,数组对象有一个length属性,用于表示数组的长度,所有类型的数组都是如此。

获取数组的长度,其语法格式如下:

数组名.length

接下来用length属性获取数组的长度,具体示例如下:

int[] list = new int[10];// 定义一个int类型的数组

int size = list.length;// size = 10,数组的长度

数组中的变量又称为元素,考虑到一个数组中的元素可能会很多,为了便于区分它们,每个元素都有下标(索引),下标从0开始,如在 int[] list = new int[10]中,list[0]是第1个元素,list[1]是第2个元素,list[9]是第10个元素,也就是最后一个元素。因此,假如数组list有n个元素,那么list[0]是第一个元素,而list[n-1]则是最后一个元素。

如果下标值小于0,或者大于等于数组长度,编译程序不会报任何错误,但运行时出现异常:ArrayIndexOutOfBoundsException : N,数组下标越界异常,N表示试图访问的数组下标。

2.数组遍历

数组的遍历是指依次访问数组中的每个元素。接下来演示循环遍历数组,如例3-1所示。

例3-1 TestArrayTraversal.java

1 public class TestArrayTraversal {

2 public static void main(String[] args) {

3 int[] list = {1, 2, 3, 4, 5};// 定义数组

4 for (int i = 0; i < list.length; i++) {// 遍历数组元素

5 System.out.println(list[i]); // 索引访问数组

6 }

7 }

8 }

程序的运行结果如图3.1所示。

图3.1 例3-1运行结果

例3-1中,声明并静态初始化一个int类型的数组,利用for循环中的循环变量充当数组的索引,依次递增索引,从而遍历数组元素。

3.数组最大值和最小值

通过前面已经掌握的知识,用数组的基本用法与流程控制语句的使用来实现得到数组中的最大值和最小值的例子,首先把数组的第一个数赋值给变量max和min,分别表示最大值和最小值,再依次判断数组的其他数值的大小,判断当前值是否是最大值或最小值,如果不是进行替换,最后输出最大值和最小值。接下来通过一个案例来获取数组的最大值和最小值,如例3-2所示。

例3-2 TestMostValue.java

1 public class TestMostValue {

2 public static void main(String[] args) {

3 // 定义数组

4 int[] score = {88, 62, 12, 100, 28};

5 int max = 0;// 最大值

6 int min = 0;// 最小值

7 max = min = score[0];// 把第一个元素值赋给max和min

8 for (int i=1; i<score.length; i++) {

9 if (score[i] > max) {// 依次判断后面元素值是否比max大

10 max = score[i];// 如果大,则修改max的值

11 }

12 if (score[i] < min) {// 依次判断后面元素值是否比min小

13 min = score[i];// 如果小,则修改min的值

14 }

15 }

16 System.out.println("最大值:"+ max);

17 System.out.println("最小值:"+ min);

18 }

19 }

程序的运行结果如图3.2所示。

图3.2 例3-2运行结果

例3-2中,在main()方法声明并静态初始化了score数组,并定义了两个变量max与min,分别用来存储最大值与最小值,接着把score数组第一个元素score[0]分别赋值到max与min中,然后使用for循环对数组进行遍历,接下来通过一个图例来分析min和max的比较过程,如图3.3所示。

图3.3 数组最大值和最小值比较过程

图3.3中,max与min存储的数值都是score数组的第一个元素88,在遍历过程中只要遇到比max值还大的元素,就将该元素赋值给max,遇到比min还小的元素,就将该元素赋值给min。

4.数组排序

数组排序是指数组元素按照特定的顺序排列。在实际应用中,经常需要对数据排序,如:老师对学生的成绩排序。数组排序有多种算法,本节介绍一种简单的排序算法——冒泡排序。这种算法是不断的比较相邻的两个元素,较小的向上冒,较大的向下沉,排序过程如同水中气泡上升,即两两比较相邻元素,反序则交换,直到没有反序的元素为止,如例3-3所示。

例3-3 TestBubbleSort.java

1 public class TestBubbleSort {

2 public static void main(String[] args) {

3 int[] array = {88, 62, 12, 100, 28};// 定义数组

4 // 外层循环控制排序轮数

5 // 最后一个元素,不用再比较

6 for (int i=0; i < array.length-1; i++) {

7 // 内层循环控制元素两两比较的次数

8 // 每轮循环沉底一个元素,沉底元素不用再参加比较

9 for (int j = 0; j < array.length - 1 - i; j++) {

10 // 比较相邻元素

11 if (array[j] > array[j+1]) {

12 // 交换元素

13 int tmp = array[j];

14 array[j] = array[j+1];

15 array[j+1] = tmp;

16 }

17 }

18 // 打印每轮排序结果

19 System.out.print("第"+(i+1)+"轮排序:");

20 for (int j=0; j<array.length; j++) {

21 System.out.print(array[j] + "\t");

22 }

23 System.out.println();

24 }

25 System.out.print("最终排序 :");

26 for (int i=0; i<array.length; i++) {

27 System.out.print(array[i] + "\t");

28 }

29 System.out.println();

30 }

31 }

程序的运行结果如图3.4所示。

图3.4 例3-3运行结果

例3-3中,通过嵌套循环实现了冒泡排序。其中,外层循环是控制排序的轮数,每一轮可以确定一个元素位置,由于最后一个元素不需要进行比较,因此外层循环的轮数为array.length-1。内层循环控制每轮比较的次数,每轮循环沉底一个元素,沉底元素不用再参加比较,因此,内层循环的次数为array.length-1-i。内层循环的次数被作为数组的索引,索引循环递增,实现相邻元素依次比较,如果当前元素小于后一个元素,则交换两个元素的位置,如图3.5所示。

图3.5 冒泡排序过程

例3-3中,第11~16行代码实现了数组中两个元素的交换。首先定义一个临时变量tmp用于保存array[j]的值,然后用array[j+1]的值覆盖array[j],最后将tmp的值赋给array[j+1],从而实现了两个元素交换。

3.1.4 数组的内存原理

数组是引用数据类型,因此数组变量就是一个引用变量,通常被存储在栈(Stack)内存中。数组初始化后,数组对象被存储在堆(Heap)内存中的连续内存空间,而数组变量存储了数组对象的首地址,指向堆内存中的数组对象。接下来演示一维数组在内存中的存储原理,如图3.6所示。

图3.6 数组存储原理

在Java中,数组一旦数组初始化完成,数组元素的内存空间分配即结束,程序只能改变数组元素的值,而无法改变数组的长度。但可以改变一个数组变量所引用的数组,从而造成数组长度可变的假象。同理,在复制数组时,直接使用赋值语句不能实现数组的复制,这样做是两个数组引用变量指向同一个数组对象。如例3-4所示。

例3-4 TestCopyArray.java

1 public class TestCopyArray {

2 public static void main(String[] args) {

3 int[] x = {88, 62, 12, 100, 28};

4 // 直接用赋值语句复制数组,赋的是数组的首地址

5 int[] y = x;

6 System.out.println(x);// 打印源数组名

7 System.out.println(y);// 打印目的数组名

8 x[0] = 22;// 修改源数组

9 System.out.println(y[0]);// 访问目的数组

10 }

11 }

程序的运行结果如图3.7所示。

图3.7 例3-4运行结果

图3.7中,从程序运行结果可发现,数组变量保存的就是数组首地址。通过赋值运算符复制数组,复制的是数组的首地址,原数组名和目的数组名都指向实际的数组内存单元,因此它们操作的是同一数组,所以不能通过赋值运算符来复制数组,如图3.8所示。

图3.8 复制数组原理

可以使用循环来复制每一个元素或者使用System类的arraycopy()方法以及使用数组的clone()方法来复制数组。

3.1.5 二维数组

虽然一维数组可以处理一些简单的一维模型,但在实际应用中模型却不止一维,比如棋盘,如图3.9所示。

图3.9 棋盘图

图3.9中有10行9列,因此它需要用二维模型来表示。Java中用二维数组来模拟二维模型,因此,二维数组的第一维可以表示棋盘的10行,第二维可以表示棋盘的9列。假定数组名为a,该棋盘用二维数组则可以表示为a[10][9],红帅的位置在第1行第5列,该位置就表示为a[0][4],其他位置依此类推。接下来详细讲解二维数组的声明及使用。

1.二维数组

二维数组可以看成以数组为元素的数组,常用来表示表格或矩形。二维数组的声明、初始化与一维数组类似。

二维数组的声明,其语法格式如下:

int[][] array;

int array[][];

二维数组动态初始化语法格式:

array=new int[3][2];// 动态初始化3*2的二维数组

array[0]={1, 2};// 初始化二维数组的第一个元素

array[1]={3, 4};// 初始化二维数组的第二个元素

array[2]={5, 6};// 初始化二维数组的第三个元素

上述示例定义了一个3行2列的二维数组,即二维数组的长度为3,每个二维数组的元素是一个长度为2的一维数组,下面演示二维数组元素的存储形式,如图3.10所示。

图3.10 二维数组动态初始化

二维数组静态初始化语法格式:

array=new int[][]{

{1},

{2, 3},

{4}

};

对于二维数组的静态初始化也可用另一种形式,具体示例如下:

int[][] array={

{1},

{2, 3},

{4}

};

需要注意的是静态初始化由系统指定数组长度,不能进行手动指定,下面演示的是错误的静态初始化。

array=new int[3][3]{ // 非法,静态初始化由系统指定数组长度,不能手动指定

{1, 2, 3},

{4, 5, 6},

{7, 8, 9}

};

下面演示二维数组元素的存储形式,如图3.11所示。

图3.11 二维数组静态

二维数组的每个元素是一个一维数组,二维数组array的长度是数组array的元素的个数,可由array.length得到,元素array[i]是一个一维数组,其长度可由array[i].length得到。如例3-5所示。

例3-5 TestTwoDimensionalArray.java

1 public class TestTwoDimensionalArray {

2 public static void main(String[] args) {

3 // 定义二维数组,3行*3列

4 int[][] array = new int[3][3];

5 // 动态初始化二维数组

6 // array.length获取二维数组的元数个数

7 // array[i].length获取二维数组元素指向的一维数组个数

8 for (int i = 0; i < array.length; i++) {

9 for (int j = 0; j < array[i].length; j++) {

10 array[i][j] = 3*i+j+1;

11 }

12 }

13 // 打印二维数组

14 for (int i = 0; i < array.length; i++) {

15 for (int j = 0; j < array[i].length; j++) {

16 System.out.print(array[i][j] + "\t");

17 }

18 System.out.println();

19 }

20 }

21 }

程序的运行结果如图3.12所示。

图3.12 例3-5运行结果

例3-5中,定义了一个3*3的二维数组,用嵌套for循环为二维数组赋值和打印。由此可发现,每多一维,嵌套循环的层数就多一层,维数越高的数组其复杂度也就越高。

2.锯齿数组

二维数组中的每一行就是一个一维数组,因此,各行的长度就可以不同。这样的数组称为锯齿数组。创建锯齿数组时,可以只指定第一个下标,此时二维数组的每个元素为空,因此必须为每个元素创建一维数组。如例3-6所示。

例3-6 TestJaggedArray.java

1 public class TestJaggedArray{

2 public static void main(String[] args) {

3 // 静态初始化锯齿数组

4 int[][] array= {

5 {1, 2, 3, 4, 5},

6 {2, 3, 4, 5},

7 {3, 4, 5},

8 {4, 5},

9 {5}

10 };

11 // 动态初始化锯齿数组

12 int[][] x = new int[5][];

13 x[0] = new int[5];

14 x[1] = new int[4];

15 x[2] = new int[3];

16 x[3] = new int[2];

17 x[4] = new int[1];

18 // 为数组赋值

19 for (int i = 0; i < x.length; i++) {

20 for (int j = 0; j < x[i].length; j++) {

21 x[i][j] = array[i][j];

22 }

23 }

24 // 打印二维数组

25 for (int i = 0; i < x.length; i++) {

26 for (int j = 0; j < x[i].length; j++) {

27 System.out.print(x[i][j] + "\t");

28 }

29 System.out.println();

30 }

31 }

32 }

程序的运行结果如图3.13所示。

图3.13 例3-6运行结果

例3-6中,首先静态初始化锯齿数组array和动态初始化数组x,然后通过嵌套for循环将锯齿数组array的数组元素值赋值给锯齿数组x的数组元素,最后通过嵌套for循环将锯齿数组x的数组元素打印出来。通过该示例,可以了解锯齿数据的基本形态和使用方法。

3.2 方法

方法(method)是一段可重用的代码,为执行一个操作组合在一起的语句集合,用于解决特定问题。在程序中多次重复使用相同的代码,重复地编写及维护比较麻烦,因此可以将此部分代码定义成一个方法,以供程序反复调用。

3.2.1 方法的定义

Java中的方法定义在类中,一个类可以声明多个方法。方法的定义由方法名、参数、返回值类型以及方法体组成。

如何定义方法,其语法格式如下:

修饰符 返回值类型 方法名([参数类型 参数名1,参数类型 参数名2,…]) {

方法体

return 返回值;

}

定义方法时需注意以下几点:

· 修饰符:方法的修饰符比较多,有对访问权限进行限定的,有静态修饰符static,还有最终修饰符final等。

· 返回值类型:限定返回值的类型。

· 参数类型:限定调用方法时传入参数的数据类型。

· 参数名:是一个变量,用于接收调用方法时传入的数据。

· return:关键字,用于结束方法以及返回方法指定类型的值。

· 返回值:被return返回的值,该值返回给调用者。

接下来演示方法声明,如图3.14所示。

图3.14 方法声明

图3.14中,方法头中声明的变量称为形式参数,简称形参。当调用方法时,给参数传入的值称为实际参数,简称实参。形参列表是指形参的类型、顺序和数量。方法不需要任何参数,则形参列表为空。

方法可以有返回值,返回值必须为方法声明的返回值类型。如果方法没有返回值,则返回类型为void,return语句可以省略。如例3-7所示。

例3-7 TestVoidMethod.java

1 public class TestVoidMethod {

2 public static void main(String[] args) {

3 int score = 78;

4 // 调用void方法

5 printGrade(score);

6 // 声明变量接收方法的返回值

7 char ret = getGrade(score);

8 System.out.println(ret);

9 }

10 // void方法

11 public static void printGrade(double score) {

12 if (score < 0 || score > 100) {

13 System.out.println("成绩输入错误!");

14 return;

15 }

16 if (score >= 90.0) {

17 System.out.println('A');

18 } else if (score >= 80.0) {

19 System.out.println('B');

20 } else if (score >= 70.0) {

21 System.out.println('C');

22 } else if (score >= 60.0) {

23 System.out.println('D');

24 } else {

25 System.out.println('F');

26 }

27 }

28 // 带返回值的方法

29 public static char getGrade(double score) {

30 if (score >= 90.0) {

31 return 'A';

32 } else if (score >= 80.0) {

33 return 'B';

34 } else if (score >= 70.0) {

35 return 'C';

36 } else if (score >= 60.0) {

37 return 'D';

38 } else {

39 return 'F';

40 }

41 }

42 }

程序的运行结果如图3.15所示。

图3.15 例3-7运行结果

例3-7中,定义了两个方法printGrade()和getGrade(),其中printGrade()方法是用void修饰的,不返回任何值。而getGrade()方法有返回值。用void修饰的方法不需要return语句,但它能用于终止方法返回到方法的调用者,控制程序的流程。当成绩不在0~100之间,调用printGrad()方法,程序将打印"成绩输入错误!",执行return语句后,它后面的语句将不再执行,程序直接返回到调用者。

3.2.2 方法的调用

方法在调用时执行方法中的代码,因此要执行方法,必须调用方法。如果方法有返回值,通常将方法调用作为一个值来处理。如果方法没有返回值,方法调用必须是一条语句。具体示例如下:

int large = max(3, 4);// 将方法的返回值赋给变量

System.out.println(max(3,4));// 直接打印方法的返回值

System.out.println("Hello World!");// println方法没有返回值,必须是语句

如果方法定义中包含形参,调用时必须提供实参。实参的类型必须与形参的类型兼容,实参顺序必须与形参的顺序一致。实参的值传递给方法的形参,称为值传递(pass by value),方法内部对形参的修改不影响实参值。当调用方法时,程序控制权转移至被调用的方法。当执行return语句或到达方法结尾时,程序控制权转移至调用者。如例3-8所示。

例3-8 TestCallMethod.java

1 public class TestCallMethod {

2 public static void main(String[] args) {

3 int n = 5;

4 int m =2;

5 System.out.println("before main\t:n="+ n + ", m=" + m);

6 swap(n, m);

7 System.out.println("end main\t:n="+ n + ", m=" + m);

8 }

9 // 交换两个数

10 public static void swap(int n, int m) {

11 System.out.println("before swap\t:n="+ n + ", m=" + m);

12 int tmp = n;

13 n = m;

14 m = tmp;

15 System.out.println("end swap\t:n="+ n + ", m=" + m);

16 }

17 }

程序的运行结果如图3.16所示。

图3.16 例3-8运行结果

例3-8中,当调用swap方法时,程序将实参n、m的值传递给形参的n、m,然后程序将控制流程转向swap方法,执行swap方法时,交换形参n和m的值,当swap方法执行完毕时,系统释放形参并将控制权返还给它的调用者main方法。因此,swap方法不能交换实参n和m的值。

每当调用一个方法时,JVM将创建一个栈帧,用于保存该方法的形参和变量。当方法调用结束返回到调用者时,JVM释放相应的栈帧。每一个方法从调用开始到执行完成的过程,就对应着一个栈帧在JVM中从入栈到出栈的过程。接下来演示堆栈中调用方法的栈帧,如图3.17所示。

图3.17 栈帧

图3.17中,main方法调用swap方法时,调用者main方法的栈帧不变,程序先将为swap方法创建一个新的栈帧,用于保存形参n、m和局部变量tmp的值,再将实参值传递给形参,并保存在该栈帧中,方法内部操作的都是该方法栈帧中的值。当swap方法执行结束时,其对应的栈帧将被释放。

3.2.3 方法的重载

方法重载(overloading)是指方法名称相同,但形参列表不同的方法。调用重载的方法时,Java编译器会根据实参列表寻找最匹配的方法进行调用。如例3-9所示。

例3-9 TestOverload.java

1 public class TestOverload {

2 public static void main(String[] args) {

3 // 调用max(int, int)方法

4 System.out.println("3和8的最大值:" + max(3, 8));

5 // 调用max(double, double)方法

6 System.out.println("3.0和8.0的最大值:" + max(3.0, 8.0));

7 // 调用max(double, double, double)方法

8 System.out.println("3.0、5.0和8.0的最大值:" + max(3.0, 5.0, 8.0));

9 // 调用max(double, double)方法

10 System.out.println("3和8.0的最大值:" + max(3, 8.0));

11 }

12 // 返回两个整数的最大值

13 public static int max(int num1, int num2) {

14 int result;

15 if (num1 > num2)

16 result = num1;

17 else

18 result = num2;

19 return result;

20 }

21 // 返回两个浮点数的最大值

22 public static double max(double num1, double num2) {

23 double result;

24 if (num1 > num2)

25 result = num1;

26 else

27 result = num2;

28 return result;

29 }

30 // 返回三个浮点数的最大值

31 public static double max(double num1, double num2, double num3) {

32 return max(max(num1, num2), num3);

33 }

34 }

程序的运行结果如图3.18所示。

图3.18 例3-9运行结果

图3.18中,从程序运行结果结果可发现,max(3, 8)调用的是max(int, int)方法,max(3.0, 8.0)调用的是max(double, double)方法,max(3.0, 5.0, 8.0)调用的是max(double, double, double)方法。而且max(3, 8.0)也能被执行,实参3被自动转换为double类型,然后调用max(double, double)方法。

为什么max(3, 8)不会调用max(double, double)方法呢?其实,max(double, double)和max(int, int)与max(3, 8)都是可能匹配。当调用方法时,Java编译器会根据实参的个数和类型寻找最准确的方法进行调用。因为max(int, int)比max(double, double)更精确,所以max(3, 8)会调用max(int, int)。

调用一个方法时,出现两个或多个可能的匹配时,编译器无法判断哪个是最精确的匹配,则会产生编译错误,称为歧义调用(ambiguous invocation)。如例3-10所示。

例3-10 TestAmbiguousInvocation.java

1 public class TestAmbiguousInvocation {

2 public static void main(String[] args) {

3 System.out.println(max(3, 8));

4 }

5 // 返回整数和浮点数的最大值

6 public static double max(int num1, double num2) {

7 if (num1 > num2)

8 return num1;

9 else

10 return num2;

11 }

12 // 返回浮点数和整数的最大值

13 public static double max(double num1, int num2) {

14 if (num1 > num2)

15 return num1;

16 else

17 return num2;

18 }

19 }

程序的运行结果如图3.19所示。

图3.19 例3-10运行结果

图3.19中,程序编译错误并提示"对max的引用不明确",原因在于max(int, double)和max(double, int)与max(3, 8)都匹配,从而产生歧义,导致编译错误。

方法只能根据参数列表(参数类型、参数顺序和参数个数)进行重载,而不能通过修饰符或返回值来重载。

3.2.4 方法的递归

方法的递归是指一个方法直接或间接调用自身的行为,递归必须要有结束条件,否则会无限地递归。递归用于解决使用简单循环难以实现的问题。如例3-11所示。

例3-11 TestRecursion.java

1 public class TestRecursion {

2 public static void main(String[] args) {

3 System.out.println("4的阶乘:"+ fact(4));

4 }

5 /*

6 计算阶乘

7 阶乘计算公式:

8 0!=1

9 n! = n * (n-1)!; n>0

10 */

11 public static long fact(int n) {

12 // 结束条件

13 if (n ==0)

14 return 1;

15 return n * fact(n - 1);

16 }

17 }

程序的运行结果如图3.20所示。

图3.20 例3-11运行结果

例3-11中,定义fact()方法用于计算阶乘,方法是将数学上的阶乘公式转换为代码。当用n=0调用该方法,程序立即返回结果,这种简单情况称为结束条件,如果没有终止条件,就会出现无限递归。当用n>0调用该方法,就将这个原始问题分解成计算n-1的阶乘的子问题,持续分解,直到问题达到最终条件为止,就将结果返回给调用者。然后调用者进行计算并将结果返回给它自己的调用者,过程持续进行,直到结果返回原始调用者为止。原始问题就可以将fact(n-1)的结果乘以n得到。调用过程称为递归调用,如图3.21所示。

图3.21 递归原理

图3.21中,描述了例3-11中的递归调用过程,整个递归过程中fact()方法被调用了5次,每次调用n的值都会递减,当n的值为0时,所有递归调用的方法都会以相反的顺序相继结束,所有的返回值会进行累乘,最终得到结果24。

3.3 数组的引用传递

在方法调用时,参数按值传递,即用实参的值去初始化形参。对于基本数据类型,形参和实参是两个不同的存储单元,因此方法执行中形参的改变不影响实参的值;对于引用数据类型,形参和实参存储的是引用(内存地址),都指向同一内存单元,在方法执行中,对形参的操作实际上就是对实参数的操作,即对执行内存单元的操作,因此,方法执行中形参的改变会影响实参。

向方法传递数组时,方法的接收参数必须是符合其类型的数组;从方法返回数组时,返回值类型必须明确的声明其返回的数组类型。数组属于引用类型,所以在执行方法中对数组的任何操作,结果都将保存下来。如例3-12所示。

例3-12 TestRefArray.java

1 public class TestRefArray {

2 public static void main(String[] args) {

3 int[] array = {1, 3, 5};

4 rev(array);// 将数组元素反序

5 System.out.print("数组的反序:");

6 printArray(array);// 打印反序后的数组

7 int[] copy = copy(array);// 复制数组

8 array[0] = 9;// 修改源数组

9 System.out.print("修改源数组:");

10 printArray(array);// 打印源数组

11 System.out.print("复制的数组:");

12 printArray(copy);// 打印复制数组

13 }

14 // 将数组元素反序

15 public static void rev(int[] pa) {

16 for (int i = 0, j = pa.length-1; i < j; i++, j--) {

17 int tmp = pa[i];

18 pa[i] = pa[j];

19 pa[j] = tmp;

20 }

21 }

22 // 复制数组元素

23 public static int[] copy(int[] pa) {

24 int[] newarray = new int[pa.length];

25 for (int i = 0; i < pa.length; i++) {

26 newarray[i] = pa[i];

27 }

28 return newarray;

29 }

30 // 打印数组元素

31 public static void printArray(int[] pa) {

32 for (int i=0; i<pa.length; i++) {

33 System.out.print(pa[i] + "\t");

34 }

35 System.out.println();

36 }

37 }

程序的运行结果如图3.22所示。

图3.22 例3-12运行结果

例3-12中,定义了3个方法rev()、printArray()和copy(),其中rev()反序一个数组,printArray()打印数组元素,而copy()复制一个数组。因为数组是引用类型,所以,在方法中修改数组,其结果也会保存下来。

接下来演示rev()反序数组的过程,如图3.23所示。

图3.23 传递数组

图3.23中,声明的array数组元素是"1、3、5"。当将此数组传递到了rev()方法中时,使用形参pa接收,也就是说此时的array实际上是将使用权传递给了rev()方法,为数组起了一个别名pa,然后在rev()方法中通过pa进行元素反序操作。rev()方法执行完毕之后,局部变量pa被释放,但是对于数组元素的改变却保留了下来,这就是参数是数组的引用传递的过程。

接下来演示copy()复制数组的过程,如图3.24所示。

图3.24 返回数组

图3.24中,声明的array数组元素是"1、3、5"。当将此数组传递到了copy()方法时,使用形参pa接收,同时在该方法中创建新数组newarray,然后通过pa将数组元素值复制到newarray数组。copy()方法执行完毕之后,局部变量pa和newarray被释放,但是newarray被传递到copy数组,这就是返回值是数组的引用传递的过程。

3.4 本章小结

通过本章的学习,能够掌握Java数组与方法的使用。重点要熟悉的是在实际开发中,要完成一个复杂的程序,可将一个复杂的程序简化成若干个方法来实现,如遇到需要定义多个相同类型的变量时,可以使用数组。

3.5 习题

1.填空题

(1) 当调用方法时,给参数传入的值称为实际参数,简称 。

(2) 在Java中,当一个方法不需要返回数据时返回类型必须是 。

(3) 一个数组中只能存储同一种 的数据。

(4) 数组的元素可通过 来访问。

(5) 是指方法名称相同,但形参列表不同的方法。

2.选择题

(1) 定义了一维int型数组a[10]后,下面错误的引用是( )。

A.a[0]=1;B.a[10]=2;

C.a[0]=5*2;D.a[1]=a[2]*a[0];

(2) 数组对象在Java中储存在( )中。

A.栈B.队列

C.堆 D.链表

(3) 方法的( )是指一个方法直接或间接调用自身的行为。

A.传递B.递归

C.访问 D.方法

(4) 数组a的第三个元素表示为( )。

A.a[2] B.a(3)

C.a(2) D.a[3]

(5) 关于数组作为方法的参数时,向方法传递的是( )。

A.数组的元素 B.数组的引用

C.数组的栈地址 D.数组自身

3.思考题

(1) 请简述什么时候为数组分配内存?

(2) 请简述数组一旦被创建,大小能不能改变?

(3) 请简述实参是如何传递给方法的?

(4) 请简述什么是方法的重载?

4.编程题

(1) 编写方法返回两个整数的最大公约数和最小公倍数。

(2) 编写程序计算字符数组中每个字符出现的次数。

标签: #java二维数组的输出 #java数组的使用方法 #java数组分成两组和相同 #java数组交换元素方法 #java比较数组中的元素大小怎么写