4.1 方法

  方法是Java中一个命名的代码块,如同在数学中用到的函数,在其他语言中常直接称为函数。

  方法通常是为完成一定的功能,把程序中特定的代码块组合在一起而构成的,其主要的好处体现在两个方面,一个是可以重用,另一个是使程序结构更加清晰。

4.1.1 Java方法概述

  第3章通过双重for循环完成过如图4.1所示的输出。现在假设需求做了调整,需要输出3个类似这样的图形(规则一样),第一个图形是5行*,第二个图形是8行*,第三个图形是12行*,如图4.2所示,具体的代码如下。

class TestMethod1 

{

    public static void main(String[] args) 

    {

        //输出第一个图形,5行*

        for(int i = 1;i <= 5;i++){

            for(int j = 1;j <= i;j++){

                System.out.print("*");

            }

            System.out.println();

        }

        //输出第二个图形,8行*

        for(int i = 1;i <= 8;i++){

            for(int j = 1;j <= i;j++){

                System.out.print("*");

            }

            System.out.println();

        }

        //输出第三个图形,12行*

        for(int i = 1;i <= 12;i++){

            for(int j = 1;j <= i;j++){

                System.out.print("*");

            }

            System.out.println();

        }

    }

}
copy
图4.1 输出图形1
图4.2 输出图形2

  虽然使用上面的代码可以实现图4.2所示图形的输出,但给程序员的感觉是编写了大量重复的代码。并且如果这个输出图形的规则发生了变化,则需要分别在输出图形的代码块中进行更改,很麻烦且容易忘记。

  接下来用Java的方法解决这个问题,Java方法声明的语法形式如下。

[修饰符]  返回值类型  方法名([形参列表]){

    方法体

}
copy

  其中,大括号前面的内容称为方法头,大括号里面的称为方法体。下面具体介绍Java方法声明中的各元素。

  • 修饰符:用来规定方法的一些特征,例如它的可见范围以及如何被调用。例如,我们一直在使用的main方法,其中的public static就是修饰符,public表示这个方法的可见范围,而static表示main方法是一个静态方法,这些内容后面的课程会详细介绍。

  • 返回值类型:表示该方法返回什么样类型的值。方法可以没有返回值,这时需要用void表示返回值类型。不过一旦一个方法需要返回值时,那么方法体里就必须使用return语句返回此类型的值,举例如下。

public void drawCircular(){...}       //该方法没有返回值

public int returnInt(){                     //该方法返回值为int类型

    int x = 10;

    ...

    return x;

}
copy

  这里需要强调,return也是一种跳转语句,和前面学过的break语句和continue语句一样,不同点在于方法执行到return语句后,会返回给主调方法。

  此外,一个方法只能有一个返回值,因此也只能有一个返回值类型。如果逻辑上确实需要返回多个值,则可以将需要返回的“多个值”先转为一个数组或一个对象,然后再返回转变后的这“一个值”。数组和对象会在后续进行学习,届时可以再深入思考。

  • 方法名:必须符合标识符的命名规则,并且能够望文知义,前面在介绍标识符时详细介绍过。

  • 形参列表:参数用来接收外界传来的信息,可以是一个或多个,也可以没有参数,但无论是否有参数,必须有小括号。方法中的这些参数称为形式参数,简称形参,形参必须说明数据类型,举例如下。

public int returnAdd(int x,int y){

    return x + y;

}
copy

  这个方法中有两个形参,都是int型的,返回值也是int型。

  需要注意的是,如果声明了多个方法,那么多个方法之间不能相互嵌套,错误示例如下。

public void methodA(){

    …

    public void methodB(){

    …

    }

}
copy

此外,之前学习过的选择、循环等逻辑代码,都必须写在方法的内部,如下。

//正确:逻辑代码写在方法内部

public void methodC(){

    if(…){

       …

    }

    for(…){

       …

    }

}

//错误:逻辑代码写在方法外部

public void methodD(){

    …

}

if(…){

   …

}
copy

  接下来,把前面输出图形的功能用一个方法来实现,这个方法没有返回值,方法名为drawStar,有1个参数:输出*的行数x,具体代码如下。

public static void drawStar(int x){

    for(int i = 1;i <= x;i++){

        for(int j = 1;j <= i;j++){

               System.out.print("*");

        }

        System.out.println();

    }

}
copy

  上面的代码只声明了方法,接下来介绍如何使用方法,即方法的调用。这里只介绍类内部方法的调用,关于调用其他类的方法,在后面面向对象的课程中会学习到。在类内部调用方法很简单,只需给出方法名以及方法的实际参数列表(实参列表的数据类型必须与形参列表一致或可以自动转换成形参列表的格式)即可。如果方法有返回值,则可以赋值给相应类型的变量。例如:

int x = returnAdd(3 + 5);

drawStar(8);
copy

  综合以上学习的内容,采用方法调用的方式实现输出图4.2所示图形的代码如下。

class TestMethod2 

{

    public static void main(String[] args) 

    {

        drawStar(5);        //调用drawStar方法,实参为5,表示行数

        drawStar(8);        //调用drawStar方法,实参为8,表示行数

        drawStar(12);      //调用drawStar方法,实参为12,表示行数

        }

        //输出一个图形,共x行,每行输出的*的个数与行数相等

    public static void drawStar(int x)

    {

        for(int i = 1;i <= x;i++){

            for(int j = 1;j <= i;j++){

                System.out.print("*");

            }

            System.out.println();

        }

    }

}
copy

  通过比较实现相同功能的两组不同的代码可以看出,使用方法调用的形式,代码结构清晰,方法声明可以被复用。

  以上提到的方法都是用户自定义方法,JDK本身也提供了很多的方法,我们一直都在使用。例如,System.out.println()为用户向控制台输出方法,nextInt()方法(Scanner类)为从控制台获取用户输入的整数方法,Math.sqrt(i)为求i的平方根方法等。具体JDK方法的使用,请读者查阅JDK API文档。

4.1.2 Java方法的使用

  第3章在介绍while循环时,完成了如图4.3所示功能的程序,其中所有的代码都写在main方法里。接下来使用方法调用的方式组织程序结构,完成相同的功能。

  程序包含如下的方法。

  public static int showMenu(){…}:该方法显示程序主界面,返回用户输入的功能菜单数。

  public static void inputData(){…}:该方法执行模块1,完成输入数据的功能。

  public static void outputData(){…}:该方法执行模块2,完成输出数据的功能。

图4.3 使用方法调用组织程序结构示例功能

  public static void main(String[] args):程序入口方法,使用while循环输出主界面,调用showMenu()方法获得用户输入,根据用户输入值使用switch语句,分别调用inputData()和outputData()方法。另外,原来的userSel是main方法中的局部变量,现在需要改为成员变量,由多个方法共享。具体代码如下所示。

import java.util.Scanner;

class TestMethod3 

{       

    //原来的userSel是main方法中的局部变量,现在需要改为成员变量,由多个方法共享

    static int userSel = -1;

    public static void main(String[] args) 

    {

        while(true){

            userSel = showMenu();                  //调用showMenu()方法获得用户输入

            switch(userSel){

                case 1:

                    inputData();        //调用inputData()方法

                    break;

                case 2:

                    outputData();      //调用outputData()方法

                    break;

                case 3:

                    System.out.println("结束程序!");

                    break;

                default:

                    System.out.println("输入数据不正确!");

                    break;

            }

            if (userSel == 3)                    //当用户输入3时,退出while循环,结束程序

            {

                break;

            }

        }

    }



    //该方法显示程序主界面,返回用户输入的功能菜单数

    public static int showMenu() 

    {

        System.out.println("1. 输入数据");

        System.out.println("2. 输出数据");

        System.out.println("3. 退出程序");

        System.out.print("请选择你的输入(只能输入1、2、3):");

        Scanner input = new Scanner(System.in);//从控制台获取用户输入的选择

        userSel = input.nextInt();

        return userSel;

    }

    //该方法执行模块1,完成输入数据的功能

    public static void inputData() 

    {

        System.out.println("执行1.输入数据模块");

        System.out.println("******************");

        System.out.println("******************");

    }

    //该方法执行模块2,完成输出数据的功能

    public static void outputData() 

    {

        System.out.println("执行2.输出数据模块");

        System.out.println("******************");

        System.out.println("******************");

    }

}
copy

4.1.3 方法递归调用

  递归调用是指一个方法在它的方法体内调用它自身。Java语言允许方法的递归调用,在递归调用中,主调方法同时也是被调方法。执行递归方法将反复调用其自身,每调用一次就再进入一次本方法。

  递归调用最大的问题是,如果递归调用没有退出的条件,则递归方法将无休止地调用其自身,这显然是不正确的。为了防止递归调用无休止地进行,必须在方法内有终止递归调用的手段。通常的做法就是增加条件判断,满足某条件后就不再进行递归调用,然后逐层返回。

  接下来使用递归调用计算整数n的阶乘,具体代码如下。

public class TestMethod4

{

    public static void main(String[] args) {

        System.out.println(factorial(5));

    }

//求n的阶乘的方法

    static long factorial(int n) {

        if(n == 1) {                                     //判断条件,一旦满足就不再递归,逐层返回

            return 1;

        }

        long sum = factorial(n - 1);   //递归调用

        return  sum * n;                   //逐层返回求阶乘

    }

}
copy

  使用递归调用虽然使程序编写会简单一些,但是不易于理解,在实际编程过程中建议不要使用递归调用。