概述
JavaSE--方法(上)--底层代码展示
- 前言
- 一丶方法概念以及使用
- (1)方法概念以及定义
- <1>方法的概念
- <2> 方法的定义
- (2)方法的调用(底层直接跳<2>)
- <1>方法的调用方法以及过程
- <2>关于形参和实参(底层代码展示)
- 二丶方法重载
- (1)方法重载概念以及存在意义
- <1>为何需要方法重载?
- <2> 方法重载的概念
- (2) 方法签名(底层跳<2>)
- <1> jvm调用方法的原理
- <2>具体实现过程(底层代码展示)
- <3>为什么与返回值类型无关?
- 总结
前言
犹豫了半天了,把自己的思路理了好几遍,终于开始写这篇博客,因为这次也是第一次尝试稍微深层次的书写,如果哪里有错误还请各位大佬指正!
一丶方法概念以及使用
(1)方法概念以及定义
<1>方法的概念
什么是方法?
举一个例子:
笔者中学阶段,偏科比较严重,理科学的比较好,有一天老师讲了一道难题,这道题的考点属于热门考点,然后老师为了测试同学们的回答情况,就叫了几位同学起来回答关于这个题目的一些问题,但是所有的同学中只有笔者完整的回答下来。
当下课铃声响起,不断的有同学过来问问题,笔者因为要写作业,没有那么多的时间讲解,所以把这道题完成流程和考点解析写在了笔者的本子上,一有同学过来问问题,我就把本子给他看,因为他们想要知道的点上面都有。
在上面这段小故事中,我们可以看到:
<1>笔者将解析写在了本子上,减少了重复工作,从而有时间做其他事情0
<2>同学们看到关于题目考点的各种解析,问题一下就可以解决。
<3>同学们直接拿起本子就可以看,而不必一遍一遍的和我询问。
编程也一样,方法的使用也是为了减少我们的重复性工作。并且便于后期维护。
<2> 方法的定义
那么,什么是方法?
所谓方法,就是一个代码片段,是一个能够被格式化的组织代码。相当于C语言中的函数!
方法有一个标准化的定义:
修饰符 返回值类型 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
这就是一个标准化的定义,是不是有点迷,那么我们具体实现一下:
问:定义一个方法,求两数相加
public static int add(int a,int b ){
int c = a + b;
return c;
}
//public static 修饰符(现固定搭配,后期介绍关于private等其他的)
//int 返回值类型
//add 方法名
//(int a,int b) 参数列表
//int c = a + b; 方法体代码
// return c; [return 返回值]
那么关于每个部分,我们有怎样的限定呢?
1.修饰符:这里的话目前不过多介绍,知道修饰符对方法进行限定就行
2.返回值类型:void的话,可以不写返回值,其他类型的话,一定要有返回值。
3.方法名:命名采用小驼峰式
4.参数列表:可以写,可以不写,但是写了的话,每个参数一定要有对应类型
5.方法体:一定要加注释,逻辑一定要完整。
6.方法的位置:无要求,因为都是从main方法开始执行,然后再去找main函数里面对应方法。
PS:方法和函数不一样,方法没有声明,所以要注释说清楚这个方法干啥的
(2)方法的调用(底层直接跳<2>)
<1>方法的调用方法以及过程
定义方法的时候,并不会执行方法的代码,直接在调用的时候才会执行,在这里方法的调用手段有三种:
1.直接调用
public static void main(String[] args) {
fun();
//输出: Hello,world!!
}
public static void fun(){
System.out.println("Hello,world!!");
}
2.打印调用
public static void main(String[] args) {
int a = 1;
int b = 1;
System.out.println(add(1,1));
//输出:2
}
public static int add(int a,int b ){
int c = a + b;
return c;
}
3.赋值调用
public static void main(String[] args) {
int a = 1;
int b = 1;
int c = add(a,b);
System.out.println(c);
//输出: 2
}
public static int add(int a,int b ){
int c = a + b;
return c;
}
在这里的话,方法都是从main方法开始执行,当执行到对应方法比如说add()时,才会去创建add(),然后调用完毕之后,add()方法销毁,这个过程是这样的:
调用方法 – 传递参数 – 找到方法地址 – 执行对应方法的方法体 --对应方法结束返回 – 回到main()方法继续往下执行
<2>关于形参和实参(底层代码展示)
首先,我们看下面这个程序:
很明显这个swap方法作用就是交换a,b的值,但是为什么在main()方法中,没有交换成功呢?是因为代码错误了吗?那继续:
很明显,在swap方法里面,确实是已经交换成功了,但是这个交换并没有影响到main()方法,这是为什么?
所以接下里在提到实参和形参这两个概念之前要说点其他的东西(划重点)
在JVM虚拟机中,内存连续存储,所以JVM对功能的不同有了不同的划分,大致划分成了五个区域:(1)虚拟机栈 (2)堆 (3)方法区 (4)本地方法栈(5)程序计数器
这里对的话,我们只对虚拟机栈进行讨论,那么何为虚拟机栈?
每一个方法的运行都会开辟一块内存空间,这块内存空间叫做"栈帧",在栈帧中,有 局部变量表,操作数栈,动态链接,返回地址,其他等等,栈帧随着方法的运行而创建,随着方法的结束而销毁。
在这里,我们先对局部变量表和操作数栈进行分析:
1 > 局部变量表(如图)
2 > 操作数栈(如图)
那么,有了以上这些概念,我们对上面程序的运行过程画一个大概的流程图
那么在程序运行的时候,JVM优先对当前栈帧进行操作,那么什么是当前栈帧?
虚拟机栈,栈顶的栈帧就称之为当前栈帧。
继续往下走,我们现在知道了虚拟机栈中这两个方法的栈帧存储以及处理方式,那么接下来就要具体进行探究,在swap()这个方法里面到底进行了那些操作,为什么main()方法中的a和b没有成功进行互换呢?
接下来,我们在IDEA中打开DOS窗口,在生成.class文件的目录下(src这个文件夹上面的out里面保存的是.class文件)输入 javap -v 打开对应的字节码文件。如下:
然后有一个主方法创建的栈帧,一个swap方法创建的栈帧,这里我们探究swap方法的栈帧:
图1:
图二:
图一和图二都是swap这个栈帧底下的,先看图一:
在这里给稍微涉及一下istore和iload的意思(因为笔者上次刚好研究过:若a = 1,a = a++ 后 ,输出a的值还是为1这个问题。所以比较简单的能说的上来,如果有错误还请指正!!!)
istore_n:将int型变量从操作数栈存入局部变量表的n号槽位
iload_n:将局部变量n号槽位压入操作数栈的栈顶
(PS:这里还有一个bipush,一块说了,意思就是把byte变为int后压入操作数栈)
可以看出这就是一个只对操作数栈和局部变量的操作。
再看图二:
这里的slot指的是槽位,也就是0号槽位,一号槽位,2号槽位,我们通过这两张图可以看出,swap这个方法创建的栈帧都是在自己的栈帧内进行操作,当这个方法结束以后,栈帧直接销毁,并没有任何返回或者说和mian()方法有任何的联系!!
所以当main()方法调用swap()方法,传递参数为基础类型参数的时候,仅仅只是将实参a和b的值拷贝了一份传递给swap()中的形参x和y,当swap()运行完之后无值返回,栈帧立马就销毁,所以main()这里的参数没有做出任何变动。
那么,就有了如下定义:
main()方法中传递的值为实际参数,也就是实参,被调用方法接收到的参数为实参的拷贝,所以叫做形参。
就像我们等差数列求和的公式:
n(a1+an) / 2
这的n和a1和an都是形参,题目中若是给了具体的值,那这个值就叫做实参。
二丶方法重载
(1)方法重载概念以及存在意义
<1>为何需要方法重载?
我们在书写方法的时候,会碰到很多问题,比如我们定义两个两个int型变量,在定义两个double型变量,这两个变量肯定是不可以用一个加法方法来解决的,所以要定义两个不同的方法,但是归根结底,它们本质上都是加法,仅仅由于参数类型不同,所以就要定义两个方法来解决:
public static int addInt(int a,int b){
return a + b;
}
public static double addDouble(double a,Double b){
return a + b;
}
那么我们能不能把他们全部改名成为add呢??
<2> 方法重载的概念
那么为了解决上面的问题,我们在Java中这样规定:
在Java中,如果几个方法的名称相同,参数列表不同,就称这几个方法被重载了
那么在这里,我们用代码实现一下:
public static void main(String[] args) {
int a = 1,b = 2;
double c = 3.0 , d = 4.0;
System.out.println(add(a,b)); //结果是:3
System.out.println(add(c,d)); //结果是:7.0
}
public static int add(int a,int b){
return a + b;
}
public static double add(double a,double b){
return a + b;
}
那么,对于上面的代码以及概念,我们对于重载有了约束:
1.方法名称必须相同
2.参数列表必须不同(参数类型不同,参数个数不同,参数类型相同但是前后排列不同)
3.与返回值类型无关
4.编译器在编译代码时候,会对实参类型进行推演,根据推演的结果来确定调用那个参数。
对于上面的第三条,只要三个不同满足一个,那么几个方法就不同
接下来,对第四个进行讨论。
(2) 方法签名(底层跳<2>)
<1> jvm调用方法的原理
在方法的重载中,方法名称相同,那么jvm是怎样知道它应该调用的是哪一个方法呢?
其实调用哪一个方法,在编译生成.class文件时候就已经确定好了,在编译期间,编译器扫描.java文件,对于参数传递过程进行推演,所以:
(1)add(a,b),推演就是int , int。然后在编译器中找参数类型为int,int的add方法(1)add(d,c)推演就是double,double 。然后在编译器中找参数类型为double的add方法
(此处参数参考上面方法重载代码中的参数)
如果没有找到,就进行隐式类型提升,如果提升之后能找到,那么就调用,找不到的话,就不调用。
代码如下:
public static void main(String[] args) {
int a = 1;
double c = 3.0;
System.out.println(add(a,c));//这里int型a整形提升为double类型
public static double add(double a,double b){
return a + b;
}
<2>具体实现过程(底层代码展示)
为什么在一个方法中,变量名称不能相同,但是在类中参数名称可以相同呢?
这里就要提一下方法签名的概念:
方法签名就是经过编译器编辑修改之后方法最终的名字。具体的展示为:方法全路径名称+参数列表+返回值类型。
这是什么意思呢?
就是编译器编译之后的方法名称,并不是单单通过方法名来进行命名,而是通过上面具体展示具体流程的来命名。
这个时候我们再次通过 javap -v命令来打开.class文件,具体展示如下:
然后我们找到对应的 #17
再找到对应的#29
(这里笔者的IDEA安装jclasslib总是失败,不然就不用通过DOS窗口打开了)
言归正传,从这里可以看出来,前面的Demo0822/CSDN就是方法全路径名称 , add就是方法名,(DD)D就是参数列表以及返回值的类型。
Demo0822/CSDN.add:(II)I同理,所以这两个方法其实是不同的。
<3>为什么与返回值类型无关?
上图中我们可以看出,方法最终的路径名返回值类型也算在了里面,也是两个路径名不同的点,但是为什么说方法重载与返回值类型无关?
具体证明,我们用反证法来推一下:如果说方法重载与返回值类型有关,那么如下图所示:
两个add方法返回值类型分别为long和int时候,直接打印调用,这个时候是分不清该用谁的,,如果我们把返回值类型为long的方法命名为add2:
这就证明两个方法单独分开了并无错误,如果命名和参数列表完全相同仅仅只有返回值类型不同,编译器在特定情况下是分不清该用谁的,就会报错。
总结
方法这一节为后面的对象打基础,所以能挖尽量深挖,加油!!!!
最后
以上就是眼睛大鸭子为你收集整理的JavaSE--方法(上)--代码底层展示前言一丶方法概念以及使用二丶方法重载总结的全部内容,希望文章能够帮你解决JavaSE--方法(上)--代码底层展示前言一丶方法概念以及使用二丶方法重载总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复