本篇介绍了Java中方法的概念以及方法的使用(方法的定义和调用,实参和形参的关系).方法重载的介绍和使用,编译器如何实现方法重载- -方法签名,介绍和使用方法调用自身解决问题的技巧–递归 对比递归和循环的优缺点
掌握Java中的方法
- 一.方法的概念及使用
- 1.什么是方法
- 2.方法的使用
- ①.方法的定义
- ②.方法的调用
- ③.实参和形参的关系
- 二.方法重载
- 1.什么是方法重载?
- 2.方法重载的使用
- 3.方法签名
- 三.学会使用递归
- 1.什么是递归?
- 2.递归的用法
一.方法的概念及使用
1.什么是方法
Java中的一个方法就是一个代码片段,而方法的作用就是可以更直观更便捷的一次或多次实现某一个功能,类似于C语言中的函数…
想象一下,当你要在多处实现同一个功能,这个功能的实现需要着几十上百行代码,那么每处都需要重复写上这些代码,看起来又不美观,代码重复率还高 冗余,看起来也很复杂,而方法正解决了这一问题,方法名即可以表示你需要实现功能的名字,更直观表达了功能的含义,每次需要这个功能只需要调用即可方法,而调用方法只需要一行代码,大大的减少了书写重复代码…
简单来说就是:要实现某一功能就将其封装成一个方法,尽量设计一个方法实现一个功能
方法存在的意义:
1 . 是能够模块化的组织代码(当代码规模比较复杂的时候).
2. 做到代码被重复使用, 一份代码可以在多个位置使用.
3. 让代码更好理解更简单.
4. 直接调用现有方法开发, 不必重复造轮子
2.方法的使用
要使用方法首先要定义方法,然后再调用方法…
①.方法的定义
方法语法格式:
// 方法定义
修饰符 (static) 返回值类型 方法名称([参数类型 形参 ...]){
方法体代码;//具体功能的实现
[return 返回值];//根据返回值类型返回对应的参数
}
示例一:实现一个计算闰年的方法:
public static boolean isLeapYear(int year){
if((0 == year % 4 && 0 != year % 100) || 0 == year % 400){
return true;
}else{
return false;
}
}
public是修饰符
static是静态修饰
boolean表示返回值类型为布尔类型
isLeapYear为方法名,表示方法是来判断是否为闰年的
括号里的int year 表示定义了一个整形类型的变量year
花括号里的为方法里具体功能的实现(实现判断是否为闰年)
return true 和return false表示最后的出口 如果是闰年会返回true反之则false
示例二:实现两个整数相加
public static int add(int x, int y) {
return x + y;
}
相比上一个求素数的方法,这个更简洁,就是整形变量x和y接受两个整数
最后返回x+y的值 得到两个整数的相加就是这个方法最后实现的功能
【注意事项】
- 修饰符:现阶段直接使用public static 固定搭配(补充:由static修饰后不需要实例化对象即可调用,且在当前类里的任何方法都可以直接使用方法名调用
而在其他类中的方法需要使用类名.方法名调用)- 返回值类型:如果方法有返回值,返回值类型必须要与返回的实体类型一致,如果没有返回值,必须写成 void
- 方法名字:采用小驼峰命名(首单词字母小写,后面每个单词首字母大写其余都小写)->isLeapYear
- 参数列表:如果方法没有参数,()中什么都不写,如果有参数,需指定参数类型,多个参数之间使用逗号隔开
- 方法体:方法内部要执行的语句
- 在java当中,方法必须写在类当中和类里面其他方法之外
- 在java当中,方法不能嵌套定义可以嵌套调用,
- 在java当中,没有方法声明一说
②.方法的调用
方法定义完后,需要执行方法内的功能就需要调用方法,调用一次即执行一次
调用方法格式:
方法名(value1,value2....);
//方法名加括号,括号里放需要传的参数多个则逗号隔开,无参数则括号里什么都不写
方法调用的执行过程:
调用方法—>传递参数—>找到方法地址—>方法形参接收传递的参数—>执行被调方法的方法体—>被调方法结束返回—>回到主调方法继续往下执行语句
补充:
方法的调用,实际会在内存中(这块内存也被称为栈区)开辟一段空间(被称作栈帧),这段空间的大小是能容纳方法实现所需要申请的空间的,之和会进行实参向形参的传递(是从右往左传参的),调用方法,栈帧开辟,调用结束.栈帧被销毁…
【注意事项】
1.定义方法的时候, 不会执行方法的代码. 只有调用的时候才会执行.
2.一个方法可以被多次调用.
3.调用的方法如果有返回值需要用一个对应类型的变量接受返回值
4.调用方法时括号里的实参可以是变量或者常量值,最后传的都是值!!!
5.实参的数据类型要和形参变量保持一致,否则会出现类型转换不必要的麻烦甚至还会因为类型不匹配而编译失败
调用方法示例一:
import java.util.Scanner; //使用Scanner类前需要导入Scanner类包
public class Note {
public static int add(int a,int b){
return a+b;
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in); //输入到控制台
while(scanner.hasNextInt()){ //循环输入整数
int num1=scanner.nextInt(); //从键盘获取整数
int num2=scanner.nextInt();
int sum= add(num1,num2);
System.out.println(sum);
}
}
}
为了实现在程序执行过程中可以读取键盘输入的数字并且能多组输入来测试add方法
介绍一种输入到控制台方法:
需要使用到Scanner类,而使用Scanner类需要用到import关键字来导入其所在的类包,一般在IDEA里输入Scanner按回车会自动导入,
Scanner scanner=new Scanner(System.in);是一个固定格式,可以看做是一个准备工作,这个类创建了一个引用变量 ,这个变量用来接受右边创建的一个对象的地址…
可以通过scanner这个变量调用这个对象里面的nextInt()方法即是从键盘读取一个整数返回,
'而while(hasNextInt)表示在这个while循环进入前会读取键盘输入的字符是否是数字,如果是则会进入循环不是则结束循环从而实现了多组输入,要结束多组输入需按Ctrl+D键
可以看到设计的add方法最后通过用键盘循环每次输入两个数字作为实参调用方法,能够实现将输入的两个数相加这一简单的功能…
定义的方法可以看做这个机器,当我们需要使用这个机器时就调用这个方法,将数字2和3作为原材料传给这个机器,最后它会加工给你最后成品数字5
③.实参和形参的关系
实参可以是变量也可以是常量值,但是最后调用函数时传递过去的是一个确切的值,而形参只能是变量,是用来接受传过来的实参值…
示例:
public class TestMethod {
public static void main(String[] args) {
int a = 10;
int b = 20;
swap(a, b);
System.out.println("main: a = " + a + " b = " + b);
}
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
System.out.println("swap: x = " + x + " y = " + y);
}
}
运行结果
swap: x = 20 y = 10 //swap方法里交换了值
main: a = 10 b = 20 //main方法里值未变化
可以看到当swap方法结束后,main方法传过去的仅仅是值 而a b两个变量的值并未发生改变!!!
要使调用swap方法使得值交换需要学习类和对象或数组的知识,使用引用方式来交换
注意:
1.形参接受的是实参传过来的一份临时拷贝的值!!!
2.实参和形参是两个完全不同的个体,形参的改变不会影响实参!!!
二.方法重载
1.什么是方法重载?
Java的方法重载,就是在类中可以创建多个方法,它们可以有相同的名字,但必须具有不同的参数,即或者是参数的个数不同,或者是参数的类型不同。
重载可以看做是一词多义,在生活很多场景中,往往有一个词语不同场景都被使用,但是这个词语可能会有多种不同或相似表达的含义,不同场景中表达不同的意思只需要一个词语就能完成,节省了造词和记词的时间,也能让别人心领神会,这可谓是中华文化,博大精深…
而在Java中的方法重载也就是允许有多个方法名相同的方法,但是方法的形参类型,大小,顺序这三者必须要有不同的,以此区分不同的方法,但返回类型不作要求…
2.方法重载的使用
当我们定义了一个add方法实现两个整数相加,后面又想再实现两个浮点数相加,甚至三个整数相加等等…这些方法实现的功能很相似但是又有不同,这时我们取名可以取add2 add3 add4等
可是随着要实现的功能越来越多,方法名字也越来越多,取方法名和记方法名又会花费很多时间
此时不妨使用方法重载,只需要一个方法名add即可,根据不同的需求改变方法的形参类型大小顺序从而得到不同功能的方法
示例:
public static void main(String[] args) { // 方法调用 方法重载
int a=1,b=2;
int num=add(a,2);
System.out.println(num);
double num2=add(1.0,2.0);
System.out.println(num2);
double num3 =add(1,2.0); //会对应最适合的方法 如果没有 double的方法对应 int可以提升为double double不能直接转为int 此时可以调用double double的方法
System.out.println(num3);
double num4=add(1.0,2);
System.out.println(num4);
}
public static double add(double a,double b){ //方法的重载 : 方法名可以一样 但是形参类型的个数 顺序 类型 不能相同 以此标记方法名相同但不同的方法
//可以实现定义多个相同名字的方法名但根据传参 类型 个数 顺序 的不同会自动识别调用对应的方法执行(避免起很多不同方法名)一词多用
return a+b+1;
}
public static double add(int a,double b){
return a+b+2;
}
public static double add(double a,int b){
return a+b+3;
}
public static int add(int a,int b){
return a+b;
}
可以看到上面代码定义了四个方法名相同的方法,但是每个方法的类型个数顺序又是不同,通过调用add方法传递的实参类型不同,最后调用了不同的add方法得到了四个不同的结果…
可见方法重载能节省取方法名和记忆方法名的时间,只需要实现设计好类型,根据调用方法时传参的不同从而调用不同的方法…
注意:
- 方法名必须相同
- 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同)
- 与返回值类型是否相同无关
- 编译器在编译代码时,会对实参类型进行推演,根据推演的结果来确定调用哪个方法
3.方法签名
方法名代表着方法的标识,而方法重载又能使多个不同的方法具有相同的方法名,这又是如何做到的?
在同一个作用域中不能定义两个相同名称的标识符。比如:方法中不能定义两个名字一样的变量,那为什么类中就可以定义方法名相同的方法呢?
方法签名即:经过编译器编译修改过之后方法最终的名字。具体方式:方法全路径名+参数列表+返回值类型,构成 方法完整的名字。
简单来说就是方法名只是我们编写代码时用到的用来标识方法的,在正在编译过程中,编译器会修改方法名,真正的方法名还包括方法名参数类型 返回类型等…
上述代码经过编译之后,然后使用JDK自带的javap反汇编工具查看,具体操作:
- 先对工程进行编译生成.class字节码文件
- 在控制台中进入到要查看的.class所在的目录
- 输入:javap -v 字节码文件名字即可
在运行命令框中使用JDK自带的反汇编查看到了对应的方法名,
Note.add:(I I)I为例 括号里的两个I 表达的是参数类型为两个整形 最右边的为返回类型整形
这也就是最后由编译器修改后的方法名(实际上返回类型是否相同并不会影响方法名)
方法签名中的一些特殊符号说明:
特殊字符 | 数据类型 |
---|---|
V | void |
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
[ | 数组(以[开头,配合其他的特殊字符,表述对应数据类型的数组,几个[表述几维数组) |
L | 引用类型,以L开头,以;结尾,中间是引用类型的全类名 |
三.学会使用递归
1.什么是递归?
我们学了方法的调用,知道方法内可以嵌套调用其他方法,那方法调用自身方法会发生什么呢?如果没有条件的控制,则会发生死循环导致栈溢出,但是如果使用的好,这是一个很简洁的解决问题的方法,也被称为递归
递归是执行方法过程中直接或间接调用自身的一种编程技巧也是一种解决问题的思路
生活中的例子:
从前有坐山,山上有座庙,庙里有个老和尚给小和尚将故事,讲的就是:
"从前有座山,山上有座庙,庙里有个老和尚给小和尚讲故事,讲的就是:
“从前有座山,山上有座庙…”
“从前有座山……”
从前有座山,山里有座庙,庙里有个老和尚给小和尚将故事,讲的就是…太困了,老和尚讲的睡着了
…
故事的故事最后每个故事里的老和尚都睡着了,故事也就结束了
"
自身中又包含了自己,该种思想在数学和编程中非常有用,因为有些时候,我们
遇到的问题直接并不好解决,但是发现将原问题拆分成其子问题之后,子问题与原问题有相同的解法,等子问题解 决之后,原问题就迎刃而解了。
2.递归的用法
一个方法在执行过程中调用自身, 就称为 “递归”.
递归相当于数学上的 “数学归纳法”, 有一个起始条件(也可以叫做终止条件), 然后有一个递推公式
当满足起始条件后,开始回推最后解决问题…
例如, 我们求 N!
假设N为5 求5! 5的阶乘为54321要乘很多数,不妨可以看成5*4!
而4!又可以看成4 * 3!..最后2 * 1! 而1!就等于1
把5看成N后
起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件.
或者当N=0的时候 N!为1(0! 结果也为1)
递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!
最后当计算机代替我们运算到N为1时会层层返回得到1! 2! 3! …最后得到N!
示例:
public static void main(String[] args) {
Scanner scanner =new Scanner(System.in);
while(scanner.hasNextInt()){ //循环读取 输入一个整数
int n=scanner.nextInt();
System.out.println(n+"的阶乘为"+fuc(n));
}
}
public static int fuc(int n){
if(n==0){
return 1;
}else{
return n*fuc(n-1); // n! *(n-1)! ......... *1! *0!
}
}
通过递归解决求N的阶乘问题,起始条件是当n==0时此时到了终点开始递推回去最后得到N!
递归的程序的执行过程不太容易理解, 要想理解清楚递归, 必须先理解清楚 “方法的执行过程”, 尤其是 “方法执行结束
之后, 回到调用位置继续往下执行”.
关于 “调用栈”
方法调用的时候, 会有一个 “栈” 这样的内存空间描述当前的调用关系. 称为调用栈.
每一次的方法调用就称为一个 “栈帧”, 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息.
因此递归的过程就是不断压栈,如果没有终止条件或者条件一直没有被满足,则最后可能栈会溢出造成程序崩溃!!!
可以看到当没有条件限制,最后会出现StackOverFlowError(栈溢出错误)
递归的必要条件:
- 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同
- 递归出口
递归和循环的区别:
递归的优点:用少量的代码就可以将一个复杂的问题化为多个小问题解决
递归的缺点:代码不好书写,开始难理解,时间和空间消耗比较大,很多计算是重复的,可能发生栈溢出循环的优点:易于理解, 执行效率高,
循环的缺点:代码量有时较多,有时需要嵌套很多循环,看起来较复杂
两者解决问题的方法,需根据不同场景使用才能体现两者的优势