在实际开发中,一个完整的程序往往由成千上万个方法组成。
例如,我们每天使用的微信、支付宝、淘宝等应用,每点击一个按钮、发送一条消息、完成一次支付,背后都会调用不同的方法来完成相应的功能。
那么,什么是方法?为什么要使用方法?
先来看一个简单的例子。
假设现在需要计算两个整数的和。
第一次:
inta=10;intb=20;System.out.println(a+b);第二次:
inta=100;intb=200;System.out.println(a+b);第三次:
inta=1000;intb=2000;System.out.println(a+b);如果程序中有几十甚至几百处这样的计算,我们只能不断地复制粘贴代码。
这种做法不仅会增加代码量,还会降低程序的可维护性。例如,当计算逻辑发生变化时,我们需要逐个修改所有地方,很容易出现遗漏。
因此,我们需要一种机制,把完成同一功能的代码封装起来,在需要的时候直接调用。
这种机制,就是方法(Method)。
一、什么是方法
概念:方法(Method)是完成某个特定功能的一段代码,它能够被重复调用,从而实现代码复用。
简单来说:方法就是将具有独立功能的代码封装起来,并通过方法名进行调用。
例如,我们每天都会使用手机的拍照功能。拍照时,我们并不需要关心:
- 相机如何打开?
- 如何自动对焦?
- 如何调整曝光?
- 如何保存图片?
我们只需要点击"拍照"按钮即可。这里的"拍照"就可以理解为一个方法。程序开发也是一样。
例如,Java 已经为我们提供了很多现成的方法:
System.out.println("Hello Java");其中:println()就是一个方法,它负责完成"输出内容到控制台"这一功能。我们只需要调用它,而不用关心它内部是如何实现的。
特点:一个方法通常具有以下特点:
- 完成一个独立的功能;
- 可以被多次调用;
- 能够接收数据(参数);
- 可以返回计算结果;
- 能够提高代码的复用性。
例如:
publicstaticvoidprintHello(){System.out.println("Hello Java");}以后只需要调用 printHello() 这个函数,就可以重复执行该功能。
二、为什么需要方法?
在程序开发中,我们经常会遇到大量重复代码。
例如,某学校管理系统需要多次输出欢迎信息。
没有使用方法时:
System.out.println("========== 欢迎使用学生管理系统 ==========");System.out.println("1. 添加学生");System.out.println("2. 删除学生");System.out.println("3. 修改学生");System.out.println("4. 查询学生");System.out.println("========== 欢迎使用学生管理系统 ==========");System.out.println("1. 添加学生");System.out.println("2. 删除学生");System.out.println("3. 修改学生");System.out.println("4. 查询学生");可以发现,同一段代码被重复编写了两次。
如果菜单内容发生变化,例如增加一个新的功能,那么所有地方都需要修改。
显然,这样的程序维护成本非常高。
如果这时候使用方法来进行封装:
publicstaticvoidprintMenu(){System.out.println("========== 欢迎使用学生管理系统 ==========");System.out.println("1. 添加学生");System.out.println("2. 删除学生");System.out.println("3. 修改学生");System.out.println("4. 查询学生");}需要显示菜单时,只需要调用:
printMenu();printMenu();程序运行结果与之前完全一致,但代码更加简洁。
方法带来的优势:
1、提高代码复用性
同一段代码只需要编写一次,就可以多次调用。
例如:
printMenu();可以随时重复使用。
2、降低维护成本
如果菜单内容需要修改,只需要修改方法中的代码即可。
例如:
publicstaticvoidprintMenu(){System.out.println("========== 欢迎使用学生管理系统 ==========");System.out.println("1. 添加学生");System.out.println("2. 删除学生");System.out.println("3. 修改学生");System.out.println("4. 查询学生");System.out.println("5. 退出系统");}所有调用该方法的地方都会自动生效。
3、提高代码可读性
比如说看到打印菜单的方法,我们就可以立马知道当前代码的作用就是输出菜单,方法名能够很好的表达程序意图。
4、符合模块化开发思想
一个大型项目通常会被拆分成多个模块。
例如:
- 用户登录
- 商品管理
- 订单管理
- 支付功能
每个模块内部又包含许多方法。
这样能够使整个项目结构更加清晰,也便于团队协作开发。
方法不是越多越好
刚学的时候可能认为:
“为了提高复用性,把每一行代码都封装成一个方法。”
这种做法其实并不可取。
例如:
publicstaticvoidprintOne(){System.out.println("1");}publicstaticvoidprintTwo(){System.out.println("2");}这种方法没有实际意义,反而增加了阅读成本。
因此,一个好的方法应该遵循:一个方法只完成一项独立的功能。
这也是软件开发中较常见的单一职责原则。
三、方法的定义
在现阶段中,定义一个方法的基本语法如下:
访问修饰符static返回值类型 方法名(参数列表){方法体}例如:
publicstaticvoidsayHello(){System.out.println("Hello Java");}这是一个最简单的方法。它的作用就是输出:Hello Java
方法定义的语法格式:
publicstaticvoid方法名(){}例如:
publicstaticvoidprintLine(){System.out.println("------------------------");}以后,只需要调用:printLine() , 就可以输出分割线。
方法定义的位置:
在 Java 中,方法必须定义在类(Class)中。
例如:
publicclassDemo{publicstaticvoidprintHello(){System.out.println("Hello");}}在 Java 中,从 Java 16 开始,允许在方法内部定义方法,这叫做局部方法(Local Method),但必须是 static 上下文且不能有访问修饰符。
publicclassDemo{publicstaticvoidmain(String[]args){// Java 16+ 支持voidprint(){System.out.println("Hello");}print();}}目前这个阶段我们暂时不讨论。
四、方法的组成
一个完整的方法由多个部分组成。
例如:
publicstaticintsum(inta,intb){returna+b;}| 组成部分 | 说明 |
|---|---|
| public | 访问修饰符 |
| static | 静态关键字 |
| int | 返回值类型 |
| sum | 方法名 |
| (int a, int b) | 参数列表 |
| {} | 方法体 |
| return | 返回结果 |
4.1 访问修饰符(public)
访问修饰符用于控制方法的访问权限。
目前学习阶段,我们统一使用:public
表示:
任何地方都可以访问该方法。
后续学习面向对象时,还会学习到:
- private
- protected
- 默认访问权限
4.2 static 关键字
目前定义的方法都会写:static
它表示:
当前方法属于类,可以直接通过类名或者在 main 方法中直接调用。
关于static的具体作用,我们将在后续《面向对象》章节进行详细讲解。
现阶段,只需要记住:定义方法时,先写上 static 即可。
4.3 返回值类型
返回值表示:方法执行完成后,是否需要把结果返回给调用者。
例如:
void表示:没有返回值。
publicstaticvoidprintHello(){}而int表示:方法会返回一个整数。
publicstaticintsum(inta,intb){}4.4 方法名
方法名就是方法的名称。比如说printHello()中的printHello就是方法名。
方法名建议遵循以下规范:
- 使用小驼峰命名法;
- 名称要见名知意;
- 一般使用动词开头。
例如:
login()printMenu()calculateTotal()findStudent()都属于比较规范的方法命名。
4.5 参数列表
参数用于接收调用者传递的数据。
(int a, int b)表示:方法需要两个整数。
如果没有参数,()括号不能为空省略,必须要写。
4.6 方法体
方法体就是{ }里面编写的代码。
例如:
publicstaticvoidprintHello(){System.out.println("Hello Java");}System.out.println("Hello Java");这里,就是方法体中的代码。
五、方法的调用
定义好方法之后,并不会自动执行。
只有调用方法时,方法中的代码才会运行。
例如:
publicclassDemo{publicstaticvoidprintHello(){System.out.println("Hello Java");}publicstaticvoidmain(String[]args){printHello();}}printHello();这里,就是方法调用。
在定义方法的时候只是创建了方法,调用方法的时候才会真正的去执行。
六、形参与实参
publicstaticvoidprintSum(inta,intb){System.out.println(a+b);}调用printSum(10, 20)的时候,这里的a、b和10、20到底是什么关系?
为什么调用方法时,把10、20传进去,方法里面就可以直接使用a、b?
要理解这一点,就需要先认识两个概念:
- 形参(Formal Parameter,形式参数)
- 实参(Actual Parameter,实际参数)
什么是形参
形参,全称形式参数。是在定义方法时声明的参数变量。
例如:
publicstaticvoidprintSum(inta,intb){System.out.println(a+b);}这里的int a 和 int b就是两个形参。
形参可以理解为一种特殊的局部变量,它们在方法被调用时由实参赋值初始化。
它们只有在方法执行时才会创建,方法结束后就会被销毁。
因此,形参并不会一直存在于程序中。
什么是实参
实参,全称实际参数。
是在调用方法时真正传递给方法的数据。
例如:
printSum(10,20);这里的10 和 20就是实参。
实参不仅可以是具体的数据,还可以是变量。例如:
intx=10;inty=20;printSum(x,y);这里的x 和 y同样属于实参。
形参与实参之间的关系
很多人都误以为:“调用方法以后,形参就是实参。”
其实并不是。
正确的理解应该是:调用方法时,实参会把自己的值复制一份,交给形参。
例如:
intx=10;print(x);对应的方法:
publicstaticvoidprint(intnum){System.out.println(num);}整个过程可以理解为:
调用之前->x=10调用时->复制了一份数据->赋值给了 num->num=10因此,x和num其实是两个完全不同的变量,只是它们保存的数据是一样的而已。修改其中一个变量的值的时候,这个时候不会影响到另一个变量。
完整的示例
例如:
publicclassDemo{publicstaticvoidprintAge(intage){System.out.println("年龄:"+age);}publicstaticvoidmain(String[]args){intmyAge=18;printAge(myAge);}}这里
| 位置 | 属于什么 |
|---|---|
| age | 形参 |
| myAge | 实参变量 |
| 18 | 实际的数据值 |
调用时:
myAge ↓18↓ 复制 ↓ age ↓18因此:方法内部操作的是age,而不是myAge
七、方法调用过程(栈内存分析)
很多初学者学习方法时,都会有一个疑问:方法调用之后,到底发生了什么?
理解这一点,对后续学习数组、面向对象、递归以及 JVM都非常重要。
下面通过一个简单的例子分析方法调用过程。
publicclassDemo{publicstaticvoidprint(intnum){System.out.println(num);}publicstaticvoidmain(String[]args){inta=10;print(a);}}第一步:程序启动
JVM 启动后会加载包含 main 方法的类,然后调用public static void main(String[] args)作为程序入口。
此时,main()方法进入栈内存(Stack)。
栈内存 ┌──────────────┐ │main()│ └──────────────┘然后执行:
inta=10;此时:
栈内存 ┌──────────────┐ │main()│ │ a=10│ └──────────────┘第二步:调用方法
执行:
print(a);Java 会进入:
print(intnum)// 进入这个方法内部于是:
JVM 会为print()方法再创建一块新的栈空间(栈帧)。
栈内存 ┌──────────────┐ │print()│ │ num=10│ ├──────────────┤ │main()│ │ a=10│ └──────────────┘这里的num就是复制得到的10,并不是a本身。
第三步:执行方法体
进入:
System.out.println(num);输出:10
第四步:方法执行结束
执行完成后:
print()立即结束。
对应的栈帧立即释放。
于是:
栈内存 ┌──────────────┐ │main()│ │ a=10│ └──────────────┘可以看到:
方法结束以后,num以及不存在了,因为它只是print()方法里面的局部变量。
方法调用遵循"先进后出"
Java 的方法调用采用栈(Stack)结构。
栈有两个特点:
- 后进入的方法先执行结束;
- 先进入的方法最后结束。
例如:
main()↓methodA()↓methodB()执行顺序:
main()↓methodA()↓methodB()↓methodB()结束 ↓methodA()结束 ↓main()结束这种结构称为:先进后出(Last In First Out,LIFO)
后续学习 JVM 时,我们会详细介绍栈帧(Stack Frame)的组成。
八、无返回值方法
8.1 无参无返回值方法
无参无返回值方法,是最简单的一类方法。
它既不需要接收数据,也不会返回结果。
语法如下:
publicstaticvoid方法名(){方法体}例如:
publicstaticvoidmain(String[]args){printLine();// 调用方法打印分割线System.out.println("欢迎学习 Java");printLine();}这种方法通常用于:
- 输出菜单
- 打印分隔线
- 输出提示信息
- 记录日志
8.2 有参无返回值方法
很多时候,一个方法需要根据不同的数据执行不同的操作。
例如,输出不同用户的姓名。
如果没有参数:
publicstaticvoidprintName(){System.out.println("张三");}无论调用多少次,都只能输出固定内容。
为了提高方法的灵活性,我们可以为方法添加参数。
例如:
publicstaticvoidprintName(Stringname){System.out.println(name);}调用:
printName("张三");printName("李四");printName("王五");可以看到,同一个方法能够处理不同的数据,而无需重复编写多个方法。
再例如,计算并输出两个整数的和:
publicstaticvoidprintSum(inta,intb){System.out.println(a+b);}调用:
printSum(10,20);printSum(100,200);printSum(5,8);与无参方法相比,有参方法最大的优势在于:方法的逻辑保持不变,但可以通过参数处理不同的数据,提高了代码的灵活性和复用性。
九、有返回值方法
例如:
publicstaticvoidprintSum(inta,intb){System.out.println(a+b);}虽然能够输出结果。
但是:如果我们需要继续使用这个计算结果呢?
例如:
计算两个数的和,再继续参与其他计算。
此时,仅仅输出结果已经不能满足需求。
因此,就需要使用有返回值方法。
什么是返回值
返回值就是:方法执行完成以后,把计算结果返回给调用者。
例如:
publicstaticintsum(inta,intb){returna+b;}这里的int表示:方法最终会返回一个整数。
调用:
intresult=sum(10,20);System.out.println(result);// 运行结果:30整个执行过程:
sum(10,20)↓30↓ result ↓ 输出返回值类型必须一致
例如:
publicstaticintgetAge(){return18;}返回值类型:int
所以:必须返回int。
注意:返回值的类型必须与声明类型兼容,即可以发生隐式类型转换,否则编译报错。
一个完整案例
编写一个方法,计算圆的面积。
publicstaticdoublegetArea(doubleradius){return3.14*radius*radius;}调用:
doublearea=getArea(5);System.out.println(area);// 输出:78.5可以看到:
计算逻辑被封装在方法内部。
调用者只需要获取最终结果即可。
十、return 关键字
在 Java 中,return是方法中非常重要的关键字。
它主要有两个作用。
作用一:返回数据
例如:
publicstaticintadd(inta,intb){returna+b;}这里的return表示把:a + b返回给调用者
比如说调用:
intresult=add(10,20);实际上是把计算好的结果返回回来,赋值给result。
作用二:结束方法
即使方法没有返回值,也可以使用:return来提前结束方法。
例如:
publicstaticvoidcheckAge(intage){if(age<18){System.out.println("未成年");return;}System.out.println("允许进入");}调用:
checkAge(15);输出 未成年 ,执行到return方法立即结束。
后面的代码System.out.println("允许进入");不会执行了。
return 后面不能再写代码
例如:
publicstaticvoidtest(){return;System.out.println("Hello");}编译错误。
因为:
执行了return后,方法已经结束。
后面的代码永远不会执行。Java 将其称为:不可达的语句。