一.方法的概念及使用
什么是方法?
方法就是一个代码片段. 类似于 C 语言中的 "函数"。它能够做到一份代码在多个位置使用,不必重复造轮子。
比如要判断某一年是不是闰年?
public class test {
public static void main(String[] args) {
int year=2023;
if((year%4==0&&year%100!=0)||year%400==0) {
System.out.println("是闰年");
} else {
System.out.println("是平年");
}
}
}
上面一串代码只能判断2023年是不是闰年,那如果想判断2024,2025...年呢,都要把这份大致相同的代码敲一遍未免太不现实,所以可以定义方法来解决。
1.1 方法的定义
方法定义格式
修饰符 返回值类型 方法名称(参数列表){
方法体;
(return 返回值;)
}
例如,我们可以写一个方法判断某个年份是否是闰年
public static boolean isLeapYear(int year){
if((year%4==0&&year%100!=0)||year%400==0) {
return true;
}else{
return false;
}
}
1.现阶段使用修饰符public static即可
2.如果方法有返回值,返回的实体类型必须和返回值类型一致;如果没有返回值,返回值类型写void
3.方法必须写在类中
4.方法不可以嵌套定义
5.在Java中没有方法声明(也就是说,即使某个方法定义在了main方法的后面,main调用它时也不必提前声明)
1.2 方法的调用及执行过程
调用方法很简单,只需要在使用该方法的地方传入参数即可
比如,用刚才的isLeapYear方法判断2023,2024,2025是否是闰年
public static void main(String[] args) {
System.out.println(isLeapYear(2023));
System.out.println(isLeapYear(2024));
System.out.println(isLeapYear(2025));
}
//false true false
那么程序在运行的时候是如何调用方法的呢?
调用方法-->传递参数-->找到该方法的地址-->执行被调方法的方法体-->被调方法结束返回-->继续执行下面的代码

1.3 实参和形参的关系
形参和实参的定义
形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值
实参是调用该方法时实际传入的值
再次借用一下上面的代码
public static boolean isLeapYear(int year){//year是形参
if((year%4==0&&year%100!=0)||year%400==0) {
return true;
}else{
return false;
}
}
public static void main(String[] args) {
System.out.println(isLeapYear(2023));
System.out.println(isLeapYear(2024));
System.out.println(isLeapYear(2025));
}//2023,2024,2025是实参,在调用时传给year
形参永远只是实参的拷贝,形参和实参是两个实体,形参和实参互不影响
比如,交换两个整型变量的值
public static void swap(int x,int y) {//x,y是a,b的形参
int tmp=x;//交换x,y的值
x=y;
y=x;
}
public static void main(String[] args) {
int a=10,b=5;
swap(a,b);
}
//运行结果
x=5 y=10
a=10 b=5
可以看到,在swap函数交换之后,形参x和y的值发生了改变,但是main方法中a和b还是交换之前的值,即没有交换成功
原因分析
实参a和b是main方法中的两个变量,其空间在main方法的栈中,而形参x和y的空间在swap方法运行时的栈中。在swap方法调用时,只是将实参a和b中的值拷贝了一份传递给了形参x和y,因此对形参x和y操作不会对实参a和b产生任何影响。
那么怎么真正交换a和b呢?
可以用数组的方式
public static void swap(int[] arr) {
int tmp=arr[0];
arr[0]=arr[1];
arr[1]=tmp;
}
public static void main(String[] args) {
int [] arr={10,5};
swap(arr);
System.out.println(arr[0]);
System.out.println(arr[1]);
}
或许这个方法在你看来有点low,但是我的能力有限,目前只能给出这一个方法了

总结:
对于基础类型来说, 形参相当于实参的拷贝. 即 传值调用
二.方法重载
现在需要一个可以实现两个数比较大小的方法,于是你进行了一顿猛如虎的操作
public static boolean isMax(int x,int y) {
return x>y?true:false;
}
但是出题人并没有说是让你比较int类型,他想让你比较double和double,double和int...
你在心里把他骂了一通,然后又敲了两段代码
public static boolean isMax2(double x,double y) {
return x>y?true:false;
}
public static boolean isMax3(double x,int y) {
return x>y?true:false;
}
然后出题人鸡蛋里挑骨头:“你列了三个比大小的函数,让我怎么记住它们的参数类型啊!”
他的话虽然可恶,但是合理,那么能不能使用同一个名字对不同的参数进行比较呢?
2.1 方法重载的概念
在自然语言中,经常会出现“一词多义”的现象,比如:“好人”,看到这个词时,你会想到扶老奶奶过马路的小学生,还是刚才那位“好人”出题者。一个词语如果有多重含义,那么就说该词语被重载了,具体代表什么含义需要结合具体的场景。
在Java中方法也是可以重载的
方法重载的条件:
1. 方法名必须相同
2. 参数列表必须不同(参数的个数不同/参数的类型不同/类型的次序必须不同)
3. 与返回值类型是否相同无关
来判断一下下面的方法是否构成重载
public static int add(int x,int y) {
return x+y;
}
public static long add(int x, int y) {
return x+y;
}
public static double add(double x,double y) {
return x+y;
}
public static int add(int x, int y) {
return x+y;
}
第一个是不可以构成重载的,返回值不会对调用方法产生影响
第二个是可以的,它们的参数类型不同
2.2 为什么方法可以重载
在同一个作用域中不能定义两个相同名称的标识符。比如:方法中不能定义两个名字一样的变量,那为什么类中就可以定义方法名相同的方法呢?
因为有方法签名
方法签名即:经过编译器编译修改过之后方法最终的名字。具体方式: 方法全路径名+参数列表+返回值类型,构成方法完整的名字。
public class test {//方法一
public static int add(int x, int y) {
return x+y;
}
public static double add(double x,double y) {//方法二
return x+y;
}
}

在main方法中分别调用了方法一和方法二,上图是它们经过编译器修改后的名字,()里的字母表示参数类型,()后面的字母显示返回值的类型,其中I表示int,D表示double
如果想要详细了解编译器的方法签名规则,可以看下表
特殊字符 | 数据类型 |
V | void |
Z | boolean |
B | byte |
C | char |
S | short |
I | int |
J | long |
F | float |
D | double |
[ | 数组(以[开头,配合其他的特殊字符,表述对应数据类型的数组,几个[表述几维数组) |
L | 引用类型,以L开头,以;结尾,中间是引用类型的全类名 |
看到这里,或许你会疑问,既然返回值的类型也被方法签名标记出来了,为什么不能构成方法重载呢?
实际上答案很简单,虽然在编译完成后的方法签名是不一样的,但是在调用的时候会存在二义性。比如下面这串代码
public static int add(int x,int y) {//方法1
return x+y;
}
public static long add(int x, int y) {//方法2
return x+y;
}
public static void main(String[] args) {
add(3,5);
}
究竟该调用方法一还是方法二是不确定的,所以自然不会编译通过
三. 递归
相信大家肯定都听过这个故事
从前有坐山,山上有座庙,庙里有个老和尚给小和尚将故事,讲的就是:
"从前有座山,山上有座庙,庙里有个老和尚给小和尚讲故事,讲的就是:
"从前有座山,山上有座庙..."
"从前有座山……"

有些时候,我们遇到的问题直接并不好解决,但是发现将原问题拆分成其子问题之后,子问题与原问题有相同的解法,等子问题解决之后,原问题就迎刃而解了,这就是递归的思想
3.1 递归的概念
一个方法在执行过程中调用自身, 就称为 "递归".
递归相当于数学上的 "数学归纳法", 有一个起始条件, 然后有一个递推公式.
例如, 我们求 N!
起始条件: N = 1 的时候, N! 为 1. 这个起始条件相当于递归的结束条件.
递归公式: 求 N! , 直接不好求, 可以把问题转换成 N! => N * (N-1)!
递归的条件:
1 . 将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同
2 . 终止条件
例如,实现一个方法求n!
public static int fac(int n) {
if(n==0||n==1) return 1;
return n*fac(n-1);
}
上面的方法虽然很简单,却可以帮助我们更好地理解递归
假设n=5

3.2 递归不是万能的
实现一个方法,求斐波那契数列的第n项
0 1 1 2 3 5 8 ...(为了防止被喷,把网址给你们斐波那契数列介绍)
public static int fib(int n) {
if(n==1) return 0;
if(n==2) return 1;
return fib(n-1)+fib(n-2);
}
当我们执行fib(40)的时候,发现程序执行速度极慢. 原因是进行了大量的重复运算.
用下面一个代码验证一下fib(3)被执行的次数
public static int count = 0;//类的成员变量,以后会提到
public static int fib(int n) {
if(n==1) return 0;
if(n==2) return 1;
if(n==3) count++;
return fib(n-1)+fib(n-2);
}
public static void main(String[] args) {
System.out.println(fib(40));
System.out.println(count);
}
//运行结果
63245986
39088169
你会惊奇的发现fib(3)的运行次数如此之多,所以不如直接使用一个循环来求
public static int fib(int n) {
int last2 = 1;
int last1 = 1;
int cur = 0;
for (int i = 3; i <= n; i++) {
cur = last1 + last2;
last2 = last1;
last1 = cur;
}
return cur;
}
此时程序的执行效率也会大大提高
3.3 递归的练习
一. 写一个递归方法,按顺序打印一个数字的每一位
(例如 1234 打印出 1 2 3 4)
public static void print(int num) {
if(num<=9) {
System.out.print(num);
System.out.print(' ');
} else {
print(num/10);
System.out.print(num%10);
System.out.print(' ');
}
}
二. 写一个递归方法,输入一个非负整数,返回组成它的数字之和
例如,输入 1729, 则应该返回1+7+2+9,它的和是19
public static int sum(int num) {
if(num<10) {
return num;
}
return num%10+sum(num/10);
}