假设当前需求:得到 1-10 的和、20-37 的和、35-90 的和。若按照前面所学循环就能实现:
public class SumDemo {
public static void main(String[] args) {
// 1-10 的和
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum = sum + i;
}
System.out.println("从1到10的和为:" + sum);
// 20-37 的和
sum = 0;
for (int i = 20; i <= 37; i++) {
sum = sum + i;
}
System.out.println("从20到37的和为:" + sum);
// 35-90 的和
sum = 0;
for (int i = 35; i <= 90; i++) {
sum = sum + i;
}
System.out.println("从35到90的和为:" + sum);
}
}
按照这种方式开发代码,很明显会出现代码冗余的情况,使用函数能避免这样的傻事发生。函数其实就是方法 function ,其本质就是一个功能块,跟数学无关,表示“能做某个事情”。像我们一直在使用到的主函数 main()
就是一个函数。
public static void main(String[] args) {
// ...
}
函数的出现是模块化编程思想的必然结果,对于大型项目开发团队分工协作,开发人员只需要考虑自己负责的模块的实现,从而提升开发效率。日后需要维护、修改,也只需要在单独模块中修改一处即可。
目前我们的开发方式就是从主函数 main()
从头到尾顺序书写代码,但随着程序功能的复杂度增加,程序结构会越来越复杂,当出现不连续的重复代码出现,就无法通过循环来解决。
此时就可以提出“模块化”代码的思想了,给某个方法(能实现一种功能)规定其内部做一些事情,那么就可以实现“书写一次,重复使用”了。就好像是我们已经使用过的 scanner.nextInt()
、System.out.println()
、 Math.random()
等,就是已经被定义好的方法,当需要使用时,直接调用就能实现其对应的效果。
同时,良好的函数命名可以大大提升代码的整体可读性,从函数名称就能让使用者见名知意明确函数的用途。
语法
函数的语法可以分为定义和调用。
就好比是打牌,摸牌摸得再好,不打出去,也是无用的。所以函数声明了不调用也是不会生效的。同样,若只是口头上大喊了一声“炸弹!”但实际上手里并没有4张一样的牌,也只是“虚晃一招”。仅有函数的调用,却又没有函数的定义,那么程序也是找不到该方法导致报错。
定义
函数的定义也被称为函数的声明。
语法:
修饰符 返回类型 函数名称(参数列表){ // 声明部分
// 实现部分
函数体;
}
其中 修饰符 返回类型 函数名称(参数列表)
属于函数的声明部分,{}
里的内容属于函数的实现部分。
对照主函数看看语法说明:
public static void main(String[] args) {
// ...
}
/*
public 定义该 main 函数是一个公共的
static 一个可选修饰符,控制访问方式,表示静态
void 定义该函数调用完没有返回值
main 函数名
String[] args 参数列表
*/
语法说明:
- 修饰符:定义本方法的访问范围、访问方式。目前所有函数的修饰符都固定写成 public static
- 返回类型:定义本方法在执行完毕后,是否有返回结果,以及返回哪种数据类型
- 若一个函数执行结束后没有返回,那么返回类型就书写 void,代表 “没有返回值”
- 若有返回值,书写返回的数据的类型
- 函数是否需要写返回值是看具体需要的
- 数字区间求和(计算 n - m 之间的和),求完和后,具体是打印或是继续做运算不归函数内部决定,就返回结果
- 注册,注册完毕以后具体是用户想马上登录,还是停止操作,也不归注册函数内部决定。就不需要返回值了
- 函数名称:该方法的名字,尽量见名望意
- 参数列表:定义本方法执行操作需要得到的初始数据。由于在定义时并不知道接收过来的值具体是什么,只能确定数据类型,所以参数列表的语法全是变量,多个参数使用逗号
,
分割 ```java // 没有参数 public static void printName(){
} // 接收一个 string 类型参数,没有返回值 public static void printName(String name){
}
// 接收 string、int 参数,返回一个 boolean 值 public static boolean printName(String name, int age){
}
<a name="wyhsW"></a>
## 书写位置
函数本身就是一个单独的模块,所以它的定义是在**其他方法之外**的,所有方法都是**平行**关系,不要在一个函数中定义另一个函数形成嵌套关系。<br />方法的定义只能在 java 类的定义中。
```java
// Test.java
public class Test {
// 主函数
public static void main(String[] args){
}
// 自定义函数 A
public static void methodA(){
}
// 自定义函数 B
public static void methodB(){
}
}
调用
刚才我们提到了摸牌得打出去才算出牌,函数声明了得调用才会执行函数体内的代码。函数的调用也非常简单和灵活。
- 声明在 main() 以外声明,调用在 main() 中调用,因为程序运行都是从 main() 执行
- 即用即调,函数可以在任意需要使用到该函数的地方完成调用
- 被调用的函数可以与调用函数不在同一个 class 文件
语法:
- 函数名(参数列表); 更常用,同一 java 文件中直接调用
类名.函数名(参数列表); 不在同一 java 文件调用前要显式指明在哪个类
// Test.java
public class Test {
public static void main(String[] args) {
method(); // 在同一 java 文件,直接使用 函数名();
Test.method(); // 也可以通过 类名.函数名();
}
// 声明一个 method 函数
public static void method() {
// ...
}
}
形参与实参
形参和实参是让函数(初学者)更加灵活(蒙圈)的部分了,不过从名称就能看出,形参,形式参数;实参,实际参数:
- 函数 声明时 的参数称为形参 public static int sum(int a, int b) 中的 a、b 就是形式参数
- 函数 调用 时传入的参数称为实参。因为只有在调用时才能明确本次调用真正传递进的每个参数的具体数值
实参必须跟定义处的形参保持匹配,个数、数据类型、顺序一致,所谓“一一对应”。
/*
不是完整代码,只看结构
*/
public static int sum(int a, int b){ // 声明时,a b 是形参
System.out.println("a:" + a + ",b:" + b); // 根据接收的 a b 值不同打印结果不同
}
sum(1, 5); // 调用时 1 5 是实参
return
在方法的实现部分,除了功能语句,还要注意和声明语句的配合。若函数的声明是 非 void 的,证明该方法一定有返回值,那么在该方法中就必须要有 return 关键字,后面跟上与返回类型匹配的返回值。
- return 后面不能再有语句,类似于像循环中的 break
- 声明为 void 的方法也能有 retrurn 关键字用于终止函数执行,只是 return 后面不跟具体的返回值
- 若在 main() 方法中书写 return 表示结束主流程
需要注意的是,要保证该方法在任意执行路径下,都要有返回值。
// 该函数执行完后,应该有一个 boolean 类型的返回值
public static boolean printName(String name, int age){
// ... 操作
return true; //
}
当函数有返回值,获取函数调用后的返回值:数据类型 变量 = 函数名(参数列表);
例如,声明一个函数,计算两个数之间的和,返回值:
// Test.java
public class Test {
public static void main(String[] args){
int result = sum(1, 5); // 调用并传参,函数内部执行完毕返回值被 result 接收
}
// 声明求和函数(求和功能)
public static int sum(int a, int b){ // 需要接受两个数字 a 和 b
int sum = 0;
for(int i = a; i <= b; i++){
sum += b;
}
return sum; // sum 在函数内部计算完后返回给函数调用处第 4 行
}
}
以上代码表示:与 main()
同级声明了一个计算和的函数 sum
接收两个参数 a 和 b,函数内部计算和后会返回结果到方法调用处。第 4 行调用 sum()
函数并且传入 1 和 5,1 就被 a 接收,5 被 b 接收,调用完毕后,和值被 result 接收。接收后回到第 5 行,到底是要打印或是要做其他操作就根据需求操作了。
不同数据类型导致的参数传递差别
函数的使用本质,是一个方法模块去调用另一个方法模块中的代码,即 函数A 调用函数 B。就像是 main() 去调用了 login() 或其他函数。
在很多情况下,被调用的方法模块需要使用到调用方的数据。即 函数 B 需要函数 A 提供数据。
在 java 中只有一种传递方式:值传递,都是把实参的值传递给形参。只是由于 java 中基本数据类型变量和引用数据类型变量保存值的方式不同,造成效果上的差异。
- 基本数据类型变量的值,是保存在变量本身当中,即将真正数据值传递给形参
- 引用数据类型变量的值,是指向真正数据的引用,所以传参时是把这个引用传给了形参,从而导致形参、实参都指向了同一个真正的数据 ```java // 基本数据类型参数 public static void main(String[] args) { int num = 5; int result = foo(num); System.out.println(“result:” + result + “,num:” + num); // result:6,num:5 } public static int foo(int num) { num++; return num; }
// 引用数据类型参数 public static void main(String[] args) { int[] arr = {1, 2, 3}; foo(arr); // 数组作为实参 System.out.println(arr[0] + “,” + arr[1] + “,” + arr[2]); }
public static void foo(int[] arr) { arr[0] = 5; // 引用数据类型整体被修改 }
<a name="ZUPgl"></a>
# 可变参数
JDK 新语法,使用在调用方法时,传入不定长度的参数,本质上是基于数组实现的。<br />传统的传数组参数,只能事先声明一个明确的数组对象。可变参数的写法在调用处既可以像传统调用方式传入一个数组,也可以采用元素罗列的方式进行传递,省略了声明数组的语句。作为形参的 args 仍然在 mthotd() 中认为是一个数组,使用数组的方法操作。
```java
public static void main(){
method("你好", "java"); // 实参传入 2 个
}
public static void method(String... args){ // 可变参数,接收了全部实参
System.out.println(args.length); // 2
System.out.println(args[0] + args[1]); // 你好java
}
方法调用栈
我们已经知道程序执行的顺序是:
- 默认从 main() 方法第一句顺序往下直到最后一句
- 分支语句、循环语句打破了顺序执行。分支语句控制了根据条件执行某部分代码,循环语句控制了部分代码被反复执行
方法的调用,其实也改变了顺序执行:
public static void main (String[] args) {
// 前置代码
mehtodA( );
// 后置代码
}
public static void methodA( ) {
// methodA 实现代码
}
一旦第 3 行被调用,第 6 行代码就开始运行,函数内部开始执行,直到执行完毕,才又回到第 4 行继续执行后置代码。
在方法进行连续调用时,先调用的方法后结束,后调用的方法先结束。形成了“先进后出”的效果,专业上称为“栈”。
那假设当前不止调用一个函数呢?
假设当前存在这样的调用结构:主函数 main() 调用了 turnOn() ,然后 turnOn() 函数内部又调用了setVolume() ,最后 setVolume() 中又调用 println() ,绘图结果就应该是:
方法重载
在一个 java 类中,允许命名多个重名的方法。当函数名相同情况下,依赖参数列表的不同来进行区分。参数列表的不同包括:
参数个数不同
- 参数类型不同
- 参数顺序不同 ```java // 接收 1 个 参数 public static void foo(String a) {
} // 接收 2 个参数。先 int 后 String public static void foo(int a, String b) {
} // 接收 2 个参数。先 String 后 int public static void foo(String a, int b) {
}
解释器会自动根据传入的参数的个数、类型、顺序的不同,自动调用相应函数:
```java
public static void main(String[] args) {
foo(5, "java");
foo("哒哒!");
foo("达咩!", 996);
}
// 接收 1 个 参数
public static void foo(String a) {
System.out.println("单参");
}
// 接收 2 个参数。先 int 后 String
public static void foo(int a, String b) {
System.out.println("先 int 后 String");
}
// 接收 2 个参数。先 String 后 int
public static void foo(String a, int b) {
System.out.println("先 String 后 int");
}
/*
输出结果:
先 int 后 String
单参
先 String 后 int
*/