1.什么是方法
方法,在很多语言中被称为函数。在java中称为方法,它代表着一个独立的,可复用的,功能。
如:
Math.random() 这句代码就是在调用一个现成的方法,它的功能是返回一个随机产生的[0,1)的double值。
Math类的random()方法
System.out.println() 这句代码也是在调用一个现成的方法,它的功能是输出()中的内容,并换行。
PrintStream类 println()方法,这里的System.out是一个PrintStream类的对象
Scanner input = new Scanner(System.in);
int num = input.nextInt(); 这句代码的 nextInt()也是一个方法,它的功能是从控制台接收一个int值
2.为什么要定义方法
当我们需要重复使用一段代码的时候,我们通常将这段代码封装为一个方法。
3.如何定义方法
一个完整的方法必须有五部分
修饰符 返回值类型 方法名(形参列表){
方法体
}
方法体语句:实现方法功能的语句,可以是1~n条语句组成。
修饰符:对方法的使用范围进行规定。
方法名:就是一个标识符而已,需要见名知意,遵循小驼峰命名法(即从第2个单词开始,首字母大写),代表方法的功能。
返回值类型,有两种情况:
-
void:代表这个方法运行完之后,没有结果返回。方法体语句中没有 “return 结果; ”的语句
-
非void:可以是int,double,String, int[]等各种Java的类型,代表这个方法运行完之后,必须给调用者返回一个结果。方法体语句中必须由“ return 结果;” 的语句
形参列表
-
():无参或空参
-
(数据类型 参数名) 或(数据类型 参数名1, 数据类型 参数名2) 或更多个参数:有参
//无参
public static void test1() {
System.out.println("哈哈哈哈");
}
//有参
public static void test2(int a, int b) {
System.out.println(a < b ? b + "大" : a + "大");
}
4.如何调用定义好的方法
4.1 跨类调用
类名.方法名(【实参列表】); //没有接收返回值
变量 = 类名.方法名(【实参列表】);//用变量接收方法的返回值
实参列表必须与被调用方法的形参列表一一对应。即实参的个数、类型、顺序必须与形参一一对应。
如果被调用方法的返回值类型是void,那么就不需要接收。
如果被调用方法的返回值类型不是void,是int,int[],double等其他类型,就可以用变量接收返回值。接收返回值的变量类型也要合适。
public class Test {
public static void main(String[] args) {
Tools.test1(4,8);
}
}
4.2 本类调用
方法名(【实参列表】); //没有接收返回值
变量 = 方法名(【实参列表】);//用变量接收方法的返回值
实参列表必须与被调用方法的形参列表一一对应。即实参的个数、类型、顺序必须与形参一一对应。
如果被调用方法的返回值类型是void,那么就不需要接收。
如果被调用方法的返回值类型不是void,是int,int[],double等其他类型,就可以用变量接收返回值。接收返回值的变量类型也要合适。
public class Test {
public static void main(String[] args) {
//调用Tools中的方法
test1(4,8);
}
public static void test1(int a, int b) {
System.out.println(a < b ? b + "大" : a + "大");
}
}
4.3 方法小结
-
方法不调用不执行
-
方法调用1次执行1次
5.方法内存分析
简单分析:
6.方法的参数传递机制
6.1 谁给谁传值?
实参给形参传值
6.2 传什么值?
-
基本数据类型:实参给形参的是数据值,或者数据值的副本
-
引用数据类型:实参给形参的是地址值,或者地址值的副本
6.3 形参的修改反过来会影响实参吗?
(1)基本数据类型
参数是基本数据类型时,形参的修改与实参完全无关。
(2)引用数据类型
参数是引用数据类型时,形参数组修改了元素,相当于实参数组修改了元素
除了8种基本数据类型以外的,都是引用数据类型,包括数组、类等。
(3)如何让基本数据类型的实参得到形参修改后的值?
(4)引用数据类型的形参所有修改都影响实参吗?
不是。当形参换新地址值时,就与实参无关。
(5)如何得到新数组?
案例:
public static void swap(int a,int b){
int temp = a;
a = b;
b = temp;
}
public static void main(String[] args){
int x = 1;
int y = 2;
System.out.println("x="+x + ",y=" + y);//x=1,y=2
swap(x,y);
System.out.println("x="+x + ",y=" + y);//x=1,y=2
//因为a,b是基本数据类型的形参。修改它又没有返回,与实参无关,所以实参不变。
}
public static void reverse(int[] arr){
for(int left=0,right=arr.length-1; left<right; left++,right--){
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
public static void main(String[] args){
int[] nums = {1,2,3};
for(int i=0; i<nums.length; i++){
System.out.print(nums[i] + " ");
}
System.out.println();
//1 2 3
reverse(nums);
System.out.println("调用reverse之后:")
for(int i=0; i<nums.length; i++){
System.out.print(nums[i] + " ");
}
System.out.println();
//3 2 1
//因为arr是数组类型,即引用数据类型,nums实参给arr形参的是数组的首地址,在reverse方法中没有产生新数组对象,
//直接针对旧数组修改了,那么相当于实参与形参是同一个数组。
}
7.可变参数
7.1 如何定义
【修饰符】 返回值类型 方法名(数据类型... 参数名){
}
可变参数的形参:它可以当做数组使用。
可变参数对于的实参,可以是:
-
对应类型的数组
-
0~n个的元素
public class TestVarParam {
//需求:定义一个方法,可以求任意个整数的和
public static int sum(int[] nums){
int result = 0;
for (int i = 0; i < nums.length; i++) {
result += nums[i];
}
return result;
}
public static void main(String[] args) {
//求1,2,3的和
int[] arr1 = {1,2,3};
int sum1 = sum(arr1);
System.out.println("sum1 = " + sum1);
//求1,2,3,4,5
int[] arr2 = {1,2,3,4,5};
int sum2 = sum(arr2);
System.out.println("sum2 = " + sum2);
//求1,2,3,4,5,6,7,8,9,10
// int sum3 = sum(1,2,3,4,5,6,7,8,9,10);//报错
// System.out.println("sum3 = " + sum3);
}
}
public class TestVarParam2 {
//需求:定义一个方法,可以求任意个整数的和
//在sum方法中,可变参数nums直接当成数组使用即可。
public static int sum(int... nums){
// System.out.println(nums.length);
int result = 0;
for (int i = 0; i < nums.length; i++) {
result += nums[i];
}
return result;
}
public static void main(String[] args) {
//求1,2,3的和
int[] arr1 = {1,2,3};
int sum1 = sum(arr1);
//实参arr1是数组类型,可以被可变参数nums赋值
System.out.println("sum1 = " + sum1);
//求1,2,3,4,5
int[] arr2 = {1,2,3,4,5};
int sum2 = sum(arr2);
//实参arr2是数组类型,可以被可变参数nums赋值
System.out.println("sum2 = " + sum2);
//求1,2,3,4,5,6,7,8,9,10
int sum3 = sum(1,2,3,4,5,6,7,8,9,10);
//10个元素直接 给可变参数nums赋值
System.out.println("sum3 = " + sum3);
int sum4 = sum();
//0个元素直接 给可变参数nums赋值
//等价于
//int[] arr4 = new int[0];//长度为0的数组
// int sum4 = sum(arr4);
System.out.println("sum4 = " + sum4);
}
}
7.2 可变参数 VS 数组
所有数组类型的形参都可以换成可变参数吗?
不是
数组类型的形参 | 可变参数的形参 | |
---|---|---|
位置 | 没有限制 | 必须是最后一个形参 |
个数 | 没有限制 | 只能有1个 |
实参 | 必须是对应类型的数组 | (1)可以是对应类型的实参(2)也可以是0~n个元素 |
总结:
-
可变参数不能完全取代数组
-
能使用可变参数的地方,可变参数比数组类型灵活
public class TestVarParam3 {
public static void swap1( int i, int j,int[] arr){
//....
}
public static void swap2(int i, int j,int... arr){
//....
}
public static void copy1(int[] arr1 , int[] arr2){
//....
}
public static void copy2(int[] arr1 , int... arr2){
//....
}
}
8.方法的重载
方法的重载(Overload):是指在同一个类中,定义了两个或更多个方法名相同,形参列表不同的方法,这些方法的关系就是重载关系。这里的形参列表不同是指形参的类型,或个数、或顺序不同。方法的重载与返回值类型、形参名、修饰符等无关。
方法重载调用的原则:
-
先找实参的类型、个数、顺序与形参的类型、个数、顺序完全匹配的,找到就执行它。
-
如果没有最匹配的,就找形参的类型>实参的类型,即形参的类型可以兼容实参的类型,找到就执行它。
-
如果没有找到最匹配的,也没有找到可以兼容的,或者找到多个兼容程度相同的,都会报错。
public class TestOverload {
//定义一个方法,可以返回两个整数的最大值
public static int max(int a, int b){
return a > b ? a : b;
}
//定义一个方法,可以返回两个小数的最大值
public static double max(double a, double b){
return a > b ? a : b;
}
//定义一个方法,可以返回三个整数的最大值
public static int max(int a, int b,int c){
int bigger = a > b ? a : b;
return bigger > c ? bigger : c;
}
//定义一个方法,可以求任意个整数的最大值
public static int max(int... nums){
int biggest = nums[0];
for (int i=1; i<nums.length; i++){
if(nums[i] > biggest){
biggest = nums[i];
}
}
return biggest;
}
//定义一个方法,可以求任意个整数的最大值,至少传入1个整数
public static int max(int first, int... nums){
int biggest = first;
for (int i=0; i<nums.length; i++){
if(nums[i] > biggest){
biggest = nums[i];
}
}
return biggest;
}
public static void main(String[] args) {
int result1 = max(5, 6);
System.out.println("result1 = " + result1);
double result2 = max(5.0, 6.0);
System.out.println("result2 = " + result2);
double result3 = max(5, 6.0);
System.out.println("result3 = " + result3);
// double result4 = max(5.0, 6.0, 4.0);//报错,无法找到最匹配的,也无法找到可以兼容的
// System.out.println("result4 = " + result4);
int result5 = max(5, 6,4);
System.out.println("result5 = " + result5);
// int result6 = max(5, 6,4,8);//报错,因为与max(int... nums)和max(int first, int... nums)都同样兼容
// System.out.println("result6 = " + result6);
}
//理论上来说,可以是形参的顺序不同的两个方法构成重载
/*public static int max(char a, int b){
return a > b ? a : b;
}
public static int max(int a, char b){
return a > b ? a : b;
}*/
}
9.简单的递归使用
递归的难在于找到数学规律,数学公式。
什么是递归?递归是指一个方法自己调用自己。
案例1:求n!
/*求n!
1=1
2=1*2
3=1*2 *3
4=1*2*3 *4
f(1)=1
f(2)=f(1)*2
f(3)=f(2)*3
f(4)=f(3)*4
*/
public static int fun(int n){
if (n<=1){
return 1;
}
return n*fun(n-1);
}
案例2:猴子吃桃
猴子吃桃子问题,猴子第一天摘下若干个桃子,当即吃了所有桃子的一半,还不过瘾,又多吃了一个。第二天又将仅剩下的桃子吃掉了一半,又多吃了一个。以后每天都吃了前一天剩下的一半多一个。到第十天,只剩下一个桃子。试求第一天共摘了多少桃子?
/*
猴子吃桃子问题,猴子第一天摘下若干个桃子,当即吃了所有桃子的一半,还不过瘾,又多吃了一个。
第二天又将仅剩下的桃子吃掉了一半,又多吃了一个。以后每天都吃了前一天剩下的一半多一个。
到第十天,只剩下一个桃子。试求第一天共摘了多少桃子?
f(10) = 1
f(9) = (1+1)*2=(f(10)+1)*2
f(1) = n
f(2) =n-( n/2)-1=n/2-1
f(n)=(f(n+1)+1)*2
*/
public static int eat(int n){
if (n>10){
return 0;
}
if (n==10){
return 1;
}
return (eat(n+1)+1)*2;
}
案例3:走台阶
有n级台阶,一次只能上1步或2步,共有多少种走法?
/*
有n级台阶,一次只能上1步或2步,共有多少种走法?
1级 1 f(1)=1
2级 11 2 f(2)=2
3级 111 12 21 f(3)=3=f(2)+f(1)
4级 1111 121 211 112 221 f(4)=4=f(3)+f(2)
f(n)=f(n-1)+f(n-2)
*/
public static int move(int n){
if (n<=2){
return n;
}
return move(n-1)+move(n-2);
}
案例4:不死神兔
用递归实现不死神兔:故事得从西元1202年说起,话说有一位意大利青年,名叫斐波那契。 在他的一部著作中提出了一个有趣的问题:假设一对刚出生的小兔一个月后就能长成大兔, 再过一个月就能生下一对小兔,并且此后每个月都生一对小兔,没有发生死亡, 问:现有一对刚出生的兔子2年后(24个月)会有多少对兔子?
/*
用递归实现不死神兔:故事得从西元1202年说起,话说有一位意大利青年,名叫斐波那契。
在他的一部著作中提出了一个有趣的问题:假设一对刚出生的小兔一个月后就能长成大兔,
再过一个月就能生下一对小兔,并且此后每个月都生一对小兔,没有发生死亡,
问:现有一对刚出生的兔子2年后(24个月)会有多少对兔子?
*注意每对小兔一个月后,才能生兔子
1 1 f(1)=1
2 1 f(2)=1
3 2 f(3)=2
4 3 f(4)=3
f(4)=f(3)+f(2)=3
f(n)=f(n-1)+f(n-2)
*/
public static int f(int n){
if (n<=2){
return 1;
}
return f(n-1)+f(n-2);
}