本套教材视频教程已经上线b站

第5章 函数
学习目标
u 掌握函数的概念,能够说出函数的作用以及构造
u 了解入口函数,能够说出入口函数的作用
u 了解查看System.out源码的方式,能够说出printlin()和print()函数的区别
u 掌握函数的参数和返回值,能够根据业务场景设计函数的参数和返回值
u 掌握格式化输出的方式
u 了解递归
u 熟悉JDK文档的查看以及生成方式
假设现在要开发一个游戏程序,程序在运行过程中,要不断地发射炮弹。发射炮弹的动作需要编写100行代码。在学习函数之前,我们可能在每次实现发射炮弹的地方都需要重复地编写这100行代码,这样的程序代码是相当臃肿的,而且程序可读性也非常差。
如果使用函数可以很好的解决上述问题,我们可以将发射炮弹的代码提取出来,放在一个{}中,并为这段代码起个名字,提取出来的代码可以看作是一个函数。我们可以把函数理解为完成特定功能的代码段,每一个函数都是一个独立的小功能。接下来,本章将针对函数的相关内容进行讲解。
5.1函数入门
5.1.1 什么是函数
大家都听说过“函数”这个词,对于数学学得好的同学,可能脑子中对函数的印象如图5-1所示。对于数学不好的同学,可能对函数的印象如图5-2所示。


计算机编程中的函数,和我们数学中的函数不一样。无论你数学学的怎么样,大家只需要认真听我们的课,都可以很好的掌握Java编程中的函数。
为了方便大家理解函数的概念,我们先看看乐高积木,如图5-3所示。

图5-3 乐高积木
我们可以通过图5-3所示的乐高积木拼出很多好玩的玩具,例如,房子,大象、熊猫等。乐高积木是由很多小片段组成的,这些下片段组合在一起,可以做成一个很棒的大玩具。
其实,计算机程序也是由很多小片段组成的,函数把这些小片段组合在一起,可以提供某种特殊的功能,方便后续重复使用。可以说,函数的作用就是更好的组织和组合代码片段。
我们结合乐高积木来理解一下函数,大家可以把乐高积木想象成程序中的各种函数,如图5-4所示。

图5-4 乐高积木小片段
如果我们把这些积木按一定顺序搭建在一起,就可以拼出一个鸟,拼积木步骤如图5-5所示。

图5-5乐高积木拼成鸟的步骤
图5-5所示的拼出鸟的步骤其实就是拼出鸟的方法。Java编程中的函数也称为方法(method),方法就是你做成事的步骤。
接下来,我们通过一个示例代码来说明方法的作用,假设现在我们要打印一个图案“ITHEIMA”,我们可以这么做:
public class Test {
public static void main(String[] args) {
System.out.println("╔══╗╔════╗╔╗─╔╗╔═══╗╔══╗╔═╗╔═╗╔═══╗");
System.out.println("╚╣╠╝║╔╗╔╗║║║─║║║╔══╝╚╣╠╝║║╚╝║║║╔═╗║");
System.out.println("─║║─╚╝║║╚╝║╚═╝║║╚══╗─║║─║╔╗╔╗║║║─║║");
System.out.println("─║║───║║──║╔═╗║║╔══╝─║║─║║║║║║║╚═╝║");
System.out.println("╔╣╠╗──║║──║║─║║║╚══╗╔╣╠╗║║║║║║║╔═╗║");
System.out.println("╚══╝──╚╝──╚╝─╚╝╚═══╝╚══╝╚╝╚╝╚╝╚╝─╚╝");
}
}
如果要打印100个图案,怎么办呢?可能有的同学会说,复制粘贴100次就可以,但是,如果图案发生变化,难道还要修改100遍吗?很明显,这种写代码的方式太不友好了,代码太冗余,而且代码修改起来很麻烦。
这个时候,我们可以把重复的代码抽成一个方法。在IDEA中将代码抽取成方法的方式比较简单,选中要抽取的代码,右击【Refactor】→【Extract Method】,具体如图5-6所示。

图5-6 将代码抽取为方法
这样我们就抽取出了一个方法,这里我们将方法命名为heimalogo。如果我们想调用这个方法,只需要编写代码heimalogo()就可以了,具体如图5-7所示。

图5-7 调用方法
如果后续还想连续输出多次logo,可以调用多次heimalogo()方法,例如,在main()方法中调用2次heimalog(),如图5-8所示

图5-8 调用2次heimalogo()方法
将重复代码抽取为方法还有另外一个好处,就是代码维护很方便。假设方法中的代码需要修改,那么我们只需在方法中修改一次即可,具体如图5-9所示。

图5-9 修改方法中的代码
5.1.2入口函数
Java中有一个非常重要的函数,即入口函数main(),它是程序运行默认执行的函数,也就是程序执行的入口,示例代码如下:
public static void main(String[] args) {
// 入口函数中的代码
}
需要注意的是,入口函数main()的函数名和写法是固定的,不可修改。
5.1.3 剖析System.out源码
前面章节的学习中,我们多次使用了System.out.println()函数输出内容。其实,代码的输出不仅可以使用System.out.println()函数,还可以使用System.out.print()函数。
虽然上述两个函数都是用来输出内容的,但是它们还是有一些区别,具体如下:
(1)System.out.print()函数的作用是把括号里面的内容输出到计算机屏幕。
(2)System.out.println()函数是作用是把括号里面的内容输出到计算机屏幕,同时换行。
理解了这两个函数的区别后,我们阅读一下它们的源码,剖析一下其内部的实现原理。
在IDEA中,按住Ctrl键的同时,使用鼠标单击println查看println()函数源码,如图5-10所示。

图5-10 查看println()函数源码
从图5-10中可以看出,在println()函数中,先调用了print()函数,再调用了newLine()函数,也就是说,println()函数的功能其实是print()函数和newLine()函数组合实现的。
继续查看newLine()函数的源码,如图5-11所示。

图5-11 newLine()源码
从图5-11中,我们继续查看textOut.newLine()的源码,具体如图5-12所示。

图5-12 newLine()源码
在图5-12中,绩效查看LineSeparator()源码,具体如图5-13所示。

图5-13 LineSeparator()源码
在图5-13中, LineSeparator()方法的注释说明了在UNIX系统,返回值是“ ”,表示返回一个换行符。在Windows系统会返回“ ”,表示返回一个回车和换行符。
通过上述源码的阅读,我们可以发现,下面两段代码的效果是等同的。
System.out.println("helloworld");
等同于:
System.out.print("helloworld");
System.out.print(" ");
5.2 函数的参数和返回值
5.2.1 认识函数参数和返回值
函数的本质,可以理解为一个或一组功能的组合。例如,我们利用System.out.println这样的小函数,组合成了打印logo的大函数。这节课我们继续探索函数的本质。
Java中的函数还有一个重要作用就是处理数据。我们知道,计算机的功能就是处理数据,当我们把二进制数据交给计算机,计算机运算完毕后,会输出新的二进制数据。
如果把人类的消化系统与计算机进行类比的话,可以把人类摄入的食物比作是向计算机输入的数据,这些食物经过唾液腺、胃、肝脏等一系列器官处理后,最终会有产物,这个产物就相当于计算机的输出。图5-14描述了人类与计算机的输入输出过程。

图5-14 人类和计算机的输入输出过程
如果把人的消化系统类比一个函数,会出现4种情况:
l 有时候吃完饭就拉粑粑
l 有时候吃完饭不拉粑粑
l 有时候不吃饭就拉粑粑
l 有时候不吃饭不拉粑粑
我们重新梳理一下上述思路,具体如下:
l 饭→消化系统函数→粑粑
l 饭→消化系统函数→nothing
l nothing→消化系统函数→粑粑
l nothing→消化系统函数→nothing
计算机是用于处理数据的,我们编写的函数一般都是用来处理或者解析数据。有时候,我们会将数据以参数的形式交给函数,函数处理完毕后,会产生输出。如果将函数类比人的消化系统,处理数据的过程如图5-15所示。

图5-15 函数处理数据的过程
实际上,函数的参数与返回值会有四种场景:
l 有输入,有输出
l 没输入,有输出
l 有输入,没输出
l 没输入,没输出
如果将上述四种场景与消化系统对应的话,如图5-16所示

图5-16 函数与消化系统的场景对应
理解了函数的输入和输出规则之后,下面我们看一下Java函数的编写规则。
函数修饰符 返回值类型 函数名(函数参数类型 函数名){
函数体语句;
}
下面我们将之前写的代码与上面的函数编写规则对应起来加深理解,如图5-17所示。

图5-17代码与函数编写规则的对应
在图5-17中,函数的访问修饰符是public static,返回值类型是void(空类型),表示没有返回值,函数名是main,表示是程序的入口函数,函数参数类型是String[],表示的是字符串数组,参数名是args。函数体语句是一个输出语句。
5.2.2 应用案例:计算器
理解了函数的参数和返回值的作用后,接下来,我们开发一个计算器,用于实现两个整数的加减乘除四则运算,具体步骤如下:
(1)创建一个表示计算器的类Calc,在该类中定义四个函数,分别实现加减乘除算法,具体代码如下:
/
* 这是一个相加的函数
*
* @param x 第1个加数
* @param y 第2个被加数
* @return x+y的结果
*/
public static int add(int x, int y) {
return x + y;
}
/
* 这是一个相减的函数
*
* @param x 减数
* @param y 被减数
* @return x-y的结果
*/
public static int sub(int x, int y) {
return x - y;
}
/
* 这是一个相乘的函数
*
* @param x 乘数
* @param y 被乘数
* @return x*y的结果
*/
public static int multi(int x, int y) {
return x * y;
}
/
* 这是一个相除的函数
*
* @param x 这是除数
* @param y 这是被除数
* @return x/y的结果
*/
public static int division(int x, int y) {
return x / y;
}
(2)在Calc类的main()函数中进行测试,具体代码如下:
public static void main(String[] args) {
int result_01 = add(3, 8);
System.out.println("x+y=" + result_01);
int result_02 = sub(10, 5);
System.out.println("x-y=" + result_02);
int result_03 = multi(3, 5);
System.out.println("x*y=" + result_03);
int result_04 = division(10, 5);
System.out.println("x/y=" + result_04);
}
(3)运行程序,结果如图5-18所示。

图5-18 Cala的运行结果
5.2.3应用案例:超市找零
函数对外提供某种业务功能,用来解决某种问题。设计函数时,最好可以做到下面几点:
l 函数的名称最好能顾名思义,通过函数名就能基本看出函数的功能。
l 根据业务提供的数据,设计函数的参数。
l 根据业务的输出要求,设计函数的返回值。
下面我们通过一个超市收银的案例来演示如何抽取现实生活中的业务场景并将业务场景设计为函数。
一、业务场景:
图5-19描述了一种超市收银的业务场景,某顾客购买了香蕉,价格是3.99元,顾客支付了5元,超市收银员会操作柜台,自动计算出需要给顾客找零多少钱。

图5-19 超市收银业务场景
二、设计函数:
(1)由于上述业务是找零的场景,所以我们可以设计函数名为makeChange。
(2)顾客消费了3.99元,但是给了收银员5元,按照这个逻辑,我们为函数设计两个参数,一个参数表示消费金额double itemCost,一个参数表示给收银员的金额double money。
(3)这个函数用来计算找零,最终函数的返回值应该是double change。
三、编写函数:
按时设计的函数,编写函数makeChange,示例代码如下:
public static double makeChange(double itemCost,double money){
return money-itemCost;
}
在Test中调用makeChange函数进行测试,示例代码如下:
public class HelloStore {
public static void main(String[] args) {
double itmeCost = 2.0;
double money = 5.0;
double change = makeChange(itmeCost, money);
System.out.println("找零" + change);
}
public static double makeChange(double itemCost, double money) {
return money - itemCost;
}
}
程序的运行结果如图5-20所示。

图5-20 HelloStore的运行结果
多学一招:实参与形参
Java中的参数分为实参和形参,其中,形参就是形式参数,用于定义函数时的参数,用来接收调用者传递的参数。实参就是实际的参数,用来调用函数时传递给函数的参数,实参在调用方法之前要预先赋值的。例如,上面的案例中,makeChange函数中的itemCost和money都是形参,而main函数中的itmeCost和money都是实参。
5.3 格式化输出
Java中提供了一个格式化输出的函数printf(),它实现了C语言风格的输出,用于输出带各种数据类型占位符的参数,其参数个数是不定的。接下来,通过一张表来罗列不同数据类型的标记占位符,如表5-1所示。
表5-1 不同数据类型的标记占位符

为了大家更好理解格式化输出的效果,接下来,通过一个案例来演示printf()函数的用法,如文件5-1所示。
文件5-1 Test.java
public class Test {
public static void main(String[] args) {
System.out.printf("My Name is %s,I was born in %d","Mike",1998);
System.out.println("");
System.out.printf("The sum of %d and %d is %d",15,40,55);
System.out.println("");
System.out.printf("Display a Number %f",15.);
System.out.println("");
System.out.printf("Display a Number %.2f",15.23);
}
}
文件5-1的运行结果如图5-21所示。

图5-21 文件5-1的运行结果
根据图5-21所示的运行结果可知,使用printf()函数格式化输出内容时,printf()函数第1个参数中的占位符,依次被后续的数值替代。需要注意的是,使用printf()函数格式化输出内容时,要保证占位符和数据类型的一致性。
5.4 函数综合案例——掷骰子
5.4.1 掷骰子-基础版
本节我们通过一个掷骰子的小游戏来继续学习函数的设计。我们的目标是编写一个掷骰子的小游戏,用户每次调用掷骰子的函数就会产生一个1~6的结果。
通过对业务分析,我们可以设计一个这样的函数:
(1)函数名:rollDice
(2)函数参数:无
(3)函数的返回值:1~6之间的一个随机整数。
这里会有一个问题,如何产生1-6这样的整数?
JDK中内置了大量的函数供我们使用,例如,System.out.println()就是在System类提供的用于打印输出的函数。同样的,在Math类里面,JDK也提供了很多方便的函数,这里我们介绍一个随机数的函数Math.random(),它的返回值是一个0~1之间的随机生成的小数。
接下来,我们先定义设计好的函数rollDice,具体代码如下:
1 public static int rollDice() {
2 double random = Math.random(); // 范围[0,1)
3 double result = random * 6 + 1; // 范围[1,7)
4 return (int) result;
5 }
上述代码中,第2行代码使用Math.random()生成了一个0~1的随机小数random。由于我们要产生的随机数范围是1~6这样的整数,所以在第3行代码中通过random*6+1这样的操作,将随机数的范围扩大到[1,7),最后通过“(int) result”的操作,获取随机小数的整数部分并返回。
在测试类HelloDice中调用rollDice()函数,具体代码如下:
public class HelloDice {
public static void main(String[] args) {
int result = rollDice();
System.out.println("扔骰子,结果:" + result + "点");
}
public static int rollDice() {
double random = Math.random();// 范围[0,1)
double result = random * 6 + 1; // 范围[1,7)
return (int) result;
}
}
多次运行程序,发现程序会随机产生1~6的数字,运行结果示例如图5-22所示。

图5-22 HelloDice的运行结果(1)
5.4.2 掷骰子-升级版
假设,现在我们要将上述场景的业务进行升级,因为骰子不仅有6个面的,其实还有5个面或者7个面的,我们能否设计一个通用的函数,任何一个面的骰子,我们都可以摇出来一个随机数。
下面我们对上述设计的rollDice函数进行升级,此时设计的函数如下:
(1)函数名:rollDice
(2)函数参数:int类型的骰子类型
(3)函数的返回值为1~6之间的随机整数。
升级后的函数rollDice,具体代码如下:
public static int rollDice(int tpye) {
double random = Math.random();
double result = random * type + 1;
return (int) result;
}
在测试类HelloDice中再次调用rollDice()函数,具体代码如下:
public class HelloDice {
public static void main(String[] args) {
int result = rollDice(7);
System.out.println("扔骰子,结果:" + result + "点");
}
public static int rollDice(int tpye){
double random = Math.random();
double result = random * tpye + 1;
return (int) result;
}
}
上述代码在调用rollDice()函数时传入的参数是7,我们多次运行程序,程序是可以随机产生7这个数字的,如图5-23所示。

图5-23 HelloDice的运行结果(2)
通过上述掷骰子的案例,希望大家在设计函数时能够对业务类型进行分析,根据业务的输入设计函数的参数,根据业务输出要求设计函数的返回值。
5.5函数特性总结
这个小节我们对函数的特性做一些总结,具体如下:
(1)函数可以理解成一个黑盒子,它包含很多行代码。但是,我们调用函数的时候,实际上是可以不用关心里面的代码是如何写的。例如,我们前面开发了一个掷骰子函数,后续我们实现掷骰子功能的时候,只需要调用掷骰子函数即可,无需关系该函数内部的代码。
(2)函数可以接收参数,参数作为函数的输入,可以在函数内部进行数据运算。
(3)函数运算后的结果,可以通过return返回。
(4)调用函数的结果,就是函数返回值,我们可以使用这个返回值实现其他业务逻辑。
5.6 JDK文档
5.7.1 JDK文档的查看和阅读
前面我们介绍了Math类的random()函数,它用于返回0~1之间的随机数。可能有的同学会有疑问,我们如何自行得知这些类或者函数的作用以及用法呢?
我们先举一个现实生活中的例子,假设我们购买了一台洗衣机,洗衣机厂商通常都会附带一份洗衣机的使用手册或者说明书,如图5-24所示。

图5-24 洗衣机的使用说明书示例
图5-24所示的洗衣机使用说明书中,包含了洗衣机操作步骤、规格及技术参数、异常处理以及洗涤用书量等信息。如果我们看了洗衣机的说明书,那么洗衣机的用法会非常清楚。
同样,Java也提供了一份“使用说明书”——JDK文档,链接如下:
https://docs.oracle.com/en/java/javase/11/docs/api/index-files/index-1.html
在JDK文档中,我们可以找到绝大多数的Java使用说明,而且这个JDK文档是绝对权威的Java开发资料,它包含了关于类、函数、方法等具体说明,所以也称为API文档。
下面,我们带大家一起看一下API文档中关于Math的介绍。
打开API文档,通过搜索关键字Math找到Math类的相关介绍,如图5-25所示。

图5-25 Math类
通过阅读Math类的相关API,我们发现Math类不仅提供了字段,而且还提供了很多函数,如图5-26所示。

图5-26 Math类提供的字段和方法
我们在Math类中找到之前讲解的random函数,如图5-27所示。

图5-27 random函数
从图5-27中random()函数的介绍可知,random()函数会返回一个大于等于0并且小于1的双精度的小数。单击random()查看random()函数的的详细介绍,如图5-28所示。

图5-28 random()函数的详细介绍
大家可以自行阅读random()函数的相关介绍,熟悉random()函数的用法。除了random()函数之外,Math类还提供了其他很多的API文档,例如,用于比较最大数的max()函数,其相关介绍如图5-29所示。

图5-29 max()函数
在图5-29中,max()函数需要传入两个参数a和b,并返回最大的一个数,max()函数的调用示例如下所示:
Math.max(13,5);
好了,至此我们介绍了Java API文档的阅读方式。
5.7.2JDK文档的生成
Java提供了API文档供开发者使用,那我们可以自己构建一个API文档吗?答案是肯定的。软件开发是一个协同工作,我们可以构建自己的API文档供其他程序员使用。下面我们以之前的HelloDice文件为例,介绍如何构建API文档,具体步骤如下:
(1)使用文档注释对代码进行说明,具体代码如下:
/
* 这是掷骰子的类
*/
public class HelloDice {
public static void main(String[] args) {
int result = rollDice(7);
System.out.println("扔骰子,结果:" + result + "点");
}
/
* 掷骰子的方法
* @param type 筛子是几个面
* @return 掷骰子后,返回1~n的随机整数值
*/
public static int rollDice(int type) {
double random = Math.random();
double result = random * type + 1;
return (int) result;
}
}
(2)单击【Tools】→【Generate JavaDoc】进入Generate JavaDoc窗口,如图5-30所示。
→

图5-30 Generate JavaDoc窗口
在图5-30中,设置Output Directory,也就是设置Java文档的输出位置,这里我们选择存放在C:czjava149day03codedoc,这里要保证文件是存在的。因为默认情况下,javadoc只支持英文,不支持中文,所以,为了使我们设置的Java文档支持中文,我们可以在构建javadoc文档时,添加中文支持的配置信息,具体如下:
-encoding UTF-8 -charset UTF-8 -windowtitle "扔骰子文档"
添加配置的位置如图5-31所示。

图5-31添加中文支持配置
此时,再次单击图5-31中的OK按钮,文档就可以顺利构建成功。构建好的文档如图5-32所示。

图5-32构建好的Java文档
好了,构建Java文档的介绍就到这里,希望大家在协同开发过程中,要养成一个编写注释的好习惯,这个注释不仅可以自己查看,还可以提供给其他程序员查看。
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/2554.html