14 Replies Latest reply: Jan 1, 2008 2:16 PM by 807601 RSS

    Strings, regexes, and scanner

    807601
      I am trying to write an alternative program for a simple command-line calculator without using an exception handler. The version for the exception handler is here:
      import java.util.*;
      
      public class CalculatorNFE {
      
          /** Main method */
          public static void main(String[] args) {
              Scanner scanner = new Scanner(System.in);
              boolean continueInput = true;
              do {
                  try {
                      System.out.println("Enter first operand: ");
                      int argument1 = scanner.nextInt();
                      System.out.println("Enter operator: ");
                      char operand = scanner.next().charAt(0);
                      System.out.println("Enter second operand: ");
                      int argument2 = scanner.nextInt();
      
                      // The result of the operation
                      int result = 0;
      
                      // Determine the operator
                      switch (operand) {
                          case '+':
                              result = argument1 + argument2;
                              break;
                          case '-':
                              result = argument1 - argument2;
                              break;
                          case '*':
                              result = argument1 * argument2;
                              break;
                          case '/':
                              result = argument1 / argument2;
                      }
                      // Display result
      
                      System.out.println(argument1 + " " + operand + " " + argument2 + " = " + result);
                      continueInput = false;
                  } catch (NumberFormatException ex) {
                      System.out.println("Please use \"java operand1 operator operand2\"");
                      scanner.nextLine();
                  } catch (InputMismatchException ex) {
                      System.out.println("Please use \"java operand1 operator operand2\"");
                      scanner.nextLine();
                  }
              } while (continueInput);
          }
      }
      and the alternative code is here:
      import java.util.*;
      
      public class CalculatorNoNFE {
      
          /** Main method */
          public static void main(String[] args) {
              Scanner scanner = new Scanner(System.in);
              int argument1 = 0;
              int argument2 = 0;
              boolean continueInput = true;
              do {
                  System.out.println("Enter first operand: ");
                  String argumentString1 = scanner.next();
                  if (argumentString1 == ("[\\D]+")) {
                      System.out.println("Please use \"java operand1 operator operand2\"");
                  } else {
                      argument1 = Integer.parseInt(argumentString1);
                  }
                  System.out.println("Enter operator: ");
                  char operand = scanner.next().charAt(0);
                  System.out.println("Enter second operand: ");
                  String argumentString2 = scanner.next();
                  if (argumentString2 == ("[\\D]+")) {
                      System.out.println("Please use \"java operand1 operator operand2\"");
                  } else {
                      argument2 = Integer.parseInt(argumentString2);
                  }
                  // The result of the operation
                  int result = 0;
      
                  // Determine the operator
                  switch (operand) {
                      case '+':
                          result = argument1 + argument2;
                          break;
                      case '-':
                          result = argument1 - argument2;
                          break;
                      case '*':
                          result = argument1 * argument2;
                          break;
                      case '/':
                          result = argument1 / argument2;
                      }
                  // Display result
      
                  System.out.println(argument1 + " " + operand + " " + argument2 + " = " + result);
                  continueInput = false;
      
              } while (continueInput);
          }
      }
      The problem is, when I try to input a non-digit string for the first operand, Netbeans still outputs an exception.

      Exception in thread "main" java.lang.NumberFormatException: For input string: "T"
      at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
      at java.lang.Integer.parseInt(Integer.java:447)
      at java.lang.Integer.parseInt(Integer.java:497)
      at CalculatorNoNFE.main(CalculatorNoNFE.java:18)

      I'd like to know about your ideas on how to handle this without resorting to exception handlers. I'm new to Java and learning by myself, if you're wondering. Thanks and Happy Holidays!
        • 1. Re: Strings, regexes, and scanner
          796365
          >

          [snip]
          The problem is, when I try to input a non-digit string for the first operand, Netbeans still outputs an exception.

          Exception in thread "main" java.lang.NumberFormatException: For input string: "T"
          at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
          at java.lang.Integer.parseInt(Integer.java:447)
          at java.lang.Integer.parseInt(Integer.java:497)
          at CalculatorNoNFE.main(CalculatorNoNFE.java:18)

          I'd like to know about your ideas on how to handle this without resorting to exception handlers. I'm new to Java and learning by myself, if you're wondering. Thanks and Happy Holidays!
          That's what exception handling is for. . .use it.
          • 2. Re: Strings, regexes, and scanner
            807601
            ChuckBing wrote:
            That's what exception handling is for. . .use it.
            Yes, I know.

            This exercise in Prof. Liang's book is only to illustrate his point, that exception handling is not to be abused if it could be handled with a simple logic test.

            My question is which logic test to use to replace exception handling in a simple command-line problem.

            I am not discounting exception handling at all. I am only trying to get more mileage on the book I have by exploring all the topics that it presents. Thanks.
            • 3. Re: Strings, regexes, and scanner
              800308
              Define abuse. Yep, There's the back edge of the professors sword.

              In your case, Does using Integer.parseInt()'s propensity for throwing NumberFormatException's when it can't convert it's input into the required output constitute abuse? I think not.

              Yep, you could spend days working up a nasty complex incomprehensible unfathomable unmaintainable (keep out of reach of children) set of regex's (it can't be done with just one you know) which together correctly discern between all the possible strings which constitute a valid number and all those strings which do not... or you could just try it and catch the exception.

              Same fella difference! Except that one is simple effective, low cost, maintainable, adapts with improvements in the API, and is all round just a good clean solution. The other is product of some wuckfuckle professors nefarious idea of a good time.

              1. Use the try catch solution

              2. Tell your professor to change his medication.

              3. Go have a beer.

              Cheers.
              • 4. Re: Strings, regexes, and scanner
                807601
                corlettk wrote:

                In your case, Does using Integer.parseInt()'s propensity for throwing NumberFormatException's when it can't convert it's input into the required output constitute abuse? I think not.
                Hmmm... I kinda get what you're getting at.

                >
                1. Use the try catch solution
                Yes, I did.
                >
                2. Tell your professor to change his medication.
                Haha, I'm learning by myself, so I'm not in a position to tell him that.
                3. Go have a beer.
                Hmmm. Okay. I gotta get one of those San Miguel Beer bottles...

                No use fixing what ain't broke anyway. Happy New Year, everyone.
                • 5. Re: Strings, regexes, and scanner
                  807601
                  The professor has a perfectly valid point. The standard technique for converting a string representation of an integer to an actual integer is to call Integer.parseInt() on the string and catch the exception if the string is malformed. It's more convenient than validating and converting in separate steps, and it usually works fine, but it also violates one of the most basic rules of good programming: don't use exception handling for control flow. Or, as Bloch says in Effective Java, "use exceptions only for exceptional conditions." Malformed command-line input is not an exceptional condition.

                  The reason your code still throwing the exception is because your validation logic is wrong:
                              if (argumentString1 == ("[\\D]+")) {
                                  System.out.println("Please use \"java operand1 operator operand2\"");
                              } else {
                                  argument1 = Integer.parseInt(argumentString1);
                              }
                  You seem to be trying to use a regex validate the input, but this is all wrong. For one thing, you should never use == to compare strings; the correct way to find out if two strings have indentical contents to use the equals() method. But what you want to use in this case is the matches() method:
                              if (argumentString1.matches("\\d+")) {
                                  argument1 = Integer.parseInt(argumentString1);
                              } else {
                                  System.out.println("Please use \"java operand1 operator operand2\"");
                              }
                  Note that I'm not recommending this approach; it's much less efficient than the catch-and-complain techinique, and it will still throw an exception if the string represents a number larger than Integer.MAX_VALUE. Those problems can be fixed, nut there's probably no point. The standard approach is usually the only approach that makes sense, but you should be aware that it relies on what is usually considered a bad practice.
                  • 6. Re: Strings, regexes, and scanner
                    807601
                    The professor has a perfectly valid point. The standard technique for converting a string representation of an integer to an actual integer is to call Integer.parseInt() on the string and catch the exception if the string is malformed. It's more convenient than validating and converting in separate steps, and it usually works fine, but it also violates one of the most basic rules of good programming: don't use exception handling for control flow. Or, as Bloch says in Effective Java, "use exceptions only for exceptional conditions." Malformed command-line input is not an exceptional condition.

                    The reason your code still throwing the exception is because your validation logic is wrong:
                                if (argumentString1 == ("[\\D]+")) {
                                    System.out.println("Please use \"java operand1 operator operand2\"");
                                } else {
                                    argument1 = Integer.parseInt(argumentString1);
                                }
                    You seem to be trying to use a regex validate the input, but this is all wrong. For one thing, you should never use == to compare strings; the correct way to find out if two strings have indentical contents to use the equals() method. But what you want to use in this case is the matches() method:
                                if (argumentString1.matches("\\d+")) {
                                    argument1 = Integer.parseInt(argumentString1);
                                } else {
                                    System.out.println("Please use \"java operand1 operator operand2\"");
                                }
                    Note that I'm not recommending this approach; it's much less efficient than the catch-and-complain techinique, and it will still throw an exception if the string represents a number larger than Integer.MAX_VALUE. Those problems can be fixed, nut there's probably no point. The standard approach is usually the only approach that makes sense, but you should be aware that it relies on what is usually considered a bad practice.
                    • 7. Re: Strings, regexes, and scanner
                      800308
                      Note that I'm not recommending this approach;
                      Ummm... What are you recommending? Seriously?

                      I mean... I understand the reasons behind the general rule, but also think this case fits squarly into "break the rules coz it works" category. So there's not hard a fast edge to the greyness... so how grey is black?

                      What is an "exceptional condition."... Is "wasting my life thinking about this to take care of some idiout user who can't even enter a valid integer when requested" an exceptional condition, or is it bread and butter. Hmmm.
                      • 8. Re: Strings, regexes, and scanner
                        807601
                        I'm not recommending anything. I'm just trying to explain what I think the professor was talking about. In this case, as I said earlier, using Integer.parseInt() to both validate and convert the string is the only sensible approach. But if you were converting a large list of strings to {color:#000080}int{color}s, that exception handling would seriously degrade your performance.
                        • 9. Re: Strings, regexes, and scanner
                          807601
                          uncle_alice wrote:
                          The professor has a perfectly valid point. The standard technique for converting a string representation of an integer to an actual integer is to call Integer.parseInt() on the string and catch the exception if the string is malformed. It's more convenient than validating and converting in separate steps, and it usually works fine, but it also violates one of the most basic rules of good programming: don't use exception handling for control flow. Or, as Bloch says in Effective Java, "use exceptions only for exceptional conditions." Malformed command-line input is not an exceptional condition.
                          I thought so too, uncle_alice.
                          >
                          The reason your code still throwing the exception is because your validation logic is wrong: [snip] For one thing, you should never use == to compare strings; the correct way to find out if two strings have indentical contents to use the equals() method.
                          I did use the equals method on my own (after posting this), and it generates the same error.
                          Note that I'm not recommending this approach; it's much less efficient than the catch-and-complain techinique, and it will still throw an exception if the string represents a number larger than Integer.MAX_VALUE. Those problems can be fixed, nut there's probably no point. The standard approach is usually the only approach that makes sense, but you should be aware that it relies on what is usually considered a bad practice.
                          Thanks. Here is my revised code. I think there would be problems in getting the input for the operator, but I'm hoping that it would be less than the problem of non-digit input. I'm sorry I gave out the stars already though, as I was really in dire need of a drink.
                          import java.util.*;
                          
                          public class CalculatorNoNFE {
                          
                              /** Main method */
                              public static void main(String[] args) {
                                  Scanner scanner = new Scanner(System.in);
                                  int argument1 = 0;
                                  int argument2 = 0;
                                  boolean continueInput = true;
                                  do {
                                      System.out.println("Enter first operand: ");
                                      String argumentString1 = scanner.next();
                                      if (argumentString1.matches("\\D+")) {
                                          System.out.println("Please use \"java operand1 operator operand2\"");
                                          System.exit(1);
                                      } else {
                                          argument1 = Integer.parseInt(argumentString1);
                                      }
                                      System.out.println("Enter operator: ");
                                      String operandString = scanner.next();
                                      char operand = operandString.charAt(0);
                                      if (operandString.matches("\\d+")) {
                                          System.out.println("Please use \"java operand1 operator operand2\"");
                                          System.exit(1);
                                      }             
                                      System.out.println("Enter second operand: ");
                                      String argumentString2 = scanner.next();
                                      if (argumentString2.matches("\\D+")) {
                                          System.out.println("Please use \"java operand1 operator operand2\"");
                                          System.exit(1);
                                      } else {
                                          argument2 = Integer.parseInt(argumentString2);
                                      }
                                      // The result of the operation
                                      int result = 0;
                          
                                      // Determine the operator
                                      switch (operand) {
                                          case '+':
                                              result = argument1 + argument2;
                                              break;
                                          case '-':
                                              result = argument1 - argument2;
                                              break;
                                          case '*':
                                              result = argument1 * argument2;
                                              break;
                                          case '/':
                                              result = argument1 / argument2;
                                          }
                                      // Display result
                          
                                      System.out.println(argument1 + " " + operand + " " + argument2 + " = " + result);
                                      continueInput = false;
                          
                                  } while (continueInput);
                              }
                          }
                          • 10. Re: Strings, regexes, and scanner
                            3004
                            uncle_alice wrote:
                            The professor has a perfectly valid point. The standard technique for converting a string representation of an integer to an actual integer is to call Integer.parseInt() on the string and catch the exception if the string is malformed. It's more convenient than validating and converting in separate steps, and it usually works fine, but it also violates one of the most basic rules of good programming: don't use exception handling for control flow. Or, as Bloch says in Effective Java, "use exceptions only for exceptional conditions." Malformed command-line input is not an exceptional condition.
                            I disagree.

                            There is an expected code path--e.g., the user enters an int, the input routine parses it, and the computation routine processes it.

                            Even though malfomed input is not uncommon, it's still "exceptional" in that it is outside the intended or generally expected path. I think catch and complain is exactly the right approach here.
                            • 11. Re: Strings, regexes, and scanner
                              807601
                                          if (argumentString1.matches("\\D+")) {
                                              System.out.println("Please use \"java operand1 operator operand2\"");
                                              System.exit(1);
                                          } else {
                                              argument1 = Integer.parseInt(argumentString1);
                                          }
                              You're misinterpreting the result of that regex match. \D+ matches a string that contains one or more characters, none of them digits. If it fails to match, that doesn't mean it contains only digits. It could contain one or more digits in addition to any number of non-digits. It could even be empty. The condition in the code above should be
                                          if (!argumentString1.matches("\\d+"))
                              You should invert the rest of the tests the same way. But I suggest you also rearrange the code so that a positive match means a positive outcome:
                                          if (argumentString1.matches("\\d+")) {
                                              argument1 = Integer.parseInt(argumentString1);
                                          } else {
                                              System.out.println("Please use \"java operand1 operator operand2\"");
                                              System.exit(1);
                                          }
                              Unless you have a compelling reason to use it, you should avoid using negative logic. Your code will be much easier to read and debug if you stick with positive logic as much as possible.

                              By the way, don't worry about the Dukes; they're just one of the many useless, malfunctioning features of these forums. :-/
                              • 12. Re: Strings, regexes, and scanner
                                807601
                                uncle_alice wrote:
                                You're misinterpreting the result of that regex match. \D+ matches a string that contains one or more characters, none of them digits. If it fails to match, that doesn't mean it contains only digits. It could contain one or more digits in addition to any number of non-digits. It could even be empty. The condition in the code above should be
                                            if (!argumentString1.matches("\\d+"))
                                You should invert the rest of the tests the same way.
                                Good point there.
                                Unless you have a compelling reason to use it, you should avoid using negative logic. Your code will be much easier to read and debug if you stick with positive logic as much as possible.
                                Another good point.
                                By the way, don't worry about the Dukes; they're just one of the many useless, malfunctioning features of these forums. :-/
                                I figured as much. But then again, it's a small token of gratitude that is better than zilch. Thanks for the explanation, I really learned something today.

                                Here is my modified code:
                                import java.util.*;
                                
                                public class CalculatorNoNFE {
                                
                                    /** Main method */
                                    public static void main(String[] args) {
                                        Scanner scanner = new Scanner(System.in);
                                        int argument1 = 0;
                                        int argument2 = 0;
                                        boolean continueInput = true;
                                        do {
                                            System.out.println("Enter first operand: ");
                                            String argumentString1 = scanner.next();
                                            if (argumentString1.matches("\\d+")) {
                                                argument1 = Integer.parseInt(argumentString1);
                                            } else {
                                                System.out.println("Please use \"java operand1 operator operand2\"");
                                                System.exit(1);
                                            }
                                            System.out.println("Enter operator: ");
                                            String operandString = scanner.next();
                                            char operand = operandString.charAt(0);
                                            if (operandString.matches("[\\d\\w\\s]")) {
                                                System.out.println("Please use \"java operand1 operator operand2\"");
                                                System.exit(1);
                                            }             
                                            System.out.println("Enter second operand: ");
                                            String argumentString2 = scanner.next();
                                            if (argumentString2.matches("\\d+")) {
                                                argument2 = Integer.parseInt(argumentString2);
                                            } else {
                                                System.out.println("Please use \"java operand1 operator operand2\"");
                                                System.exit(1);
                                            }
                                            // The result of the operation
                                            int result = 0;
                                
                                            // Determine the operator
                                            switch (operand) {
                                                case '+':
                                                    result = argument1 + argument2;
                                                    break;
                                                case '-':
                                                    result = argument1 - argument2;
                                                    break;
                                                case '*':
                                                    result = argument1 * argument2;
                                                    break;
                                                case '/':
                                                    result = argument1 / argument2;
                                                }
                                            // Display result
                                
                                            System.out.println(argument1 + " " + operand + " " + argument2 + " = " + result);
                                            continueInput = false;
                                
                                        } while (continueInput);
                                    }
                                }
                                • 13. Re: Strings, regexes, and scanner
                                  807601
                                  Your validation logic for the operator still needs work. You're making the same mistake you did with the numbers, trying to match one or more invalid characters on the assumption that a failed match means the string contains only valid characters. But your regex only matches ASCII letters, digits, whitespace, and the underscore character. That still leaves all of the ASCII punctuation and non-whitespace control characters, plus all non-ASCII characters. And again, a supposedly valid input (ie, one that fails to match the regex) can be any length including zero, but a valid input in this case should only contain one character. You'll be much better off using a regex that matches a valid input, like this:
                                  if (!operandString.matches("[+/*-]"))
                                  In this case you have a good reason to use a negated condition--two of them, in fact:

                                  1.) A regex to match all possible invalid inputs would be much more complicated than the regex for a valid input.

                                  2.) If the match succeeds, no further action is required. In the other tests, you have to parse the string to get the number it represents, but here only the case of a failed match is interesting.

                                  But in actual fact, you are doing further processing on that input: you're extracting the first character and storing it in the 'operand' variable (which is a misnomer, by the way: the second input is the operator and the other two are the operands). In fact, you're doing that assignment before you even validate the input. You could get away with that if you were doing the test right, but it's counter-intuitive. That way lies spaghetti code, the bane of every programmer's existence (actually, one of many banes, but you get the picture). Unless you're using a well-known idiom like the double-tasking of Integer.parseInt() that we were just talking about, you should try to structure your code to reflect the logic of the task: in this case, validate the input before you do anything with it. Here's how I would structure that test:
                                              char operator = '\0'; 
                                              if (operatorString.matches("[+/*-]")) {
                                                  operator = operatorString.charAt(0);
                                              } else {
                                                  // Try to let the user know exactly what they did wrong
                                                  System.out.println("Invalid operator: " + operatorString);
                                                  System.exit(1);
                                              }
                                  This has the added advantage of following the same pattern as the other two validation segments. That makes it easier for a programmer to scan the source code and get the gist of what it does. Readability is one of the most important virtues of good code.
                                  • 14. Re: Strings, regexes, and scanner
                                    807601
                                    Hi uncle_alice, what can I say? You've nailed it all on the head. Just some notes:
                                    which is a misnomer, by the way
                                    With all the time I spent looking at that code, I still missed it.
                                    You could get away with that if you were doing the test right,
                                    I wasn't. In the code previously posted, it would still permit input outside the four characters as operators.

                                    I felt a little guilty forgetting to revisit this one, I just ignored the errors in the hope that when I get to finish the book I'm reading, I would get the insight to finally troubleshoot those errors.

                                    Here is the edited code, and no more errors (from my standpoint):
                                    /*********************************************
                                     * with support from uncle_alice (Sun Forums)*
                                     *********************************************/
                                    import java.util.*;
                                    
                                    public class CalculatorNoNFE {
                                    
                                        /** Main method */
                                        public static void main(String[] args) {
                                            Scanner scanner = new Scanner(System.in);
                                            int argument1 = 0;
                                            int argument2 = 0;
                                            boolean continueInput = true;
                                            do {
                                                System.out.println("Enter first operand: ");
                                                String argumentString1 = scanner.next();
                                                if (argumentString1.matches("\\d+")) {
                                                    argument1 = Integer.parseInt(argumentString1);
                                                } else {
                                                    System.out.println("Please use \"java operand1 operator operand2\"");
                                                    System.exit(1);
                                                }
                                                System.out.println("Enter operator: ");
                                                String operatorString = scanner.next();
                                                char operator = '\0';
                                                if (operatorString.matches("[+/*-]")) {
                                                    operator = operatorString.charAt(0);
                                                } else {
                                                    // Try to let the user know exactly what they did wrong
                                                    System.out.println("Invalid operator: " + operatorString);
                                                    System.exit(1);
                                                }
                                                System.out.println("Enter second operand: ");
                                                String argumentString2 = scanner.next();
                                                if (argumentString2.matches("\\d+")) {
                                                    argument2 = Integer.parseInt(argumentString2);
                                                } else {
                                                    System.out.println("Please use \"java operand1 operator operand2\"");
                                                    System.exit(1);
                                                }
                                                // The result of the operation
                                                int result = 0;
                                    
                                                // Determine the operator
                                                switch (operator) {
                                                    case '+':
                                                        result = argument1 + argument2;
                                                        break;
                                                    case '-':
                                                        result = argument1 - argument2;
                                                        break;
                                                    case '*':
                                                        result = argument1 * argument2;
                                                        break;
                                                    case '/':
                                                        result = argument1 / argument2;
                                                    }
                                                // Display result
                                    
                                                System.out.println(argument1 + " " + operator + " " + argument2 + " = " + result);
                                                continueInput = false;
                                    
                                            } while (continueInput);
                                        }
                                    }