JavaSe基础---方法
# 1. 方法概述
- 我们先不讲方法是什么,先来看一段代码,分析以下程序存在哪些缺点,应该如何去改进:
1 |
|
- 以上代码完成了三个求和的功能,每一次求和的时候都把代码重新写了一遍,显然代码没有得到 “重复利用”,表面上看是三个功能,但实际上只是 “一个” 求和功能,只不过每一次参与求和的实际数值不同。
我们现在需要一种方式能让我们把功能性代码写一次,然后给这个功能性代码传递不同的数据,来完成对应的功能。 - 这就需要我们掌握 java 语言中的方法机制,接下来大家看看改进之后的代码:
1 |
|
- 通过以上程序我们可以看出,其实方法也没什么神秘的,方法其实就是一段普通的代码片段,并且这段代码可以完成某个特定的功能,而且可以被重复的调用 / 使用。java 中的方法又叫做 method,在 C 语言中叫做函数。
- 从现在开始大家以后在写代码的时候就要有定义方法的意识了,只要是可以独立出来的功能,我们都可以定义为单独的一个方法来完成,如果以后需要使用此功能时直接调用这个方法就行了,这样就会有效增强代码的” 可复用性” 了。
- 总结以上,就可以轻易的得到方法的定义了。
# 2. 方法的使用
- 定义 / 声明方法的语法格式如下所示:
1 |
|
- 接下来我将列出方法的相关规则,其中一些规则目前可能需要大家死记硬背,还有一些规则希望大家在理解的前提下进行记忆:
- (1)[修饰符列表],此项是可选项,不是必须的,目前大家统一写成 public static,后面的课程会详细讲解。
- (2)返回值类型,此项可以是 java 语言当中任何一种数据类型,包括基本数据类型,也包括所有的引用数据类型,当然,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写 void。返回值的类型例如:byte,short,int,long,float,double,boolean,char,String,void 等。
- (3)方法名,此项需要是合法的标识符,开发规范中要求方法名首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意,例如:login、getUsername、findAllUser 等。
- (4)形式参数列表 (int a, int b),此项又被称为形参,其实每一个形参都是 “局部变量”,形参的个数为 0~N 个,如果是多个参数,则采用半角 “,” 进行分隔,形参中起决定性作用的是参数的数据类型,参数名就是变量名,变量名是可以修改的,也就是说 (int a , int b) 也可以写成 (int x , int y)。
- (5)方法体,由一对儿大括号括起来,在形参的后面,这个大括号当中的是实现功能的核心代码,方法体由 java 语句构成,方法体当中的代码只能遵循自上而下的顺序依次逐行执行,不能跳行执行,核心代码在执行过程中如果需要外部提供数据,则通过形参进行获取。
- (6)方法调用,当一个方法声明之后,我们应该如何去让这个方法执行呢,当然,这个时候就需要亲自去调用这个方法了,调用方法的语法格式是(前提是方法的修饰符列表中带有 static 关键字):“类名。方法名 (实际参数列表);”
- 注意:现在学习的方法都是带有 static 关键字的,也就是使用类就能够进行直接调用的,将来还有不带有 static 关键字的方法,是只有对象才能够调用的方法。
- (7)关于实参,方法在调用的时候,实际传给这个方法的数据被称为实际参数列表,简称实参,java 语法中有这样的规定:实参和形参必须一一对应,所谓的一一对应就是,个数要一样,数据类型要对应相同。例如:实参 (100 , 200) 对应的形参 (int x , int y),如果不是一一对应则编译器就会报错。当然也可能会存在自动类型转换,例如:实参 (100 , 200) 也可以传递给这样的形参 (long a , long b)。
- (8)当在 a () 方法执行过程中调用 b () 方法的时候,并且 a () 方法和 b () 方法在同一个类当中,此时 “类名.” 可以省略不写,但如果 a () 方法和 b () 方法不在同一个类当中,“类名.” 则不能省略。
- 方法定义总结:整体来说方法的声明语法是很简单的,我相信每个人都能记住,其实我觉得方法的定义难度最大的不是语法,而是方法在定义的时候,返回值类型定为什么类型比较合适?方法的形式参数列表中定义几个参数合适?每个参数的数据类型定义为什么比较合适?方法的定义要不要传参数?传什么参数?要不要返回值?要什么返回值?这一切的设计都是由需求做决定的!!!
# 3. 方法的返回值
# 3.1 返回值概述
- 每个方法都是为了完成某个特定的功能,例如:登录功能、求和功能等,既然是功能,那么当这个功能完成之后,大多数情况下都会有一个结果的,比如,登录成功了或者失败了(true/false),求和之后最后的结果是 100 或者 200,等等。这个结果本质上就是一个数据,那么既然是一个数据,就一定会有对应的类型,所以在方法定义的时候需要指定该方法的返回值类型。(注意:开发中也经常会设计没有返回值的方法)
- java 语言中方法的返回值类型可以是任何一种数据类型,包括基本数据类型,也包括引用数据类型,例如:byte,short,int,long,float,double,boolean,char,String,Student(自定义类)等。当然,如果这个方法在执行完之后不需要返回任何数据,返回值类型必须写 void 关键字,不能空着不写。
# 4. 方法执行过程中内存的变化
# 4.1 栈数据结构
- 常见的数据结构有哪些呢?例如:栈、队列、链表、数组、树、图、堆、散列表等。目前我们先来学习一下栈(stack)数据结构,这是一种非常简单的数据结构。
如图所示: - 栈(stack),它是一种运算受限的线性表。其限制是:仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈(push),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从
一个栈删除元素又称作出栈、退栈或弹栈(pop),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 - 通过以上的学习,我们可以得知栈数据结构存储数据有这样的特点:先进后出,或者后进先出原则。也就是说最先进去的元素一定是最后出去,最后进去的元素一定是最先出去,因为一端是开口的,另一端是封闭的。
# 4.2 方法执行过程中内存的变化
- 对于虚拟机中的内存结构,目前我们只研究其中的 “栈”。方法在执行过程中需要的内存空间在栈中分配。开始调用 main 方法,main 方法被调用的瞬间,会给 main 方法在 “栈” 内存中分配所属的活动空间,此时发生压栈动作,main 方法的活动空间处于栈底。也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java 虚拟机才会在 “栈内存” 当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。由于栈的特点是先进后出,所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main 方法最先被调用,那么它一定是最后一个结束的。换句话说:main 方法结束了,程序也就结束了。案例:
1 |
|
执行结果:
# 5. 方法重载 /overload
- 关于方法重载是什么,以及怎么进行重载,这些我们目前先不去研究,先来看看以下代码不使用方法重载机制,存在哪些缺点?
1 |
|
- 运行结果:
- 我们可以看到以上三个方法功能 “相似”,都是求和,只不过参与求和的数据类型不同,因此定义了三个方法,分别起了三个不同的方法名。这种方式会增加程序员编程的压力,因为程序员起码要记忆三个方法名,另外代码也不是很美观。所以,我们需要使用方法重载机制来解决这样的问题。
- 方法重载(overload)是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java 编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。调用方法时通过传递给它们的不同个数和类型的实参来决定具体使用哪个方法。
- 什么情况下我们考虑使用方法重载呢?在同一个类当中,如果多个功能是相似的,可以考虑将它们的方法名定义的一致,使用方法重载机制,这样便于程序员的调用,以及代码美观,但相反,如果两个方法所完成的功能完全不同,那么方法名也一定要不一样,这样才是合理的。
构成方法重载需要满足以下 3 个条件:- 在同一个类当中。
- 方法名相同。
- 参数列表不同:个数不同算不同,顺序不同算不同,类型不同也算不同。
- 接下来我们来看看以下程序哪些方法构成了方法重载,哪些没有:
1 |
|
- 通过观察以上代码以及测试结果我们得知:
# 6. 方法递归
# 6.1 什么是方法递归
- 方法递归就是方法自己调用自己
- 我们先来看一段代码
1 |
|
执行结果:
- 我们可以看到以上代码的执行过程中,一直输出 “m begin”,“m over” 一次也没有输出,直到最终发生了错误:java.lang.StackOverflowError,这个错误是栈内存溢出错误,错误发生后,JVM 退出了,程序结束了。 实际上以上代码在 m () 方法执行过程中又调用了 m () 方法,方法自身调用自身,这就是方法递归调用。m () 方法一直在被调用(方法中的代码必须遵循自上而下的顺序依次逐行执行,不能跳行执行),对于栈内存来说一直在进行压操作,m () 方法从未结束过,所以没有弹栈操作,即使栈内存足够大(也是有限的内存),总有一天栈内存会不够用的,这个时候就会出现栈内存溢出错误。通过以上研究得出递归必须要有合法的结束条件,没有结束条件就一定会发生 StackOverflowError。
- 递归总结:
- 一个递归程序有的时候存在合法有效的终止条件,但由于递归的太深,在还没有等到条件成立的时候,栈内存已经发生了溢出,这种情况也是存在的,所以实际开发中我们尽可能使用循环来代替递归算法,原则是:能不用递归尽量不用,能用循环代替的尽可能使用循环。
-
-
JavaSe基础---方法
https://rofgd.github.io/2020/05/06/JavaSe基础---方法/