文章目录
- 一、方法的重载
- 介绍
- 案例
- (1)案例1
- 练习
- (1)练习1
- (2)练习2
- (3)练习3
- (4)练习4
- 二、可变个数形参的方法
- 介绍
- 举例
- (1)举例1
- (2)举例2
- (3)举例3
- (4)举例4
- (5)举例5
- 练习
- 三、方法值传递机制
- 介绍
- 举例
- (1)案例1
- (2)案例2
- 练习
- (1)练习1
- (2)练习2
- (3)练习3
- 四、递归方法
- 介绍
- 举例
- (1)举例1
- (2)举例2
- (3)举例3
- (4)举例4
- 练习
- (1)练习1
- (2)练习2
- (3)练习3
- (4)练习4
一、方法的重载
再谈方法之1:方法的重载(overload)
介绍
在idea里面键盘输入Ctrl+N
,然后输入arrays:
点击第一个工具类,进入之后按Ctrl+F12,可以看到很多同名的方法,比如equals方法:
这些方法方法名一样,形参列表不一样,这些方法其实就可以称为重载的方法。
-
定义:在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
满足这样特征的多个方法,彼此之间构成方法的重载。 -
总结为:“两同一不同”
两同:同一个类、相同的方法名
一不同:参数列表不同。① 参数个数不同 ② 参数类型不同
(int,String)与(String,int)不同
注意:方法的重载与形参的名、权限修饰符、返回值类型都没有关系。
- 以下都是add方法重载:
public class test{
public void add(int i,int j){
}
public void add(int i,int j,int k){
}
public void add(String s1,String s2){
}
public void add(int i,String s){
}
public void add(String s,int i){
}
}
- 下面两个就无法构成重载:
public void add(int i,int j){
}
public void add(int m,int n){
}
编译器报错信息:
此时编译器分辨不出两个方法的区别,比如现在调用它们:
public class test{
public static void main(String[] args){
test p=new test();
test.add(12,4);
}
public void add(int i,int j){
}
public void add(int m,int n){
}
}
这时候编译器会报错,因为分辨不出来调用的add是哪一个。
- 下面两个也无法构成重载:
public void add(int i,int j){
}
public int add(int m,int n){
}
方法名之前的内容根本不影响方法重载的判断,就是add
前面的public int
,只需要看add
后面的()
里面的内容即可。只要括号里面的东西不一样就叫重载,一样就不能构成重载,编译器分辨不出到底是哪一个方法,就会报错。
-
举例
Arrays类中sort(xxx[] arr)、binarySearch(xxx[] arr,xxx)、equals(xxx[] ,yyy[]) -
如何判断两个方法是相同的呢?(换句话说,编译器是如何确定调用的某个具体的方法呢?)
先看方法名,再看形参列表。
- 如何判断两个方法是相同的呢?
方法名相同,且形参列表相同。(形参列表相同指的是参数个数和类型都相同,与形参名没关系)
要求:在一个类中,允许存在多个相同名字的方法,只要他们的形参列表不同即可。
- 编译器是如何确定调用的某个具体的方法呢?
先通过方法名确定了一波重载的方法,进而通过不同的形参列表,确定具体的某一个方法。
- 在同一个类中不允许定义两个相同的方法。
案例
(1)案例1
看下面代码:
public class test{
public static void main(String[] args){
test p=new test();
test.add(12,4);
}
public void add(int i,int j){
System.out.println("111111");
}
public void add(double m,double n){
System.out.println("222222");
}
}
当我们调用test.add(12,4)
的时候,12和4可以看作int类型–>匹配第一个add方法,12和4也可以看作自动类型提升–>匹配第二个add方法。
那么到底匹配哪一个方法呢?
其实编译器很懒,能不自动提升就不提升,两个add方法可以同时存在,同时存在的时候优先调用没有提升的(第一个add方法);
若没有第一个add方法,它就会自动提升为double类型,进而调用第二个add方法。
如若非要让
test.add(12,4);
调用第二个add方法,那么可以强制类型转换它:test.add((double)12,4);
但是如果现在调用的是这样:
public class test{
public static void main(String[] args){
test p=new test();
test.add(6,3.0);
}
public void add(int i,int j){
System.out.println("111111");
}
public void add(double m,double n){
System.out.println("222222");
}
}
因为3.0是double类型,所以前面的6只能自动类型提升为double类型,进而调用的是第二个add方法。
练习
(1)练习1
练习1:判断与void show(int a,char b,double c){}构成重载的有:
a)void show(int x,char y,double z){} //no
b)int show(int a,double c,char b){} //yes
c) void show(int a,double c,char b){} //yes
d) boolean show(int c,char b){} //yes
e) void show(double c){} //yes
f) double show(int x,char y,double z){} //no
g) void shows(){double c} //no
(2)练习2
练习2:
编写程序,定义三个重载方法并调用。方法名为mOL。
三个方法分别接收一个int参数、两个int参数、一个字符串参数。
分别执行平方运算并输出结果,相乘并输出结果,输出字符串信息。
public class test1 {
public int mOL(int a){
return a*a;
}
public int mOL(int a,int b){
return a*b;
}
public void mOL(String a){
System.out.println(a);
}
}
public class test1_1 {
public static void main(String[] args) {
test1 ob=new test1();
int c=ob.mOL(5);
System.out.println(c);
int d=ob.mOL(4,6);
System.out.println(d);
ob.mOL("helllo");
}
}
运行结果:
(3)练习3
练习3:
定义三个重载方法max():
第一个方法求两个int值中的最大值,
第二个方法求两个double值中的最大值,
第三个方法求三个double值中的最大值,并分别调用三个方法。
public class test2 {
public int max(int a,int b){
return (a>=b)?a:b;
}
public double max(double a,double b){
return (a>=b)?a:b;
}
public double max(double i,double j,double k){
/*double tempMax=max(i,j);
return max(tempMax,k);*/
return (max(i,j)>k)?max(i,j):k;
}
}
(4)练习4
//面试题
public class InterviewTest {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3};
System.out.println(arr);//地址值
char[] arr1 = new char[]{'a','b','c','d','e'};
System.out.println(arr1);//abc
boolean[] arr2 = new boolean[]{false,true,true};
System.out.println(arr2);//地址值
}
}
输出结果:
看似我们调用的是一个方法,其实是不一样的。
可以将鼠标光标放在println
上。
可以看到,第一个println
调用的形参是Object
:(第三个也一样)
数组是引用类型,编译器认为调用的是Object,默认打印地址值。
而第二个println
调用的形参是char[]
:(当数组是char类型的,编译器会帮咱们遍历一下)
将鼠标光标放在println上面,按住Ctrl键,点击println
,然后按Ctrl+F12
:
二、可变个数形参的方法
再谈方法之2:可变个数形参的方法(jdk5.0)
介绍
- 使用场景
在调用方法时,可能会出现方法形参的类型是确定的,但是参数的个数不确定。此时,我们就可以使用可变个数形参的方法
方法形参的类型是确定的,但是参数的个数不确定
在操作数据库的时候,可能会使用到下面的方法:
String sql="update customers set name=?,email=? where id=?"; //修改客户表
String sql1="update customers set name=? where id=?";
public void update(String sql,Object ... objs); //上述两种情况,第一个传进来的有多少个问号第二个参数就有多少项
-
格式:
(参数类型 ... 参数名)
-
说明:
① 可变个数形参的方法在调用时,针对于可变的形参赋的实参的个数可以为:0个、1个或多个
② 可变个数形参的方法与同一个类中,同名的多个方法之间可以构成重载
③ 特例:可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载。
④ 可变个数的形参必须声明在形参列表的最后
⑤ 可变个数的形参最多在一个方法的形参列表中出现一次
举例
(1)举例1
public class ArgsTest {
public static void main(String[] args) {
ArgsTest test=new ArgsTest();
test.print();
test.print(1,2);
}
public void print(int...num){
System.out.println("111");
}
}
结果:
(2)举例2
public class ArgsTest {
public static void main(String[] args) {
ArgsTest test=new ArgsTest();
test.print();
test.print(1);
test.print(1,2);
}
public void print(int...num){
System.out.println("111");
}
public void print(int i){
System.out.println("222");
}
public void print(int i,int j){
System.out.println("333");
}
}
结果:
先找确定的,没有冲突就行。
(3)举例3
可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载。
下面代码有误:
public void print(int...num){
System.out.println("111");
}
public void print(int[] nums){
}
注意:编译器认为int...num
与int[] nums
一致。
因此这样也不会报错:
public class ArgsTest {
public static void main(String[] args) {
ArgsTest test=new ArgsTest();
test.print(new int[]{1,2,3});
}
public void print(int...num){
System.out.println("111");
}
/*public void print(int[] nums){
}*/
}
在方法里面,将它们都遍历一遍:
编写的时候不一样,调用的时候就当它是数组。
public void print(int...num){
System.out.println("111");
for (int i = 0; i <num.length ; i++) {
System.out.println(num[i]);
}
}
/*public void print(int[] nums){
for (int i = 0; i <num.length ; i++) {
System.out.println(num[i]);
}
}*/
若是数组,调用的时候需要new一下,但现在只需要将数放在这即可。
test.print(1,2,3);
//test.print(new int[]{1,2,3});
(4)举例4
可变个数的形参必须声明在形参列表的最后
以下方式是正确的:
public void print(int i,int...num){
}
以下方式会报错:
public void print(int...num,int i){
}
报错信息:
(5)举例5
这俩个方法在一个类中可以同时存在:
//①
public void print(int...num){
}
//②
public void print(int i,int...num){
}
但是不建议写,虽然不报错,但是调用的时候编译器会懵:
练习
题目: n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回 空字符串" "
public class StringConCatTest {
public static void main(String[] args) {
StringConCatTest test=new StringConCatTest();
String info=test.concat("-","hello","world");
System.out.println(info); //hello-world
System.out.println(test.concat("/", "hello")); //hello
System.out.println(test.concat("-")); //无
}
public String concat(String operate,String...strs){
String result="";
for (int i = 0; i < strs.length; i++) {
if(i==0){
result+=strs[i];
}else{
result+=(operate+strs[i]);
}
}
return result;
}
}
测试结果:
三、方法值传递机制
介绍
再谈方法之3:方法的值传递机制(针对变量赋值操作)
对于方法中的变量有两种:①方法内声明的变量(普通局部变量)②方法形参的位置声明的变量,在调用方法的时候给它传具体的实参。
- (复习)对于方法内声明的局部变量来说:如果出现赋值操作
如果是基本数据类型的变量,则将此变量保存的数据值传递出去。
如果是引用数据类型的变量,则将此变量保存的地址值传递出去。
public class ValueTransferTest {
public static void main(String[] args) {
//main方法中声明的变量是局部变量
//1.基本数据类型的局部变量
int m=10;
int n=m; //传递的是数据值
System.out.println("m="+m+",n="+n); //m=10,n=10
m++;
System.out.println("m="+m+",n="+n); //m=11,n=10
//2.引用数据类型的局部变量
//2.1数组类型
int[] arr1=new int[]{1,2,3,4};
int[] arr2=arr1; //传递的是地址值
arr2[0]=10;
System.out.println(arr1[0]); //10
//2.2对象类型
Order order1=new Order();
order1.orderId=1001;
Order order2=order1;
order2.orderId=1002;
System.out.println(order1.orderId); //1002
}
}
class Order{
int orderId;
}
👻图示:
①基本数据类型
②数组类型
③对象类型
- 方法的参数的传递机制:值传递机制(存什么传什么)
2.1 概念(复习)
形参:在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。
实参:在调用方法时,方法名后面括号()中的使用的值/变量/表达式称为实际参数,简称实参。
2.2 规则:实参给形参赋值的过程
> 如果形参是基本数据类型的变量,则将实参保存的数据值赋给形参。
> 如果形参是引用数据类型的变量,则将实参保存的地址值赋给形参。
①基本数据类型
public class ValueTransferTest1 {
public static void main(String[] args) {
ValueTransferTest1 test=new ValueTransferTest1();
//1.对于基本数据类型的变量来说
int m=10;
test.method1(m);
System.out.println("m="+m); //10
}
public void method1(int m){
m++;
}
}
内存:
②引用数据类型
public class ValueTransferTest1 {
public static void main(String[] args) {
ValueTransferTest1 test=new ValueTransferTest1();
//2.对于引用数据类型的变量来说
Person p=new Person();
p.age=10;
test.method2(p);
System.out.println(p.age); //11
}
public void method2(Person p){
p.age++;
}
}
class Person{
int age;
}
内存:
- 面试题:Java中的参数传递机制是什么?值传递。(不是引用传递)
举例
4.案例
(1)案例1
交换两个变量的值。
public class ValueTransferTest2 {
public static void main(String[] args) {
int m=10;
int n=20;
System.out.println("交换前:m="+m+",n="+n);
//交换两个变量的值
int temp=m;
m=n;
n=temp;
System.out.println("交换后:m="+m+",n="+n);
}
}
能不能将交换两个变量的值封装到一个方法中?
下面的方法不可行:
public class ValueTransferTest2 {
public static void main(String[] args) {
int m=10;
int n=20;
System.out.println("交换前:m="+m+",n="+n); //交换前:m=10,n=20
//1.操作1
/*//交换两个变量的值
int temp=m;
m=n;
n=temp;*/
//2.操作2
//调用方法
ValueTransferTest2 test=new ValueTransferTest2();
test.swap(3,4); //调用完就会出栈
System.out.println("交换后:m="+m+",n="+n); //交换后:m=10,n=20
}
public void swap(int m,int n){
int temp=m;
m=n;
n=temp;
}
}
swap方法压入栈之后,只在swap里交换了值,然后swap弹出栈,不会改变原有的值
核心代码解析:
(2)案例2
下面这种方式能不能实现互换呢?
public class ValueTransferTest3 {
public static void main(String[] args) {
Data data=new Data();
data.m=10;
data.n=20;
System.out.println("交换前:m="+data.m+",n="+data.n);
//1.操作1
int temp=data.m;
data.m=data.n;
data.n=temp;
System.out.println("交换后:m="+data.m+",n="+data.n);
}
}
class Data{
int m;
int n;
}
显然是可以的,结果如下:
接下来,将交换的代码写在方法里面,可不可行呢?(见操作2)
public class ValueTransferTest3 {
public static void main(String[] args) {
Data data=new Data();
data.m=10;
data.n=20;
System.out.println("交换前:m="+data.m+",n="+data.n);
//1.操作1
/*int temp=data.m;
data.m=data.n;
data.n=temp;*/
//2.操作2
ValueTransferTest3 test=new ValueTransferTest3();
test.swap(data);
System.out.println("交换后:m="+data.m+",n="+data.n);
}
public void swap(Data data){
int temp=data.m;
data.m=data.n;
data.n=temp;
}
}
class Data{
int m;
int n;
}
运行结果如下:
核心代码解析:
练习
(1)练习1
1.定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积。
public class Circle {
double radius; //半径
public double findArea(){
return Math.PI*radius*radius;
}
}
- 定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:
public void printAreas(Circle c, int time)
。 - 在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
例如,time为5,则输出半径1,2,3,4,5,以及对应的圆面积。 - 在main方法中调用printAreas()方法,调用完毕后输出当前半径值。程序运行结果如图所示。
代码展示:
public class PassObject {
public static void main(String[] args) {
PassObject test=new PassObject();
Circle circle=new Circle();
test.printAreas(circle,5);
System.out.println("now radious is:"+circle.radius);
}
public void printAreas(Circle c, int time){
System.out.println("Radious\t\tArea");
int i = 1;
for (; i <= time; i++) {
c.radius=i;
System.out.println(c.radius+"\t\t\t"+c.findArea());
}
c.radius=i;
}
}
这里将circle作为实参传递给形参c的时候,它们指向的是堆空间的同一个对象,所以在printArea方法里面改变c的半径的时候,circle指向的对象里面的数据也会更改。
输出结果:
(2)练习2
针对下面MyArrays类的如下方法进行改进:数组排序,可以指明排序的方式(从小到大、从大到小)
package Object9;
/**
* ClassName: MyArrays
* Package: Object9
* Description:
*案例:
*
* 根据上一章数组中的常用算法操作,自定义一个操作int[]的工具类。
* 涉及到的方法有:求最大值、最小值、总和、平均数、遍历数组、复制数组、数组反转、
* 数组排序(默认从小到大排序)、查找等
*
* @Author 雨翼轻尘
* @Create 2023/9/25 0025 12:02
*/
public class MyArrays {
/**
* 获取int[]数组的最大值
* @param arr 要获取最大值的数组
* @return 数组的最大值
*/
public int getMax(int[] arr) {
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[0] > max) {
max = arr[0];
}
}
return max;
}
/**
* 获取int[]数组的最小值
* @param arr 要获取最小值的数组
* @return 数组的最小值
*/
public int gexMin(int[] arr){
int min=arr[0];
for (int i = 0; i < arr.length; i++) {
if(arr[0]<min){
min=arr[0];
}
}
return min;
}
/**
* 获取int[]数组的和
* @param arr 要求和的数组
* @return 数组的和
*/
public int getSum(int[] arr){
int sum=0;
for (int i = 0; i < arr.length; i++) {
sum+=arr[i];
}
return sum;
}
/**
* 获取int[]数组的平均数
* @param arr 要求平均值的数组
* @return 数组的平均值
*/
public int getAvg(int[] arr){
/* int sum=0,avg=0;
for (int i = 0; i < arr.length; i++) {
sum+=arr[i];
}
return sum/avg;*/
return getSum(arr)/arr.length;
}
/**
* 获取int[]数组的元素
* @param arr 要遍历的数组
*/
public void print(int[] arr){
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
System.out.println();
}
//拷贝
public int[] copy(int[] arr){
int[] newArr=new int[arr.length];
for (int i = 0; i < arr.length; i++) {
newArr[i]=arr[i];
}
return newArr;
}
//反转
public void reserve(int[] arr){
for (int i = 0,j=arr.length-1; i <j ; i++,j--) {
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
//排序
public void sort(int[] arr){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
/**
* 线性查找查找指定元素
* @param arr 待查找的数组
* @param target 要查找的元素
* @return target元素在arr数组中的索引位置,若未找到,则返回-1
*/
public int LinearSearch(int[] arr,int target){
for (int i = 0; i < arr.length; i++) {
if(target==arr[i]){
return i;
}
}
//没有找到
return -1;
}
}
排序代码如下:
//排序
public void sort(int[] arr){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}
改进之后:
/**
* 针对于数组进行排序操作
* @param arr 待排序的数组
* @param sortMethod asc:升序 desc:降序
*/
public void sort(int[] arr,String sortMethod){
if(sortMethod.equals("asc")){ //ascend:升序
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}else if(sortMethod.equals("desc")){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}else{
System.out.println("你输入的排序方式有误");
}
}
现在健壮性不够好,比如在main方法里面调用:
arrs.sort(arr,null);
编译器会报错:(空指针异常)
可以在方法里面判断是不是null,也可以这样:
将sortMethod.equals("asc")
改为"asc".equals(sortMethod)
。
将sortMethod.equals("desc")
改为"desc".equals(sortMethod)
。
修改之后的代码如下:
/**
* 针对于数组进行排序操作
* @param arr 待排序的数组
* @param sortMethod asc:升序 desc:降序
*/
public void sort(int[] arr,String sortMethod){
if("asc".equals(sortMethod)){ //ascend:升序
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}else if("desc".equals(sortMethod)){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
}else{
System.out.println("你输入的排序方式有误");
}
}
接下来再来试一下:
在main方法里面调用:
arrs.sort(arr,null);
输出结果:
if判断的时候,将字面量(“asc”、“desc”)写在前面,这样就会规避空指针的问题。
刚才写的代码,交换两个数据的值用了两次,可以将它抽取出来重新造一个方法。
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
上面抽取的代码怎么写呢?
这样写可以吗?
重新定义一个方法:
public void swap(int i,int j){
int temp=i;
i=j;
j=temp;
}
然后做如下的改动:
现在整体代码长这样:
/**
* 针对于数组进行排序操作
* @param arr 待排序的数组
* @param sortMethod asc:升序 desc:降序
*/
public void sort(int[] arr,String sortMethod){
if("asc".equals(sortMethod)){ //ascend:升序
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
/*if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}*/
if(arr[j]>arr[j+1]){
swap(arr[j],arr[j+1]);
}
}
}
}else if("desc".equals(sortMethod)){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
/*if(arr[j]<arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}*/
if(arr[j]<arr[j+1]){
swap(arr[j],arr[j+1]);
}
}
}
}else{
System.out.println("你输入的排序方式有误");
}
}
public void swap(int i,int j){
int temp=i;
i=j;
j=temp;
}
在主方法里面发现,并不成功:
这个原因和上面举例说过的数值传递一样:(回顾)
再看看刚才的代码:(精简)
m就是arr[j]
,n就是arr[j+1]
。调用swap方法时,将arr[j]
和arr[j+1]
这两个值赋值给swap方法里面新的变量i和j,i和j在swap方法里面一顿操作,结束之后swap方法栈帧弹出,而在sort方法里面的arr[j]
和arr[j+1]
并没有变化。
public void sort(int[] arr,String sortMethod){
//...
if(arr[j]<arr[j+1]){
swap(arr[j],arr[j+1]);
}
}
public void swap(int i,int j){
int temp=i;
i=j;
j=temp;
}
⚡所以该怎么写呢?
以后遇到这种情况都得这样写:
想要交换某数组中两个元素,告诉我数组是谁,要交换的是哪个位置上的元素。
代码如下:
public void swap(int[]arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
将数组传递过去,其实就是将地址值传递过去了,swap里面的数组指向和sort里面数组指向一致,任何一方修改数据,另一方的数据也会修改。
swap方法
代码前后修改对比:(重要!!)
最后整体代码:
/**
* 针对于数组进行排序操作
* @param arr 待排序的数组
* @param sortMethod asc:升序 desc:降序
*/
public void sort(int[] arr,String sortMethod){
if("asc".equals(sortMethod)){ //ascend:升序
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
/*修改
if(arr[j]>arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}*/
/*错误
if(arr[j]>arr[j+1]){
swap(arr[j],arr[j+1]);
}*/
if(arr[j]>arr[j+1]){
swap(arr,j,j+1);
}
}
}
}else if("desc".equals(sortMethod)){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
/*修改
if(arr[j]<arr[j+1]){
int temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}*/
/*错误
if(arr[j]<arr[j+1]){
swap(arr[j],arr[j+1]);
}*/
if(arr[j]<arr[j+1]){
swap(arr,j,j+1);
}
}
}
}else{
System.out.println("你输入的排序方式有误");
}
}
//不可行
/* public void swap(int i,int j){
int temp=i;
i=j;
j=temp;
}*/
public void swap(int[]arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
测试结果也是对的:
(3)练习3
需要在method方法被调用之后,仅打印出a=100,b=200,请写出method方法中的代码。
public class Test{
public static void main(String[] args){
int a=10;
int b=10;
method(a,b); //需要在method方法被调用之后,仅打印出a=100,b=200,请写出method方法中的代码
System.out.println("a="+a);
System.out.println("b="+b);
}
}
提供以下几种做法:
public class Answer {
//错误做法
public static void method0(int a,int b){
a *= 10;
b *= 20;
}
//正确做法一:
public static void method1(int a, int b) {
// 在不改变原本题目的前提下,如何写这个函数才能在main函数中输出a=100,b=200?
a = a * 10;
b = b * 20;
System.out.println(a);
System.out.println(b);
System.exit(0);//和上面错误做法一样直接改数值,改完之后直接输出,不给它机会出去继续执行Test类后边的语句了,直接退出
}
//正确做法二:
public static void method2(int a, int b) {
PrintStream ps = new PrintStream(System.out) {
@Override
public void println(String x) {
if ("a=10".equals(x)) {
x = "a=100";
} else if ("b=10".equals(x)) {
x = "b=200";
}
super.println(x); //将println方法“重写”,若发现a是10,则输出100;若b是10,则输出200
}
};
System.setOut(ps);
}
}
四、递归方法
介绍
再谈方法之4:递归方法
-
何为递归方法?方法自己调用自己的现象就称为递归。
-
递归方法分类:直接递归、间接递归
- 直接递归:方法自身调用自己。
public void methodA(){
methodA();
}
- 间接递归:可以理解为A()方法调用B()方法,B()方法调用C()方法,C()方法调用A()方法。
public static void A(){
B();
}
public static void B(){
C();
}
public static void C(){
A();
}
- 使用说明:
- 递归方法包含了一种隐式的循环。
- 递归方法会重复执行某段代码,但这种重复执行无须循环控制。
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,停不下来,类似于死循环。最终发生栈内存溢出。
比如:
public class Recursion {
public static void main(String[] args) {
Recursion test=new Recursion();
test.method1();
}
public void method1(){
System.out.println("method()...");
method1(); //自己调用自己:递归
}
}
这样会存在问题:
会导致StackOverflowError
(栈溢出):
注意:
- 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多,
所以在使用递归时要慎重。 - 在要求高性能的情况下尽量避免使用递归,递归调用既花时间又耗内存。考虑使用循环迭代
举例
(1)举例1
计算1-100内自然数的总和
方法一、用循环的方式(getSum1方法)
public class Recursion {
public static void main(String[] args) {
Recursion test=new Recursion();
System.out.println(test.getSum1(100));
}
//计算1-100内自然数的总和
public int getSum1(int num){
int sum=0;
for (int i = 0; i <=num; i++) {
sum+=i;
}
return sum;
}
}
运行结果:
方法二、用递归的方式(getSum2方法)
分析:
public int getSum2(int num){
if(num==1){
return 1;
}else{
return getSum2(num-1)+num;
}
}
若num等于2,返回getSum2(1)+2=1+2=3。
若num等于3,返回getSum2(2)+3=getSum2(1)+2+3=1+2+3=6。
整体代码:
public class Recursion {
public static void main(String[] args) {
Recursion test=new Recursion();
System.out.println(test.getSum2(100));
}
//计算1-100内自然数的总和
public int getSum2(int num){
if(num==1){
return 1;
}else{
return getSum2(num-1)+num;
}
}
}
执行结果:
再举个递归的简单例子:
(2)举例2
计算n!
和上一个例子一样。
public class Recursion {
public static void main(String[] args) {
Recursion test=new Recursion();
System.out.println(test.getMul(5));
}
public int getMul(int n){
if(n==1){
return 1;
}else{
return getMul(n-1)*n;
}
}
}
运行结果:
分析图:
(3)举例3
在I/O流中,我们会说到File类,它的对象可以表示一个文件目录。那如何获取文件目录的大小呢?
下图是某个文件夹的大小:
计算指定文件目录大小,遍历指定的文件目录中所有的文件,删除指定的文件目录
有兴趣的可以自己试试。
(4)举例4
计算斐波那契数列(Fibonacci)的第n个值,斐波那契数列满足如下规律: 1,1,2,3,5,8,13,21,34,55,… , 即从第三个数开始,一个数等于前两个数之和。假设f(n)代表斐波那契数列的第n个值,那么f(n)满足: f(n) = f(n-2) + f(n-1);
public class Recursion {
public static void main(String[] args) {
Recursion test=new Recursion();
System.out.println(test.f(10));
}
//斐波那契数列
//1 1 2 3 5 8...
//f(n)=f(n-1)+f(n-2)
public int f(int n){
if(n==1){
return 1;
} else if (n==2) {
return 1;
}else{
return f(n-1)+f(n-2);
}
}
}
输出结果:
练习
(1)练习1
👻题目:
已知一个数列:f(20) = 1,f(21) = 4,f(n+2) = 2*f(n+1)+f(n),
其中n是大于0的整数,求f(10)的值。
代码:
public class RecusionExer01 {
public static void main(String[] args) {
Recursion test=new Recursion();
System.out.println(test.f(10));
}
public int f(int n){
if(n==20){
return 1;
}else if(n==21){
return 4;
}else{
return f(n+2)-2*f(n+1);
}
}
}
运行结果:
分析:
下面这种方式是正确的,计算的数值在往已知的f(20)和f(21)靠拢。
若是将已知的条件f(n+2) = 2*f(n+1)+f(n)
改为f(n)=2*f(n-1)+f(n-2)
,则是错误的:
数值往小的地方跑,就是往不已知的方向跑,是算不出结果的。(可是我的编译器怎么能执行出来正确结果。。。)大家还是尽量别这样写。
(2)练习2
👻题目:
已知有一个数列:f(0) = 1,f(1) = 4,
f(n+2)=2*f(n+1) + f(n),其中n是大于0的整数,求f(10)的值。
代码:
public class RecusionExer02 {
public static void main(String[] args) {
RecusionExer02 test=new RecusionExer02();
System.out.println(test.func(10));
}
public int func(int n){
if(n==0){
return 1;
} else if (n==1) {
return 4;
}else{
//错误的
//return func(n+2)-2*func(n+1);
//正确的
return 2*func(n-1)+func(n-2);
}
}
}
执行结果:
(3)练习3
👻题目:不死神兔
用递归实现不死神兔:故事得从西元1202年说起,话说有一位意大利青年,名叫斐波那契(Fibonacci)。
在他的一部著作中提出了一个有趣的问题:假设一对刚出生的小兔一个月后就能长成大兔,
再过一个月就能生下一对小兔,并且此后每个月都生一对小兔,没有发生死亡,
问:现有一对刚出生的兔子2年后(24个月)会有多少对兔子?
分析:斐波那契数列 f(n) = f(n-2) + f(n-1);
月份 1 2 3 4 5
兔子对数 1 1 2 3 5
代码:
public class RabbitExer {
public static void main(String[] args) {
RabbitExer rabbit=new RabbitExer();
System.out.println("兔子的对数为:"+rabbit.getRabbitNumber(24));
}
public int getRabbitNumber(int month){
if(month==1){
return 1;
} else if (month==2) {
return 1;
}else{
return getRabbitNumber(month-1)+getRabbitNumber(month-2);
}
}
}
执行结果:
(4)练习4
拓展:走台阶问题
假如有10阶楼梯,小朋友每次只能向上走1阶或者2阶,请问一共有多少种不同的走法呢?
分析:
阶数 1 2 3 4 。。。
走法 1 2 3 5 。。。
从n为3开始:
f(n) = f(n - 1) + f(n - 2) 【斐波那契数列】
代码:
public class test {
public static void main(String[] args) {
test k=new test();
System.out.println("4阶台阶有"+k.walk(4)+"种走法");
}
public int walk(int m){
if(m==1){
return 1;
} else if (m==2) {
return 2;
}else{
return walk(m-1)+walk(m-2);
}
}
}
执行结果:
【奇妙的属性】随着数列的增加,斐波那契数列前一个数与后一个数的比值越来越逼近黄金分割的数值0.618。