第 5 章 方法 结构化语言(如C语言)实现软件系统模块化的最小单位是函数(方法), 软件 系统由很多函数构成,函数之间存在复杂的调用关系。Java作为纯面向对象语 言,实现软件系统模块化的最小单位是类,软件系统由很多类构成类之间存在继 承、依赖、关联等关系。Java程序中是否还需要方法?Java方法的定义与使用是 否与C等结构化语言一样? 作为一门优秀的程序设计语言,Java方法是否有新特 征? 本章将为读者一一解答这些问题。 本章内容 (1)传统方法。 (2)形参长度可变方法。 (3)方法重载。 (4)递归方法。 ..5.1 传统方法 方法定义5.1 及调用1.方法的概念 方法是一段可重复调用的代码块。软件开发中,把完成相同功能的代码块定 义为方法能提高软件开发效率以及软件可维护性。例如,学生成绩管理系统中, 任课教师需要对课程成绩排序,学生辅导员和教学秘书也需要对课程成绩排序。 如果3个子系统分别编写学生成绩排序代码会显得非常烦琐,并且修改也比较困 难,如果改进了排序算法,需要在3个不同位置同时修改。如果把成绩排序功能定 义成一个方法,只要在3个不同位置调用即可。 第一种方式,如图5-1(a)所示,改进成绩排序算法,需要修改3个位置的代码 块;第二种方式,如图5-1(b)所示,仅需要修改一个位置的代码块。 有些书把方法称为函数,与本书的方法是相同概念,只是称呼方式不一样。 1.定义及调用传统方法 5.2 Java提供了定义方法的多种形式,定义传统方法需要确定返回值类型、方法 名和参数等三要素。 第5章 方法 71 图5-1 教务管理系统成绩排序的两种实现方式 方法定义及 调用程序 【语法格式5-1】 定义传统方法。 public static 返回值类型方法名(类型形参1,类型形参2,…){ 方法体; //方法主体 [return 表达式]; //方法返回值 } 上面的语法格式中,方法声明前增加了publicstatic关键字,表示该方法能直接被main() 方法调用。 Java调用传统方法的形式与C语言一样。 【语法格式5-2】 调用传统方法。 方法名(实参1,实参2,…) 下面代码段,第2行定义方法max(intx,inty)返回两个整数的较大者,第6行调用 max(20,30)方法。 1 public class Demo { 2 public static int max(int x,int y) { //定义方法max() 3 return x>y? x:y; 4 } 5 public static void main(String[] args) { 6 System.out.println(max(20,30) //调用方法max(); 7 } 8 } 程序案例5-1演示了传统方法的定义与调用。第5~17行定义了方法,该方法返回值 72 Java编程基础(微课版) 图5-2 程序案例5-1 运行结果 类型void,方法名printBook(),形参String类型。第20~25 行定义了方法,该方法返回值类型double,方法名total(),形 参是double类型的一维数组。第27行调用方法printBook(), 实参字符串”老子”,第29行调用方法total(),实参是double类型 一维数组score。程序运行结果如图5-2所示。 【程序案例5-1】 定义及调用传统方法。 1 package chapter05; 2 public class Demo0501 { 3 //=========自定义传统方法1========== 4 //返回值类型,方法名,形参 5 public static void printBook(String name) { 6 if (name.equals("孔子")) { 7 System.out.println("孔子的代表作《论语》"); 8 return; 9 } else if (name.equals("老子")) { 10 System.out.println("老子的代表作《道德经》"); 11 return; 12 } else if (name.equals("司马光")) { 13 System.out.println("司马光的代表作《资治通鉴》"); 14 } else { 15 System.out.println("数据库中不存在" + name + " 的作品"); 16 } 17 } 18 //=========自定义传统方法2========== 19 //返回值类型,方法名,形参 20 public static double total(double[] arr) { //方法签名 21 double sum = 0; 22 for (double d : arr) //foreach 语句 23 sum = sum + d; 24 return sum; 25 } 26 public static void main(String[] args) { 27 printBook("老子"); //调用方法printBook() 28 double[] score = { 88.5, 92.5, 78 }; 29 System.out.println("成绩总分: " + total(score)); //调用方法total() 30 } 31 } 5.1.3 参数传递方式 C/C++语言中,方法的实参向方法形参传递数据有两种方式:传递值和传递指针。为 了增强系统安全性,Java取消了指针,但提供了引用数据类型,如数组、类、接口等。在Java 语言中,方法的实参向形参传递参数也有两种方式:传递基本数据类型和传递引用类型。 如果传递引用类型(相当于C/C++传递指针),形参的改变影响实参。 程序案例5-2演示了实参向形参传递引用类型(数组)。第6行修改了形参数组arr位 置location的值,这种改变影响了第11行实参数组tempArr。程序运行结果如图5-3所示。 第5章 方法 73 图5-3 程序案例5-2运行结果 【程序案例5-2】 实参向形参传递数组。 1 package chapter05; 2 import java.util.Arrays; 3 public class Demo0502 { 4 //把数组arr 中location 位置元素的值修改为newValue 5 public static void update(int[] arr, int location, int newValue) { 6 arr[location] = newValue; //修改location 位置的元素 7 String str=Arrays.toString(arr); 8 System.out.println("形参数组:\t\t"+str); 9 } 10 public static void main(String[] args) { 11 int tempArr[] = { 1, 3, 5 }; //静态初始化定义数组 12 update(tempArr, 1, 100); //传递数组(引用,相当于指针) 13 String strTempArr = Arrays.toString(tempArr); 14 System.out.print("原始数组(实参):\t"+strTempArr); 15 } 16 } 程序说明:运行结果显示形参与实参一致,表示形参的改变影响了实参。 .. 5.2 形参长度可变方法 5.2.1 形参长度可变方法的概念 开发软件项目时,可能存在调用方法时才能确定形参数量的情况,由于传统方法的形参 数量是固定的,不能满足该需求。满足用户需求是软件系统的不懈追求。例如,total()方法 返回若干整数之和,若干整数可能以整型数组的形式存在(int[]arr),也可能是数量不固定 的零散整数(形式如3,4,7或者18,14,17,13,19)。 Java1.5之后,提供了定义形参长度可变方法的能力,允许为方法指定数量不固定的 形参。 5.2.2 定义形参长度可变方法 【语法格式5-3】 定义形参长度可变方法。 public static 返回值类型方法名(类型形参1,类型形参2, 类型…形参) { 方法体; //方法主体 [return 表达式;] //方法返回值 } 定义方法时,最后一个形参类型后增加省略号,表明该形参能接受个数不确定的实参, 74 Java编程基础(微课版) 多个实参被当成数组传入。 定义形参长度可变方法需注意两点:①方法中只能定义一个可变形参;②如果这个方 法还有其他形参,它们要放在可变形参之前。 编译时,编译器会把最后一个可变形参转换为一个数组形参,并在编译的class文件中 标上记号,表明该方法的实参个数可变。 例如,下面代码段定义了形参长度可变方法total(),表明调用该方法时,可向第3个参 数传入int类型的一维数组,也可传入数量不确定的若干整数。 1 //计算数组arr 中begin~end 的元素之和 2 //最后一个参数arr 为可变形参 3 public static int total(int begin, int end, int… arr) { 4 int sum = 0; 5 if (!(begin >= 0 && end <= arr.length)) 6 return sum; 7 for (int i = begin; i < end; i++) 8 sum += arr[i]; 9 return sum; 10 } 5.2.3 调用形参长度可变方法 调用形参长度可变方法有两种形式:①实参是对应类型的一维数组;②实参是若干对 应类型的变量或常量。 【语法格式5-4】 调用形参长度可变方法。 方法名(实参1, 实参2, 数组名) 或者 方法名(实参1, 实参2, 实参x1, 实参x2,…,实参n) 图5-4 程序案例5-3运行结果 程序案例5-3演示了调用形参长度可变方法。第15行 定义了形参长度可变方法total(),main()方法中采用两种 形式调用total();第5、6行向total()方法传入的实参是一 维数组;第8行前面两个参数表示begin和end,第3~6个 参数(9,8,7,6)转换成数组后传给方法total()的第3个形 参。程序运行结果如图5-4所示。 【程序案例5-3】 调用形参长度可变方法。 1 package chapter05; 2 public class Demo0503 { 3 public static void main(String[] args) { 4 int[] arr = { 3, 4, 5, 6 }; 5 int sum1 = total(0, arr.length, arr); // 第 一种调用,实参是数组 6 int sum2 = total(0, 3, new int[] { 4, 5, 6, 7, 8 }); //第一种调用,实参是数组 7 //begin end 不确定实参 8 int sum3 = total(0, 2, 9, 8, 7, 6); //第二种调用,不确定个数的实参:9,8,7,6 第5章 方法 75 9 System.out.println("sum1="+sum1); 10 System.out.println("sum2="+sum2); 11 System.out.println("sum3="+sum3); 12 } 13 //计算数组arr 中begin~end 的元素之和 14 //最后一个参数arr 为可变形参 15 public static int total(int begin, int end, int… arr) { 16 int sum = 0; 17 if (!(begin >= 0 && end <= arr.length)) 18 return sum; 19 for (int i = begin; i < end; i++) 20 sum += arr[i]; 21 return sum; 22 } 23 } 方法重载 .. 5.3 方法重载 C程序中,一个文件中的方法不能同名,这种规范具有避免调用方法时出现混淆、提高 调用方法效率等优点。但在编程实践中存在不足,如定义不同数据类型的加法方法, addString(Stringstr1,Stringstr2)实现两个数字字符串数值相加、addTwoInt(intx,inty )实现两个int类型数据相加、addThreeInt(intx,inty,intz)实现3个int类型数据相加, 这种方式要求编程人员记忆多个不同名的加法方法,增加了编程人员负担。 Java改进了方法的定义和调用机制,允许一个类中定义多个同名方法,但这些方法的 参数类型和参数个数不同,称这些方法是重载(Overload)。编译阶段,Java编译器根据每个 方法的参数类型和个数决定调用哪个具体方法。通过方法重载,同名方法能完成不同功能, 减轻了编程人员的记忆负担。例如,定义加法方法,add(Stringstr1,Stringstr2)实现两个 数字字符串的数值相加,add(intx,inty)实现两个int类型数据相加,add(intx,inty,int z)实现3个int类型数据相加,3个方法名都是add,通过参数类型不同实现不同功能。 方法重载是Java非常重要的一个特性,以前的程序中已经接触到,如利用System.out. println()方法能输出任何类型的数据。println()方法是一个重载多次的方法。 方法重载 程序 1 System.out.println("为梦想而努力奋斗!"); / /输出字符串 2 System.out.println(960); / /输出int 型数据 3 System.out.println(true); // 输出boolean 型数据 图5-5 程序案例5-4运行结果 程序案例5-4演示了方法重载。第17行add(inta,intb)方法有两个int类型的形参, 第21行add(Stringa,Stringb)方法有两个String 类型的形参,第27行add(inta,intb,intc)方法有3 个int类型的形参。第9行调用第17行的add()方 法,第10行调用第21行的add()方法,第13行调 用第27 行的add()方法。程序运行结果如 图5-5所示。 76 Java编程基础(微课版) 【程序案例5-4】 方法重载。 1 package chapter05; 2 public class Demo0504 { 3 public static void main(String[] args) { 4 int a = 1921; 5 int b = 28; 6 int c = 29; 7 String s1 = "1921"; 8 String s2 = "28"; 9 System.out.println(a + "+" + b + "=" + add(a, b)); //调用重载方法add 10 System.out.println(s1 + "+" + s2 + "=" + add(s1, s2)); //调用重载方法add 11 System.out.println("s1+s2=" + s1 + s2); //字符串连接 12 //System.out.println(s1+"+"+a+"="+add(s1,a)); //出现错误提示 13 System.out.println(a + "+" + b + "+" + c + "=" + add(a, b, c)); 14 } 15 //重载方法add() 16 //该方法有两个int 类型的形参 17 public static int add(int a, int b) { 18 return a + b; 19 } 20 //该方法有两个String 类型的形参 21 public static int add(String a, String b) { 22 int x = Integer.parseInt(a); 23 int y = Integer.parseInt(b); 24 return x + y; 25 } 26 //该方法有3 个int 类型的形参 27 public static int add(int a, int b, int c) { 28 return a + b + c; 29 } 30 } 方法重载是Java语言非常重要的特性之一,它减轻了编程人员负担,提高了软件开发 效率,使软件结构更加清晰。 定义方法重载时注意以下3个问题。 (1)一个类中(或者有继承关系的类之间)的方法才能重载,不同类中的方法不能重载。 例如,下面代码段,第2行类DemoX中定义的方法add(intx,inty)与第7行类DemoY 中 定义的方法add(intx,inty),这两个方法的签名一样,但它们不是重载关系。 1 class DemoX{ 2 int add(int x,int y) { 3 return x+y; 4 } 5 } 6 class DemoY{ 7 int add(int x,int y) { 8 return x+y; 9 } 10 } (2)方法重载有3种形式:①方法声明的形参个数不同;②方法声明中对应的形参类 第5章 方法 77 型不同;③方法声明的形参个数和类型都不同。 (3)方法的返回值类型不同不能作为方法重载的依据。例如,下面代码段类DemoX 中,第2行定义了intadd(intx,inty),该方法的返回值类型int;第5行定义了Stringadd (intx,inty),该方法的返回值类型String,虽然这两个方法的返回值类型不同(分别是int 和String),但形参一样,因此它们不是重载方法。 1 class DemoX{ 2 int add(int x,int y){ 3 return x+y; 4 } 5 String add(int x,int y) { 6 return String.valueOf(x)+String.valueOf(y); 7 } 8 } .. 5.4 递归方法 Java语言与其他高级语言(如C/C++、Python)一样,都提供了方法递归调用功能。递 归方法使程序更加简单清晰,而且还会使程序的执行逻辑与数理逻辑保持一致。图5-6显 示了递归方法的调用过程。本书仅介绍Java具有定义递归方法的能力,关于递归方法的详 细内容可参考有关书籍。 程序案例5-5演示了递归方法。第8行factorial()方法自己调用自己(即递归调用),问 题规模不断缩小,一直向递归结束条件逼近,num 等于1时结束递归。程序运行结果如 图5-7所示。 图5-6 递归方法的调用过程 图5-7 程序案例5-5运行结果 【程序案例5-5】 递归方法计算n!。 1 package chapter05; 2 public class Demo0505 { 3 public static void main(String[] args) { 4 int number=10; //定义整数 5 System.out.println(number+"!="+factorial(number)); //调用递归方法 6 } 7 //定义递归方法计算1×2×3×4×…×N 8 public static long factorial(int num){ 78 Java编程基础(微课版) 9 if(1==num) //递归结束条件 10 return 1; 11 else 12 return factorial(num-1)*num; //递归调用方法 13 } 14 } .. 5.5 小 结 本章主要知识点如下。 (1)方法是一段可重复调用的代码块,定义方法需要确定返回值类型、方法名和形参。 (2)Java取消了指针类型,提供了类似的引用类型,实参向形参传递引用类型时(如数 组),形参的改变将影响实参,类似于C/C++语言实参向形参传递指针类型。 (3)Java1.5提供了形参长度可变方法,该方法要求可变参数放在形参列表的最后位 置,并且只有一个可变形参。 (4)方法重载是Java语言非常重要的特性之一,其指一个类中的方法名相同,但方法 的参数类型或者参数个数不同。 (5)Java提供了定义递归方法的能力。 .. 5.6 习 题 5.6.1 填空题 1.Java程序中,如果方法的形参是数组,则调用该方法时传递的是数组的( )。 2.在使用Arrays.binarySearch()方法查找一维数组中的某个元素前,需要利用Arrays 中的( )方法对该一维数组进行排序。 3.Java程序中,定义多个名字相同但参数类型或参数个数不同的方法,称这些方法是 ( )。在( )阶段,Java编译器根据每个方法的参数类型和个数决定调用哪个具体 方法。 5.6.2 选择题 1.( )不是重载方法。 A.intprint(Stringstr){} intprint(Stringstr,intnum){} B.intprint(Stringstr,intnum){} intprint(intnum,Stringstr){} C.intprint(Stringstr){} staticprint(Stringstr){} D.intprint(doublex){} intprint(floatx){} 2.数组名作为方法形参,调用该方法时实参向形参传递的是( )。 A.数组的元素B.数组的栈地址 第5章 方法 79 C.数组自身D.数组的引用 3.方法声明正确的是( )。 A.voidmethod(int…x,inty,intz){} B.voidmethod(intx,int…y,intz){} C.voidmethod(intx,inty,int…z){} D.voidmethod(int…x,int…y,int z){} 4.定义如下类Base,( )是setNum()方法的重载方法。 class Base{ public void setNum (int a,int b,float c){ } } A.protectedfloatsetNum (inta,intb,floatc){returnc;} B.voidsetNum (inta,intb,floatc){ } C.intsetNum (inta,intb,intc){returna;} D.protectedintsetNum (intx,inty,floatz){returnb;} 5.( )是voidgetSort(intx)的重载方法。 A.publicgetSort(floatx) B.intgetSort(inty) C.doublegetSort(intx,inty) D.voidget(intx,inty) 5.6.3 简答题 1.简述方法重载的实现方式,并举例说明。 2.简述调用方法时实参向形参传递基本类型数据与引用类型数据之间的区别。 5.6.4 编程题 1.intmaxArray(int[]arr)和intmaxArray(int[][]arr)两个方法返回数组所有元素 的最大值,编写程序定义这两个方法并测试。 2.斐波那契数列在现代物理、准晶体结构、化学等领域都有直接应用。斐波那契数列 公式:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)。利用递归算法编程实现该公式。