函数Functions
代码新格式
以前
1 2 3 4 5 public class Main { public static void main(String[] args) { // 执行代码 }}
现在
1 2 3-4+5 6 7+8+9+10 public class Main { public static void main(String[] args) { // 执行代码 new Main().run(); } public void run(){ // 执行代码 }}
注意:代码文件名、class 后面的名字 和 new 后面的名字,必须完全一致!
其它地方照抄,不要动
何时使用
如果你之后写代码,要用到函数,且还没学会 JAVA 112 - 高级内存与面向对象,
就需要这个格式,会减少很多问题
函数功效预览
使用前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Question { public static void main(String[] args) { new Question().run(); } public void run(){ Console.println("请选择操作"); Console.println("1. 加法"); Console.println("2. 减法"); Console.println("3. 乘法"); Console.println("4. 除法"); Console.println("0. 退出"); int command = Console.readInt(); while (command != 0){ double value1 = Console.readDouble(); double value2 = Console.readDouble(); double result = 0.0; if (command == 1) { result = value1 + value2; } else if (command == 2) { result = value1 - value2; } else if (command == 3) { result = value1 * value2; } else if (command == 4) { result = value1 / value2; } Console.println(result); Console.println("请选择操作"); Console.println("1. 加法"); Console.println("2. 减法"); Console.println("3. 乘法"); Console.println("4. 除法"); Console.println("0. 退出"); command = Console.readInt(); } }}
功能抽离 与 转接调用
1 2 3 4 5 6+7+8+9+10+11+12+13+14+15 16 17 18*19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class Question { public static void main(String[] args) { new Question().run(); } // 功能抽离 public void printMenu(){ Console.println("请选择操作"); Console.println("1. 加法"); Console.println("2. 减法"); Console.println("3. 乘法"); Console.println("4. 除法"); Console.println("0. 退出"); } public void run(){ // 转接调用 printMenu(); int command = Console.readInt(); while (command != 0){ double value1 = Console.readDouble(); double value2 = Console.readDouble(); double result = 0.0; if (command == 1) { result = value1 + value2; } else if (command == 2) { result = value1 - value2; } else if (command == 3) { result = value1 * value2; } else if (command == 4) { result = value1 / value2; } Console.println(result); Console.println("请选择操作"); Console.println("1. 加法"); Console.println("2. 减法"); Console.println("3. 乘法"); Console.println("4. 除法"); Console.println("0. 退出"); command = Console.readInt(); } }}
收益
化繁为简,段落归纳
将一大部分逻辑紧凑的代码进行打包
打包后 需要起个名字,可以用来总结段落大意
代码重用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43*44 45 46 47 public class Question { public static void main(String[] args) { new Question().run(); } // 功能抽离 public void printMenu(){ Console.println("请选择操作"); Console.println("1. 加法"); Console.println("2. 减法"); Console.println("3. 乘法"); Console.println("4. 除法"); Console.println("0. 退出"); } public void run(){ // 转接调用 printMenu(); int command = Console.readInt(); while (command != 0){ double value1 = Console.readDouble(); double value2 = Console.readDouble(); double result = 0.0; if (command == 1) { result = value1 + value2; } else if (command == 2) { result = value1 - value2; } else if (command == 3) { result = value1 * value2; } else if (command == 4) { result = value1 / value2; } Console.println(result); // 代码重用 printMenu(); command = Console.readInt(); } }}
收益
代码重用,重复代码消除
函数基础语法
基本元素
变量基本元素
1 2 3 4 5 6 7 8 9 10 public class Main{ public static void main(String[] args) { new Main().run(); } public void run(){ int a = 1; Console.println(a); }}
变量有 三个基本元素
声明
赋值
使用 / 取值
定义 = 声明 + 赋值
函数基本元素
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Func{ public static void main(String[] args) { new Func().run(); } public void run(){ f(); } public void f(){ Console.println("Hello"); }}
函数也有 三个基本元素
声明
实现
使用 / 调用
定义 = 声明 + 实现
函数的声明与实现
1 2 3 public void 函数名() { <statements>}
函数声明后,上下均可使用
注意:函数的声明 应该出现在 有一个缩进的位置
调用语法
1函数名();
执行规则

遇到函数,原函数暂停
执行被调用函数
结束后,再恢复执行原函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Func{ public static void main(String[] args) { new Func().run(); } public void run(){ 1 f(); 2 } public void f(){ 3 }}
执行顺序:1 3 2
挑战
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Func{ public static void main(String[] args) { new Func().run(); } public void run(){ Console.println(11); h(); Console.println(12); f(); Console.println(13); } public void f(){ Console.println(21); g(); Console.println(22); h(); Console.println(23); } public void g(){ Console.println(31); h(); Console.println(32); } public void h(){ Console.println(41); }}
答案
run { 11 h { 41 } 12 f { 21 g { 31 h { 41 } 32 } 22 h { 41 } 23 } 13 }
函数生命周期Function Lifecycle
函数内的变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Func{ public static void main(String[] args) { new Func().run(); } public void run(){ int a = 1; f(); Console.println(a); } public void f(){ int a = 1; a++; Console.println(a); }}
你会看见什么
2 1
2 2
规则
通过大括号规则,可以看到 变量活在自己所在的函数里
每个函数有自己的数据空间,用于存储变量,且相互独立

函数间的数据传输
希望把数据 从一个函数里 传到 另外一个函数里

函数级别的 IO | 专业名词 | 数据传输方向 |
---|---|---|
输入 | 参数 | 从 外面 进入到 函数里 |
输出 | 返回值 | 从 函数里 流出到 外面 |
函数调用的完整生命周期
调用前

开始调用 f,准备变量空间
调用 f 时,为 被调用函数 f,开启单独变量空间
run 可使用 参数 将一些值 带到 函数 f的 新的变量空间

运行 被调用函数 f
被调用的函数 f 运行时,如果需要开变量,会开在自己的空间里

被调用函数 f 执行结束,准备返回
有些函数有返回值,此时会带着返回值 回到 刚才的函数 run

调用结束,销毁函数 f
结束后,会将 被调用函数 f 和对应的变量空间,彻底销毁,就当没发生过

参数Parameters
是什么
变量,只不过可以接受传值
函数的输入

参数声明
变量定义,用 ,
分割
public void f(int a, int b) { }
函数调用时传参
public void run() { f(3, 4); }
函数调用前表达式缩合
1 2 3 4 5 6 7 8 9 10 public void run() { int c = 3; int d = 4; int e = c + d; = 3 + 4; = 7; f(c, d); f(3, 4);}public void f(int a, int b) { int a = 3; int b = 4; Console.println(a); Console.println(b);}
传输的是值,不是变量
是数据格里的数据,而不是 数据格子

1 2 3 4 5 6 7 8 9 public void run() { int a = 1; f(a); Console.println(a); }public void f(int a) { a = 9;}
上面代码运行完了,你会看到什么
1
9
实参 与 形参
中文 | 英文 | 意义 |
---|---|---|
实参 | arguments | 传到参数里的值 |
形参 | parameters | 接收参数值的那个参数变量 |
传参数量匹配
传过去的和接收的,必须一致,多了也不行,少了也不行
1 2!3 4!5 6 7 8 public void run() { f(); f(1); f(1, 2);}public void f(int a) { Console.println(a);}
顺序传输
按顺序传参,一个个传输
1 2 3 4 5 6 7 8 public void run() { int a = 1; int b = 2; f(b, a);}public void f(int a, int b) { Console.println(a + ", " + b);}
上面代码运行完了,你会看到什么
1, 2
2, 1
传参类型匹配
按着顺序,每一个的类型必须匹配
也可以不匹配,那么就遵循变量赋值的 类型转换规则
1 2 3 4!5 6 7 8 < <public void run() { f(1); f(1.3); f("1");}public void f(double a) { Console.println(a);}1.01.3
返回值Return Value
是什么
用来给 调用方 返回 结果使用
函数的输出
返回值声明
函数可以有一个返回值
1 2 3 public int f() { }
void
如果不希望返回东西,则需要写 void
1 2 3 public void f() { }
返回值
1 2 3 public int f() { return 1; }
变量接住函数返回值
函数表达式 会缩合成 函数的返回值
1 2 3 <4 public void run() { int a = f(); = 1; Console.println(a); 1}
函数返回前的表达式缩合
1 2 3 <4 5 6 7 8 9 public void run() { int a = f(); Console.println(a); 1} public int f() { int c = 1; return c; return 1;}
函数返回的是值,而不是变量
不是数据格,是数据格里的数据
1 2 3 4 5 6 7 8 9 10 public void run() { int a = 1; a = f(); Console.println(a);} public int f() { int a = 9; return a;}
上面代码运行完了,你会看到什么
1
9
return
return 有两个效果,
立刻终止当前函数
后面可以跟值,达到返回值效果
也就是说,可以在无返回值函数里,干写一个 return,达到提前结束的效果
1 2 3 4 5 6 7 8 9 10 public void run() { f(3);} public void f(int v) { if (v < 0) { return; } Console.println(v);}
三体匹配
return,返回值类型声明,变量接收 三者要匹配
return 与 返回值类型声明匹配
函数声明 无返回值 | 函数声明 有返回值 | |
---|---|---|
写 return; | 可以写,写了算提前终止 | 不可以 |
写 return x; | 不可以 | 必须要写,不能不写,且需要类型匹配 |
变量接收 与 返回值类型声明匹配
函数声明 无返回值 | 函数声明 有返回值 | |
---|---|---|
使用 变量接收 a = f(); | 不可以 | 可以接收,但需要类型匹配 |
不使用 变量接收 f(); | 可以 | 也可以不接,此时返回值就丢了 |
函数综合应用
完整语法
public <返回值类型> <函数名>(<函数参数列表>) { <语句> }
<函数参数列表> = <参数1类型> <参数1名>, <参数2类型> <参数2名> ...
带参数,带返回值的函数
1 2 3 4 5 6 7 8 9 10 public void run() { int a = 1; a = f(a); Console.println(a);} public int f(int a) { a = 9; return a;}
上面代码运行完了,你会看到什么
1
9
挑战
add
写一个函数,算出两个整数的和
max
写一个函数,算出两个整数的最大值
greating
写一个函数,接收一个名字后,控制台打印欢迎 ta 的信息
next
写一个函数,接收一个整数,返回比它大的下一个整数
roll
写一个函数,返回一个 1 - 6 的随机值
randomSelect
写一个函数,从数组中随机抽选一个值 返回回去
函数对接
一个函数的返回值,可以直接对接另外一个函数的参数
1 2 3 4 5 6 7 8 9 10 11 public void run() { echo(generate());} public int generate() { return 1;} public void echo(int value) { Console.println(value);}
函数重载Function Overloading
能干什么
同一个文件下,可以有同名函数
函数签名
函数签名 = 函数名 + 参数类型列表
案例
1 2 3 public int max(int a, int b) { return 0}
签名为
1max(int, int)
函数重名规则
在一个结构下,不能出现两个函数签名一样的函数
挑战
1 int f(int a){}
以下这些函数,是否可以跟上面的函数同时出现在一个结构里
1 int f(int a, int b){}
可以同时出现
不可以同时出现
1 int f(int b){}
可以同时出现
不可以同时出现
1 int f(double a){}
可以同时出现
不可以同时出现
1 int g(int a){}
可以同时出现
不可以同时出现
1 double f(int a){}
可以同时出现
不可以同时出现
函数调试
样例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int[] filterOutZeros(int[] values) { int count = 0; for (int i = 0; i < values.length; i++) { if (values[i] != 0) { count++; } } int[] filteredValues = new int[count]; int index = 0; for (int i = 0; i < values.length; i++) { if (values[i] != 0) { filteredValues[index] = values[i]; index++; } } return filteredValues;}
单元测试
针对一个函数进行测试
1 2 3 4 5 public void run() { int[] values = {6, 7, 3, 8, 4}; int[] result = filterOutZeros(values); Console.debugPrintln(values);}
使用代码块 隔离变量
1 2 3 4 5 6 7 8 9 10 11 12 public void run() { { int[] values = {6, 7, 3, 8, 4}; int[] result = filterOutZeros(values); Console.debugPrintln(values); } { int[] values = {6, 7, 0, 8, 4}; int[] result = filterOutZeros(values); Console.debugPrintln(values); }}
函数下的人工内存
每个函数会 开出自己的新空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void run() { int[] values = {6, 7, 3, 8, 4}; int[] result = filterOutZeros(values); Console.debugPrintln(values);}public int[] filterOutZeros(int[] values) { int count = 0; for (int i = 0; i < values.length; i++) { if (values[i] != 0) { count++; } } int[] filteredValues = new int[count]; int index = 0; for (int i = 0; i < values.length; i++) { if (values[i] != 0) { filteredValues[index] = values[i]; index++; } } return filteredValues;}
run(): line io 2 [6, 7, 3, 8, 4] 3 call filterOutZeros( [6, 7, 3, 8, 4] ) values filterOutZeros(): line 6 [6, 7, 3, 8, 4] 7 | 0 8 | | 0 9 | | | true 10 | 1 | values count i ...
debug 工具
跳过 函数
进入 函数
跳出 函数
递归Recursions
什么是
自己调自己
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void run() { int[] values = {6, 7, 3}; recursivePrint(values, values.length);} public void recursivePrint(int[] arr, int length) { if (length == 0) { } else if (length == 1) { Console.print(arr[length - 1]); } else { recursivePrint(arr, length - 1); Console.print(", " + arr[length - 1]); }}
递归下的变量管理
当自己调用自己的时候,等同于开了一个新的同名函数
各自保持自己的变量空间

数据传输
三大传输
赋值
传参
返回值
数据传输的规则
数据传输,需要负责类型匹配规则
变量 要比 值 大 或者一样
函数参数 要比 接收的值 大 或者 一样
接收返回值的变量 要比 返回值 大 或者 一样
数据传输下的问题
修改变量
1 2 3 4 5 6 7 8 9 public void run(){ int a = 1; f(a); Console.println(a);} public void f(int a) { a = 2;}
修改数组变量
1 2 3 4 5 6 7 8 9 public void run(){ int[] a = {1, 2}; f(a); Console.debugPrintln(a);} public void f(int[] a) { a = new int[]{3, 4};}
修改数组变量 里的内容!
1 2 3 4 5 6 7 8 9 10 public void run(){ int[] a = {1, 2}; f(a); Console.debugPrintln(a);} public void f(int[] a) { a[0] = 3; a[1] = 4;}
注意:使用 中括号语法,会让 a 变量产生迷幻的同步效果