十二、方法(思维导入)

1 练习:制作一个基础通讯录程序

在讲解 “方法” 这个概念之前,我们先对我们之前掌握的知识,进行一个小小的检验,制作一个功能很基础的通讯录程序。

要求:

  1. 通讯录中可以存储多个人的姓名和他的电话号码;

  2. 通讯录中的内容由用户自行添加;

  3. 用户可以通过输入联系人姓名来查询此联系人的电话号码。

  4. 用户可以随时向通讯录中添加一条新的记录,即并不是用户先将所用联系人信息录入,再进行查询,而是录入与查询可随意交替进行。

分析:

  1. 我们需要存储两种数据——姓名和电话号码,因此我们可以使用两个数组来存储这些数据。只要让同一个人的姓名和电话号码,在两个数组中的索引相同,就可以实现姓名与电话号码的一一对应。

  2. 我们需要使用 String 类型的变量,记录来自用户的输入,并且需要一个 int 类型的变量,记录下一次应该接收输入的数组索引号。

  3. 遍历姓名数组,逐一判断记录值是否与输入值一致,如果一致,则获取电话号码数组中,对应索引值的电话号码。

  4. 程序总体上应该是一个大的循环结构,这被称为 “主循环” 。在主循环中,我们应根据用户输入,来确定应该被使用的功能。

答案:

import java.util.Arrays;
import java.util.Objects;
import java.util.Scanner;

public class Hello {
    public static void main(String[] args) {
        // 两个数组,用于记录姓名与电话号码
        String[] names = new String[10];
        String[] phoneNumbers = new String[10];
        // 最小的暂未存入数据的数组索引值,用于确定下一个联系人信息存入位置
        int indexOfNullPos = 0;

        // 用于接收用户输入
        Scanner input = new Scanner(System.in);
        // 用于接收用户选择的功能序号
        int indexOfFunction = -1;
        // 程序主循环
        while (true) {
            // 显示主界面
            System.out.println("-----简易通讯录-----");
            System.out.println("1:添加联系人");
            System.out.println("2:查询联系人电话");
            System.out.println("0:退出程序");
            System.out.println("------------------");

            // 接收用户的选择
            if (input.hasNextInt()) {
                indexOfFunction = input.nextInt();
                input.nextLine();
                if (indexOfFunction < 0) {
                    System.out.println("请输入正确的功能序号!!!");
                    continue;
                }
            } else {
                input.nextLine();
                System.out.println("请输入正确的功能序号!!!");
                continue;
            }

            if (indexOfFunction == 1) {
                // 添加联系人功能
                if (indexOfNullPos == names.length){
                    System.out.println("通讯录已满,无法存入新数据!");
                    continue;
                }
                System.out.println("请输入新联系人姓名:");
                names[indexOfNullPos] = input.nextLine();
                System.out.println("请输入联系人电话号码:");
                phoneNumbers[indexOfNullPos] = input.nextLine();
                indexOfNullPos++;
            } else if (indexOfFunction == 2) {
                // 查询联系人功能
                System.out.println("请输入要查找的联系人姓名:");
                String tempName = input.nextLine();
                int tempIndex = 0;
                for (; tempIndex < indexOfNullPos; tempIndex++){
                    if (names[tempIndex].equals(tempName)){
                        System.out.println("这位联系人的电话号码是:" + phoneNumbers[tempIndex]);
                        break;
                    }
                }
                if (tempIndex == names.length){
                    System.out.println("未保存过此联系人信息!!!");
                    continue;
                }
            } else if (indexOfFunction == 0) {
                // 退出程序
                break;
            } else {
                // 此序号没有对应功能,请重新输入
                System.out.println("请输入正确的功能序号!!!");
                continue;
            }
        }
    }
}

2 发现问题

经过千辛万苦,我们终于做完了这个程序。那么,我们在程序开发过程中,有没有发现,在软件开发流程方面,有什么可能的优化方向呢?

让我们回看整个代码,我们最为直观的感受是好长。是的,这是我们迄今为止,写过的最长的程序。然而,这在真正的程序面前,是不值一提的,我们所使用的各种程序,代码量往往会达到几千几万行,这个不到 100 行的小程序,甚至不能称之为 “小” ,可能 “袖珍程序” 会更合适些。

然而,即使在如此袖珍的程序开发过程中,我们仍能感受到,如果我们想要对这个程序增加功能或者对原有代码进行修改,我们需要掌握整个程序流程。随着代码量的增加,程序流程越来越复杂,我们记住整个程序流程的难度越来越大。一个人完成整个程序的开发或许还容易些,如果需要团队协作,那么“开发前熟悉代码”这种基础步骤都会变得难以完成。

除此之外,有没有同学能够发现,这个程序逻辑上存在的问题?

如果用户忘记了曾经保存过的一条联系人信息,因而再次试图保存同一个人的电话号码时,程序并不会阻止用户这样操作。然而,新存入的这条联系人信息,永远不可能被程序查询到。

我们要如何解决这个问题呢?我们需要在用户输入联系人姓名后,遍历整个 names 数组,逐一比较这个名字是否已经存在。不存在,则允许保存;存在,则阻止用户。

当我们想到遍历 names 数组时,大家有没有感到这个操作相当熟悉,我们在实现 “查询联系人” 功能时,就写过这段程序。而以我们现在的知识,我们最方便的办法,就是复制这段代码,进行粘贴。

然而,一方面,在大段代码中找到要复制的部分也不容易,找到了也不一定可以一点不改地直接用;另一方面,假如后期这段代码需要进行修改,我们很难做到不遗漏一处地对遍布这个程序的这一段代码的修改,而遗漏的代码,很容易成为程序 Bug 。

3 解决问题——“方法” 应运而生

要解决这个问题,我们需要使用 “模块化” 的思想,就好像计算机的外设——U盘、麦克风、耳机等,这些设备都是一个个 “模块” 。

以一个U盘为例,U盘的生产者,只需要关注U盘怎样获得电流和任何处理来自计算机USB接口的数据,然后,将计算机需要的数据传输给计算机即可。U盘的生产者,不需要知道整个电脑的工作流程。

类比到一段代码模块,一名程序员,不需要知道整个程序的工作流程,他只需要关注自己这段代码模块的功能。比如,这名程序员,制作一段用于遍历整个数组的代码:“输入信号” 为 names 数组和用户输入的联系人姓名;“输出信号” 为这个姓名对应的索引值,如果这个姓名不存在,则输出 -1 。

对于一个U盘来说,它并不是与某一个USB接口绑定的,我们可以把它插在其他USB接口上,甚至可以插在其它设备上,我们不需要为每个USB接口买一个U盘,而用户在这个中存入新的文件,当我们把U盘插入其他USB接口或者设备时,新的文件就已经存在了,并不需要我们在每个设备上,重复一遍存入新文件的操作。

类比到代码模块,也就是一次编写,多处使用。

这种代码模块,就是 “方法”(也可以称为 “函数”) 。