方法就是一个代码片段. 类似于 C 语言中的 “函数”.
方法存在的意义(不要背, 重在体会):
回忆一个之前写过的代码: 计算 1! + 2! + 3! + 4! + 5!
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 5; i++) {
int tmp = 1;
for (int j = 1; j <= i; j++) {
tmp *= j;
}
sum += tmp;
}
System.out.println("sum = " + sum);
}
这个代码中使用双重循环, 比较容易写错.
接下来我们可以使用方法来优化这个代码
/**
* 计算 n 阶乘
* @param n
* @return
*/
public static int tmp(int n){
int ret = 1;
for(int i = 1; i <= n; i++){
ret = ret * i;
}
return ret;
}
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 5; i++) {
//产生 1 - 5的数
int ret = tmp(i);//tmp 这个函数就是 计算 n 阶乘
sum = sum + ret;
}
System.out.println("sum = " + sum);
}
像上面tmp函数的功能就是计算一个数的阶乘,这就是函数的基本用法,并且这个函数的功能是唯一的
// 方法定义
public static 方法返回值 方法名称([参数类型 形参 ...]){
方法体代码;
[return 返回值];
}
public static int sumAdd(int x, int y){
return x + y;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
int sum = sumAdd(a, b);
System.out.println("sum = " + sum);
}
方法的参数叫做形参,main方法的参数叫实参,形参相当于实参的一份拷贝,这种传参方式被称为 按值传递(传值)
由此引申出一个知识点, 在Java中 是没有 传址 的概念,只有传值,所以我们在调用方法的时候,只需要注重 形参 和 实参 的类型 和 个数 是否匹配(相同)
想了解 c方面的或者想对比一下的,可以看这篇文章函数栈帧销毁与创建(vs2013)- 修改版
- public 和 static 两个关键字在此处具有特定含义, 我们暂时不讨论, 后面会详细介绍.
- 方法定义时, 参数可以没有. 每个参数要指定类型
- 方法定义时, 返回值也可以没有, 如果没有返回值, 则返回值类型应写成 void
- 方法定义时的参数称为 “形参”, 方法调用时的参数称为 “实参”.
- 方法的定义必须在类之中, 代码书写在调用位置的上方或者下方均可.
- Java 中没有 “函数声明” 这样的概念
基本规则
定义方法的时候, 不会执行方法的代码. 只有调用的时候才会执行.
当方法被调用的时候, 会将实参赋值给形参.
参数传递完毕后,就会执行到方法体代码.
当方法执行完毕之后(遇到 return 语句), 就执行完毕, 回到方法调用位置继续往下执行
一个方法可以被多次调用.
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("交换前:" + "a = " + a + " b = " + b);
swap(a, b);
System.out.println("交换前:" + "a = " + a + " b = " + b);
}
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
原因分析:
刚才的代码, 没有完成数据的交换.
对于基础类型来说, 形参相当于实参的拷贝. 即 传值调用
可以看到, 对 x 和 y 的修改, 不影响 a 和 b
解决办法: 传引用类型参数 (例如数组来解决这个问题),这个数组 的 时候在讲。
就像上面的swap函数,是没有返回值的
//有两种形式:
public static void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
public static void fun(int c){
return;
}
有些时候我们需要用一个函数同时兼容多种参数的情况, 我们就可以使用到方法重载
错误的例子
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 12.5;
double b2 = 18.5;
double ret2 = add(a2, b2);//图 13
System.out.println("ret2 = " + ret2);
}
public static int add(int x,int y){
return x+y;
}
由于参数类型不匹配, 所以不能直接使用现有的 add 方法
那么是不是应该创建这样的代码呢?
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = addInt(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = addDouble(a2, b2);
System.out.println("ret2 = " + ret2);
}
public static int addInt(int x, int y) {
return x + y;
}
public static double addDouble(double x, double y) {
return x + y;
}
如果要计算float类型,那是不是要写一个addFloat函数,那这样不就很麻烦吗
所以就要使用方法的重载
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b);
System.out.println("ret = " + ret);
double a2 = 10.5;
double b2 = 20.5;
double ret2 = add(a2, b2);
System.out.println("ret2 = " + ret2);
double a3 = 10.5;
double b3 = 10.5;
double c3 = 20.5;
double ret3 = add(a3, b3, c3);
System.out.println("ret3 = " + ret3);
}
public static int add(int x, int y) {
return x + y;
}
public static double add(double x, double y) {
return x + y;
}
public static double add(double x, double y, double z) {
return x + y + z;
}
方法的名字都叫 add. 但是有的 add 是计算 int 相加, 有的是 double 相加; 有的计算两个数字相加,
有的是计算三个数字相加. 同一个方法名字, 提供不同版本的实现, 称为 方法重载
参考JavaSE的标准文档
可以不是用一个类,继承关系也是可以的
一个方法在执行过程中调用自身, 就称为 “递归”.
递归相当于数学上的 “数学归纳法”, 有一个起始条件, 然后有一个递推公式
递推公式是递归的重点,推出它,递归就很好写。
public static void main(String[] args) {
func();
}
这样写,就不满足 使用递归所需的第一个条件 没有一个终止递归的条件
所以该程序会无限递归死循环,最终导致栈溢出(栈空间不是无限大,是有限,func方法一直调用自己下去,最终肯定是会 爆满/溢出 的)
因为 每次调用 func方法时,都会为其在栈上空间开辟块自己的空间
public static void func(){
func();
}
1. 有一个趋近于终止的条件(停止递归的条件)
2. 自己调用自己
public static void main(String[] args) {
//递归求 N 的阶乘
int n = 5;
int ret = fun(n);
System.out.println(ret);
}
public static int fun(int n){
if(n == 1){
return 1;
}
return n * fun(n-1);
}
1! == 1 //这就是我们的起始条件,也是我们的终止条件
2! == 2*1 == 2 * 1!
3! == 3*2*1 == 3 * 2!
4! == 4*3*2*1 == 4 * 3!
5! == 5*4*3*2*1 == 5 * 4!
你发现了 求 5!的值,它是 5 * (5-1)!
而 4! == 4 * (4-1)!
3! == 3 * (3-1)!
2! == 2 * (2-1)!
5! == 5 * 4!== 5 * 4 * 3! == 5 * 4 * 3 * 2! == 5 * 3 * 2 * 1
程序运行 跟我们刚才藏宝殿是一样的,一层一层的抢。从最里面的开始抢
也就从我们终止条件开始
n == 1; return 1;// 这里的返回值 是返回到 调用它的上一层级(藏宝殿核心是第一层吗,抢完了,肯定去抢第二程啊)
n == 2 return 2* factorial(2-1) == 2 * 1 == 2
n == 3; return 3 * 2 == 6
n == 4; return 4 * 6 == 24
n == 5; return 5 * ==
考验你们的时候,在下面评论,看你们到底有没有看懂学会
递归的字面意思
递归 n * factorial(n-1)l n-1 就是它传过去的值,称为递, factorial(n-1)返回的值,称为归
结合称为: 递归。
![public static void main(String[] args) {
//递归求 N 的阶乘
int n = 5;
int ret = fun(n);
System.out.println(ret);
}
public static int fun(int n){
if(n == 1){
return 1;
}
return n * fun(n-1);
}
关于 “调用栈”
方法调用的时候, 会有一个 “栈” 这样的内存空间描述当前的调用关系. 称为调用栈.
每一次的方法调用就称为一个 “栈帧”, 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息.
后面我们借助 IDEA 很容易看到调用栈的内容
public static void main(String[] args) {
//按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)
int n = 4569;
print(n);
}
public static void print(int n){
if(n > 9){
print(n / 10);
}
System.out.println(n % 10);
}
public static void main(String[] args) {
//递归求 1 + 2 + 3 + ... + 10
int a = 10;
int ret = sumAdd(a);
System.out.println(ret);
}
public static int sumAdd(int n){
if(n == 1){
return 1;
}
return n + sumAdd(n - 1);
}
public static void main(String[] args) {
//代码示例3 写一个递归方法,输入一个非负整数,返回组成它的数字之和.
// 例如,输入 1729, 则应该返回1+7+2+9,它的和是19
int a = 1729;
int ret = printAdd(a);
System.out.println(ret);
}
public static int printAdd(int num){
if(num <= 9){
return num;
}
return num % 10 + printAdd(num / 10);
}
斐波那契数列介绍
斐波那契数列:1,1,2,3,5,8,13,21,34,55,89…
public static void main(String[] args) {
///代码示例4 求斐波那契数列的第 N 项
//斐波那契数列:1,1,2,3,5,8,13,21,34,55,89...
int n = 10;
int ret = fibonacci(n);
System.out.println(ret);
}
public static int fibonacci(int n){
if(n == 1 || n == 2){
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
当我们求 fibonacci(40) 的时候发现, 程序执行速度极慢. 原因是进行了大量的重复运算
public static int cont = 0;
public static void main(String[] args) {
///代码示例4 求斐波那契数列的第 N 项
//斐波那契数列:1,1,2,3,5,8,13,21,34,55,89...
int n = 40;
int ret = fibonacci(n);
System.out.println(ret);
System.out.println("函数执行了:" + cont + "次");
}
public static int fibonacci(int n){
if(n == 1 || n == 2){
return 1;
}
if(n == 3){
cont++;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
可以使用迭代/循环的方式来求斐波那契数列问题, 避免出现冗余运算
public static int cont = 0;
public static int fibonacci(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;
cont++;
}
System.out.println("函数执行了:" + cont + "次");
return cur;
}
public static void main(String[] args) {
///代码示例4 求斐波那契数列的第 N 项
//斐波那契数列:1,1,2,3,5,8,13,21,34,55,89...
int n = 40;
int ret = fibonacci(n);
System.out.println(ret);
}
递归是一种重要的编程解决问题的方式.
有些问题天然就是使用递归方式定义的(例如斐波那契数列, 二叉树等), 此时使用递归来解就很容易.
有些问题使用递归和使用非递归(循环)都可以解决. 那么此时更推荐使用循环, 相比于递归, 非递归程序更加高效