【JavaSE】方法的使用

196 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

1.方法的概念及使用 1.1什么是方法 方法就是一个代码片段. 类似于 C 语言中的 "函数"。

方法存在的意义

是能够模块化的组织代码(当代码规模比较复杂的时候). 做到代码被重复使用, 一份代码可以在多个位置使用. 让代码更好理解更简单. 直接调用现有方法开发, 不必重复造轮子. 1.2方法的定义 // 方法定义 修饰符 返回值类型 方法名称([参数类型 形参 ...]){ 方法体代码; [return 返回值]; }

示例:判断一个年份是否为闰年

public static boolean isLeapYear(int year) {
    if (year % 100 != 0 && year % 4 == 0 || year % 400 == 0) {
        return true;
    }
    else{
        return false;
    }
}

【注意事项】

修饰符:现阶段直接使用public static 固定搭配 返回值类型:如果方法有返回值,返回值类型必须要与返回的实体类型一致,如果没有返回值,必须写成void 方法名字:采用小驼峰命名 参数列表:如果方法没有参数,()中什么都不写,如果有参数,需指定参数类型,多个参数之间使用逗号隔开 方法体:方法内部要执行的语句 在java当中,方法必须写在类当中 在java当中,方法不能嵌套定义 在java当中,没有方法声明一说

1.3方法调用的执行过程 【方法调用过程】 调用方法--->传递参数--->找到方法地址--->执行被调方法的方法体--->被调方法结束返回--->回到主调方法继续往下执行

示例:计算两个数的相加

public static int add (int a,int b) {
    return a+b;
}
public static void main(String[] args) {
    int x = 10;
    int y = 10;
    int ret = add(x,y);
    System.out.println(ret);
}

代码示例:计算 1!+2!+3!+4!+5!

/**
 * 求某个数字的阶乘
 * @param n  5! = 5*4*3*2*1
 * @return
 */
public static int fac(int n){
    int ret = 1;
   for(int i = 1; i <= n ; i++){
       ret *= i;
   }
   return ret;
}
public static int facSum(int n){
    int sum=0;
    for(int i=1;i<=n;i++){
        sum+=fac(i);
    }
    return sum;
}
public static void main1(String[] args) {
    //计算 1! + 2! + 3! + 4! + 5!
    System.out.println(facSum(5));
}

}

1.4形参和实参的关系 Java中形参的名字可以随意取,对方法都没有任何影响,形参只是方法在定义时需要借助的一个变量,用来保存方法在调用时传递过来的值。 在Java中,实参的值永远都是拷贝到形参中,形参和实参本质是两个实体 代码示例:交换两个整型变量

public static void Swap(int a , int b){
    int tmp = a;
    a = b;
    b = tmp;
}
public static void main(String[] args) {
    int a = 10;
    int b = 20;
    System.out.println("交换前:"+a+" "+b);
    Swap(a,b);
    System.out.println("交换后:"+a+" "+b);
}

可以看到,在调用Swap函数后,两个数并没有成功交换

【原因分析】

实参a和b是main方法中的两个变量,其空间在main方法的栈(一块特殊的内存空间)中, 而形参a和b是swap方法中的两个变量,a和b的空间在swap方法运行时的栈中, 因此:实参a和b 与 形参a和b是两个没有任何关联性的变量, 在swap方法调用时,只是将实参a和b中的值拷贝了一份传递给了形参a和b, 因此对形参a和b操作不会对实参a和b产生任何影响

而在Java中,我们是拿不到栈上的地址的,所以我们不能实现传址调用,我们是拿不到a和b的地址的,我们只能实现按值调用

如果要去做,只能把a和b放到堆上,而放到堆上的都是对象,这个以后会讲到

1.5没有返回值的方法 方法的返回值是可选的. 有些时候可以没有的,没有时返回值类型必须写成 void

代码示例:

public static void main(String[] args) {
    int a = 10;
    int b = 20;
    print(a, b);
} 
public static void print(int x, int y) {
    System.out.println("x = " + x + " y = " + y);
}

2.方法重载 2.1为什么需要方法重载 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); } public static int add(int x, int y) { return x + y; }

        // 编译出错
//Test.java:13: 错误: 不兼容的类型: 从double转换到int可能会有损失
//double ret2 = add(a2, b2);
        //          ^

由于参数类型不匹配, 所以不能直接使用现有的 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;
    }

上述代码确实可以解决问题,但不友好的地方是:需要提供许多不同的方法名,而取名字本来就是让人头疼的事情。那能否将所有的名字都给成 add 呢?

2.2方法重载概念 在自然语言中,一个词语如果有多重含义,那么就说该词语被重载了,具体代表什么含义需要结合具体的场景。

在Java中方法也是可以重载(overload)的

在Java中,如果多个方法的名字相同,参数列表不同,则称该几种方法被重载了

public static void main(String[] args) {
    add(1, 2); // 调用add(int, int)
    add(1.5, 2.5); // 调用add(double, double)
    add(1.5, 2.5, 3.5); // 调用add(double, double, double)
}
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;
}

注意:

方法名必须相同 参数列表必须不同(参数的个数不同、参数的类型不同、类型的次序必须不同) 与返回值类型是否相同无关,对方法的返回值不做要求 编译器在编译代码时,会对实参类型进行推演,根据推演的结果来确定调用哪个方法

// 注意:两个方法如果仅仅只是因为返回值类型不同,是不能构成重载的
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int ret = add1(a, b);
        System.out.println("ret = " + ret);
    }
    public static int add1(int x, int y) {
        return x + y;
    }
    public static double add1(int x, int y) {
        return x + y;
    }
    // 编译出错
//Test.java:13: 错误: 已在类 Test中定义了方法 add(int,int)
//public static double add(int x, int y)
//                      ^
//      1 个错误

2.3方法签名 在同一个作用域中不能定义两个相同名称的标识符。

比如:方法中不能定义两个名字一样的变量,那为什么类中就可以定义方法名相同的方法呢?

方法签名即:经过编译器编译修改过之后方法最终的名字。 具体方式:方法全路径名+参数列表+返回值类型,构成方法完整的名字。

public static int add2(int x, int y){
    return x + y;
}
public static double add2(double x, double y){
    return x + y;
} 
public static void main(String[] args) {
    add2(1,2);
    add2(1.5, 2.5);
}

上述代码经过编译之后,然后使用JDK自带的javap反汇编工具查看,具体操作:

先对工程进行编译生成.class字节码文件 在控制台中进入到要查看的.class所在的目录 输入:javap -v 字节码文件名字即可

方法签名中的一些特殊符号说明:

特殊字符 数据类型 V void Z boolean B byte C char S short I int J long F float D double [ 数组(以[开头,配合其他的特殊字符,表述对应数据类型的数组,几个[表述几维数组) L 引用类型,以L开头,以;结尾,中间是引用类型的全类名 3.一些代码的练习来熟悉方法的使用 1.求100到999的水仙花数并打印

public static boolean judge_Narcissistic_number ( int i){
    int tmp = i;
    int count = 1;//记录位数
    tmp /= 10;
    while (tmp != 0) {
        count++;
        tmp /= 10;
    }
    //计算
    tmp = i;
    int sum = 0;
    while (tmp != 0) {
        sum += Math.pow(tmp % 10, count);
        tmp /= 10;
    }
    if (i == sum) {
        return true;
    } else {
        return false;
    }
}
public static void main1(String[] args) {
    //输出100到999的水仙花数
    int i = 0;
    for (i = 100; i <= 999; i++) {
        if (judge_Narcissistic_number(i) == true) {
            System.out.println(i);
        }
    }
}

}

2.统计二进制中1的个数

public class TestDemo { public static void main(String[] args) { //统计二进制中1的个数 Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); System.out.println(Count_number1(n)); System.out.println(Count_number2(n));

}

//方法1 public static int Count_number1(int n){ int count = 0; while(n!=0){ if((n&1)==1){ count++; } n = n >>> 1; } return count; } //方法2 public static int Count_number2(int n){ int count = 0; while(n!=0){ count++; n = n & (n-1); } return count; }

3.获取一个数二进制序列中所有的偶数位和奇数位

public class TestDemo { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); fun(n); }

public static void fun(int n){
    int i = 0;
    //打印偶数位
    for(i=30;i>=0;i-=2){
        System.out.print(((n >> i) & 1) +" ");
    }
    System.out.println();
    //打印奇数位
    for(i=31;i>=1;i-=2){
        System.out.print(((n >> i) & 1) +" ");

    }
}

4.获取一个整数的每一位

public class TestDemo { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); print(n); } public static void print(int n){ //逆序打印 123-->3 2 1 while(n!=0){ System.out.print((n%10)+" "); n /= 10; } } 5.打印 x 型图案

public class TestDemo { public static void func(int n){ for(int i =0 ; i<n ;i++){ for(int j = 0; j<n;j++){ if(i==j){ System.out.print(""); } else if(i+j==n-1){ System.out.print(""); } else{ System.out.print(" "); } } System.out.println(); } } public static void main(String[] args) { //打印 x型 图案 Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); func(n); }

分析:

6.找出数组中只出现了一次的数

在做题之前,我们需要先回顾一个知识点就是

0 ^ n = n n ^ n = 0 这道题目我们就利用了这个结论,我们的做法是遍历数组并全部异或

public static void main(String[] args) {
    //找出数组中只出现了一次的数
    int [] array = {1,2,3,2,1};
    int ret = 0 ;
    for(int i = 0 ; i< array.length;i++){
        ret = ret ^ array[i];
    }
    System.out.println(ret);
}

} 这里我们成功得出了结果,但是有一个缺陷

这里的ret初值为0,我们实际上是 ret^1^2^3^2^1,也就是 0^1^2^3^2^1

若ret的初始值改变了,那么就不能达到预期的效果

所以我们改进一下

public static void main(String[] args) {
    //找出数组中只出现了一次的数
    int [] array = {1,2,3,2,1};
    int ret = array[0] ;
    for(int i = 1 ; i< array.length;i++){
        ret = ret ^ array[i];
    }
    System.out.println(ret);
}

这样代码就更加完美了

7.调整数组中数的顺序,使奇数位于偶数之前

分析:

代码:

public static void main(String[] args) {
    //调整数组中数的顺序,使奇数位于偶数之前
    int [] array ={1,2,3,4,5};
    int left = 0;
    int right = array.length - 1 ;
    while(left<right){
        while( (left<right) && (array[left]%2 != 0) ){
            left++;
        }
        while( (left<right) && (array[right]%2 == 0) ){
            right--;
        }
        int tmp = array[left];
        array[left]=array[right];
        array[right]=tmp;
    }
    for(int i =0 ;i< array.length;i++){
        System.out.print(array[i]+" ");
    }
}

4.递归 一个方法在执行过程中调用自身, 就称为 "递归". 递归相当于数学上的 "数学归纳法", 有一个起始条件, 然后有一个递推公式

递归的必要条件:

将原问题划分成其子问题,注意:子问题必须要与原问题的解法相同 递归出口

代码示例: 递归求 N 的阶乘

public static int fac(int n){
    //求n的阶乘
    if(n<=1){
        return 1;
    } else{
        return n*fac(n-1);
    }
}

关于递归的更详细的介绍和递归的执行过程建议参考我之前在学习C语言时写的递归文章:

函数的递归——Living_Amethyst的文章

关于 "调用栈"

方法调用的时候, 会有一个 "栈" 这样的内存空间描述当前的调用关系. 称为调用栈. 每一次的方法调用就称为一个 "栈帧", 每个栈帧中包含了这次调用的参数是哪些, 返回到哪里继续执行等信息. 后面我们借助 IDEA 很容易看到调用栈的内容 4.1递归练习 代码示例1 按顺序打印一个数字的每一位(例如 1234 打印出 1 2 3 4)

public static void Print(int n){
  //按顺序打印整数的每一位 如:1234--> 1 2 3 4
    if(n>9){
      Print(n/10);
  }
      System.out.print(n % 10+" ");
}

代码示例2 递归求 1 + 2 + 3 + ... + 10

public static int Sum(int n){
    //求 1 + 2 + 3 + 4 + 5 +...+n
    if(n==0){
        return 0;
    }else{
        return n+Sum(n-1);
    }
}

代码示例3 写一个递归方法,输入一个非负整数,返回组成它的数字之和.

例如,输入 1729, 则应该返回1+7+2+9,它的和是19

public static int SumNumber(int n) {
    //写一个递归方法,输入一个非负整数,返回组成它的数字之和.
    //例如,输入 1729, 则应该返回1+7+2+9,它的和是19
    if(n<10){
        return n;
    }
    return n%10 + SumNumber(n/10);

}

代码示例4 求斐波那契数列的第 N 项

public static int fib(int n){
    //求斐波那契数列的din项的值
    if(n==1||n==2){
        return 1;
    }
    return fib(n-1)+fib(n-2);
}

当我们求 fib(40) 的时候发现, 程序执行速度极慢. 原因是进行了大量的重复运算

可以使用循环的方式来求斐波那契数列问题, 避免出现冗余运算.

public static int fib2(int n){
    //迭代求斐波那契数列的效率比递归高很多
    if(n==1 || n==2){
        return 1;
    }
    int f1=1;
    int f2=1;
    int f3=0;
    for(int i=3;i<=n;i++){
        f3=f1+f2;
        f1=f2;
        f2=f3;
    }
    return f3;
}

此时程序的执行效率大大提高了

代码示例5.青蛙跳台阶问题 一只青蛙一次可以挑上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法

我们先画张图来帮助我们思考

我们不难发现,青蛙跳台阶就是一个变相的斐波那契数列

只是这个数列的第一项是1,第二项是2

然后我们用代码实现

public static int FrogJump(int n){
    //青蛙跳台阶问题(递归)
    if(n==1||n==2){
        return n;
    }else{
        return FrogJump(n-1)+FrogJump(n-2);
    }
}

public static int FrogJump2(int n){
    //青蛙跳台阶问题(非递归)
    if(n==1||n==2){
        return n;
    }
    int f1=1;
    int f2=2;
    int f3=0;
    for(int i=3;i<=n;i++){
        f3=f1+f2;
        f1=f2;
        f2=f3;
    }
    return f3;
}

代码示例6 汉诺塔 汉诺塔(Tower of Hanoi)源于印度传说中,大梵天创造世界时造了三根金钢石柱子,其中一根柱子自底向上叠着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘

这是示意图,a是起始柱,c是目标柱,b起到中转作用

而图中正好是有三个圆盘的情况

这是示意图,a是起始柱,c是目标柱,b起到中转作用

我们看几种情况得出了一个结论

我们分两种情况来讨论:

一. 当 n == 1时,直接将盘子从 A 移动到C 二. 当 n > 1时,可以拆分成3大步骤

由此可以看出步骤一、三是个递归应用

下面我们用代码实现它

public class TestDemo { public static void move(char pos1,char pos2){ System.out.print(pos1+"->"+pos2+" "); } /** * * @param n 代表盘子的个数 * @param pos1 盘子的起始位置 * @param pos2 盘子的中转位置 * @param pos3 盘子的结束位置 */ public static void hanio(int n,char pos1,char pos2,char pos3){ if(n==1){ move(pos1,pos3); }else{ //将pos3看作中间柱子,把n-1个盘子从pos1柱子移到pos2柱子上 hanio(n-1,pos1,pos3,pos2); move(pos1,pos3); //将pos1看作中间柱子,把n-1个盘子从pos2柱子移到pos3柱子上 hanio(n-1,pos2,pos1,pos3); } }

public static void main(String[] args) {
    hanio(1,'A','B','C');
    System.out.println();
    hanio(2,'A','B','C');
    System.out.println();
    hanio(3,'A','B','C');
    System.out.println();
}

}