PhoneBook in Java – follow-up

Posted on

Problem

This is a follow-up to this phonebook code.

This Phone Book allows the user to Enter a value between 1-3.

Entering 1 lets the user Add a number to the phone book, when the process is finished, the user is asked again to pick 1 of the 3 tasks.

Entering 2 lets the user speed dial a number. If the number exists, it will be dialled, else an error statement will be printed. Again the user will pick from 1 of 3 options.

Entering 3 exits the program.

Question: How could I make this code easier to follow/understand. Can I make it more objected oriented and how? I’ve also been told using too many static methods is bad practice. I’ve passed the scanner and arraylist to the class to prevent this, and just want to make sure it was done correctly as I haven’t done this before.

PhoneBookV2.java

package com.java.phonebook;

import java.util.ArrayList; //Import ArrayList
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner; //Import Scanner

public class PhoneBookV2 {
    public static void main(String[] args){
        final List<String> numbers = new ArrayList<>(); //Initialize new ArrayList

        Scanner scan = null;

        try {
            scan = new Scanner(System.in);
            PhoneBook phoneBook = new PhoneBook(scan, numbers);
            phoneBook.task();

        }catch(InputMismatchException e){
            System.out.println("Scanner Not Found");
        }finally{
            if(scan != null){
                scan.close();
            }
        }
    }

}

PhoneBook.java

package com.java.phonebook;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class PhoneBook {
    Scanner scan;
    List<String> numbers;


    public PhoneBook(Scanner scan, List<String> numbers) {
        this.scan = scan;
        this.numbers = numbers;
    }


    public void task() {
        boolean cont = true;
        do {
            //Ask user for input on which task they want to do
            System.out.println("Please Pick A Task: n 1:Add A Number to Speed Dial n 2:Speed Dial A Number n 3:Exit");
            String choice = scan.nextLine(); //read input
            //check user input and continue accordingly
            switch (choice) {
                case "1":
                    AddNumber(); //run AddNumber Method
                    cont = false;
                    break;
                case "2":
                    CallNumber(); //Run CallNumber method
                    cont = false;
                    break;
                case "3":
                    System.out.println("Goodbye!");
                    System.exit(0); //Terminate Program
                    cont = false;
                    break;
                default:
                    System.err.println("Invalid");
            }
        }while(cont);
    }

    public void CallNumber() {
        boolean cont = true;
        //create Keypad
        do try {
            String[][] keys = {
                    {" ", "1", " ", "2", " ", "3"},
                    {" ", "4", " ", "5", " ", "6"},
                    {" ", "7", " ", "8", " ", "9"},
                    {" ", " ", " ", "0", " ", " "}
            };
            //print keypad
            printPhoneBook(keys);
            //ask user for key number
            System.out.println("Pick A Speed Dial Number");
            String phoneNum = null; // should not be initialized as "", otherwise dialing message would be displayed even if choice is invalid.

            if (scan.hasNextInt()) {
                int choice = Integer.parseInt(scan.nextLine()); //convert string to int
                if (choice >= 0 && choice <= 9) {
                    phoneNum = numbers.get((choice + 9) % 10);
                } else {
                    System.err.println("Does Not Exist");
                    continue;
                }
            }

            //if number exists, Dial number
            if (phoneNum != null) {
                System.out.println("Dialing " + phoneNum + "....");
                cont = false;
            }
            //or say no number exists
            else {
                System.out.println("There is No Number At This Location");
                break;
            }
            //ask user tasks again
            task();
        } catch (IndexOutOfBoundsException e) { //catch possible errors
            System.err.println("There is No Number At This Location");
            //ask for tasks again
            task();
        }while(cont);
    }


    public void AddNumber() {

        boolean cont = true; //initialize boolean for loop
        do try{
            System.out.print("Please Enter The Number You Wish To Save Under Speed Dial: ");
            if(scan.hasNextLong()) {
                long input = Long.parseLong((scan.nextLine().trim().strip()));
                if ((input >= 100) && (input <= 999_999_999)) {
                    // Add the next number to the ArrayList
                    numbers.add(String.valueOf(input));
                }else if((input < 1000) || (input > 999_999_999)){
                    System.out.println("Invalid Entry");
                    continue;
                }
            }else if (scan.hasNextLine()) {
                scan.nextLine();
                System.out.println("Invalid Entry");
                continue;
            }

            System.out.print("Would you like to add another? Yes or No: ");
            String answer = scan.nextLine().toLowerCase(); //take input and convert to lowercase
            if (answer.equals("yes")){
                continue; //if they want to continue, allow it
            }else if (answer.equals("no")) { //if not, print arraylist and exit loop
                print((ArrayList<String>) numbers);
                cont = false;
            }else if(answer.equals("")){
                System.out.println("Invalid Entry");
            }else{
                System.out.println("Invalid");
            }

        }catch(NumberFormatException e){

        }while(cont);

        //ask user tasks again
        task();

    }

    public static void printPhoneBook(String[][] keys){
        // Loop to print array
        for(String[] row : keys){
            for(String s : row){
                System.out.print(s);
            }
            System.out.println();
        }
    }

    public void print(){
        //loop to print array numbers
        for(int i = 0; i< numbers.size(); i++){
            System.out.println((i+1) + ") " + numbers.get(i));
        }

    }

}

Solution

Conventions

Following language conventions can make your code more approachable to other people. Java methods follow camelCase, so CallNumber should be callNumber etc. More naming conventions here.

keys

I think you’ve done this so that it’s easier to write the display code:

String[][] keys = {
                  {" ", "1", " ", "2", " ", "3"},

However, it means that keys doesn’t just contain keys. It also contains white space that is to be printed between keys. This can be a little confusion and may cause issues if you wanted change the way print the phone in the future.

Since keys is only used by the private method printPhoneBook, it might make sense for it to be defined as a local variable in that method, rather than in the CallNumber method.

Public interface

The public methods on your class setup the expectation about how the class is to be used. All of the methods in your PhoneBook class are public, however it looks like task is the only one that you’d really expect a client to call. Some methods like CallNumber seem like they could be public methods, however they then call task which presents the user with a ‘what do you want to do next decision’. This could be a little confusing.

It does what is says on the tin…

Naming is an important skill, which is surprisingly difficult get right. A good start is trying to make sure that names reflect what a variable/method/class represents/does/is responsible for. printPhoneBook doesn’t look to me like it prints a phonebook. It looks like it prints they key pad (assuming the client passes in the correct structure).

Exit!

System.out.println("Goodbye!");
System.exit(0); //Terminate Program
cont = false;
break;

Nothing after the exit is run, because the process terminates. Having code after the exit is confusing. Generally you don’t actually want to call System.exit other than in extreme circumstances. A better approach is to either throw an Exception, or in this case simply return from the task method so that the caller can decide what to do next.

Looping / Recursion.

Your task method has a loop in it that keeps going until a valid choice is selected. If a valid choice is selected though, each choice calls back to task to loop again. It seems like just having task keep looping until exit is selected may be more straightforward. It would also mean that AddNumber and CallNumber didn’t have to call task to display the menu again. This in turn would mean that it was possible to call AddNumber from a different class, which goes some way towards making the class more reusable.

The main things you seem to have done is a) share the Scanner and b) move the processing into a class. You’ve also added some validation of your input. This is still not OOP.

  • The first problem I find is that your PhoneBook class will not compile. At the very least, code you submit here should compile and pass some basic sanity tests.
  • Your approach to indexing is odd, and unclear. Are you sure you understand how 0-based indexing works in Java?
  • The fixed size list is enforced in some parts of your code – you only allow entries 0-9 to be chosen, but you allow an unlimited number of entries to be added. (Is this because you want to allow an unlimited number of entries, and speed-dial applies to the first 10? If so, you should document that design decision).
  • Your input validation rejects invalid numbers (like “banana”) without giving any feedback, and the try…catch block is bigger than it needs to be, making it harder to understand.
  • Your range check on phone numbers is inconsistent (100 or 1000?) and if you were consistent, then the second “if” (in the “else”) would be redundant.
  • You still use recursion for your main “loop”
  • You still have no distinction between the data and its external presentation. Nothing about your Phonebook could be reused sensibly if you, for example, moved to a GUI or a REST API approach. Read up on “Separation Of Concerns”…
  • As already mentioned, you don’t follow Java naming conventions, and your code formatting is also non-standard – I’ve never seen “do” and “try” on the same line before, and hope never to do so again. (Formatting is easily fixed by using a decent GUI, such as Eclipse or IntelliJ and getting it to format your code)
  • Your commenting is frequently pointless – import java.util.Scanner; //Import Scanner has no value at all. Comments should add value, usually by explaining why the code is as it is.

Here’s a suggestion to get you started on an Object-Oriented approach.

In this design I assume that your PhoneBook should store an unlimited number of phone numbers in the order in which they have been entered, and that the first 10 entries (indexes 0 to 9) should be available as speed-dial numbers.

First, write a PhoneBook class which does no I/O – it should implement Iterable<String> and provide the following:

  1. A constructor which takes no arguments.
  2. An addPhoneNumber() method which takes a phone number as a parameter and stores it in some internal store – an ArrayList seems a reasonable choice (create this in the constructor).
  3. A getPhoneNumber() method which takes an integer as a parameter and returns the appropriate phone number, returning null if no corresponding phone number exists.
  4. An iterator() method which returns an iterator over the internal list of phone numbers.

Now write some unit tests for this class. Ideally use Junit, as that’s the norm in Java and learning it will give you a valuable skill. Make sure your code compiles, and passes your unit tests. You should consider offering this class and the unit tests for review.

Now write your interactive code to use this class, taking into account the feedback you’ve already been given, and following Java standards for naming and layout. Compile and test your program. Then raise a fresh request for review.

Here’s skeleton code for the PhoneBook class

import java.util.Iterator;

public class PhoneBook implements Iterable<String> {

  public PhoneBook() {
   // Add code to create the internal storage for phone numbers  
  }
  
  public void addPhoneNumber(String phoneNumber) {
    // Add the phone number to the storage
  }
  
  public String getPhoneNumber(int index) {
    return null; // Change this to return the appropriate phone number, or null if none exists 
  }
  
  public Iterator<String> iterator() {
    return null; // change this to return a suitable Iterator
  }
  
}

Leave a Reply

Your email address will not be published. Required fields are marked *