Самый элегантный способ определить, является ли String числом? - PullRequest
8 голосов
/ 11 декабря 2008

Есть ли лучший, более элегантный (и / или, возможно, более быстрый) способ, чем

boolean isNumber = false;
try{
   Double.valueOf(myNumber);
   isNumber = true;
} catch (NumberFormatException e) {
}

...


Редактировать : Поскольку я не могу выбрать два ответа, я выбираю один из регулярных выражений, потому что а) это изящно и б) говорить "Джон Скит решил проблему" - это тавтология, потому что сам Джон Скит - решение всех проблем.

Ответы [ 11 ]

9 голосов
/ 11 декабря 2008

Я не верю, что в Java есть что-то встроенное, чтобы делать это быстрее и надежнее, предполагая, что позже вам захочется фактически проанализировать это с Double.valueOf (или подобным).

Я бы использовал Double.parseDouble вместо Double.valueOf, чтобы избежать ненужного создания Double, и вы также можете избавиться от явно глупых чисел быстрее, чем исключение, проверив цифры, E, - и. заранее. Итак, что-то вроде:

public boolean isDouble(String value)
{        
    boolean seenDot = false;
    boolean seenExp = false;
    boolean justSeenExp = false;
    boolean seenDigit = false;
    for (int i=0; i < value.length(); i++)
    {
        char c = value.charAt(i);
        if (c >= '0' && c <= '9')
        {
            seenDigit = true;
            continue;
        }
        if ((c == '-' || c=='+') && (i == 0 || justSeenExp))
        {
            continue;
        }
        if (c == '.' && !seenDot)
        {
            seenDot = true;
            continue;
        }
        justSeenExp = false;
        if ((c == 'e' || c == 'E') && !seenExp)
        {
            seenExp = true;
            justSeenExp = true;
            continue;
        }
        return false;
    }
    if (!seenDigit)
    {
        return false;
    }
    try
    {
        Double.parseDouble(value);
        return true;
    }
    catch (NumberFormatException e)
    {
        return false;
    }
}

Обратите внимание, что, несмотря на несколько попыток, это все же не охватывает значения "NaN" или шестнадцатеричные значения. То, хотите ли вы, чтобы они прошли или нет, зависит от контекста.

По моему опыту регулярные выражения работают медленнее, чем чек, указанный выше.

9 голосов
/ 11 декабря 2008

Вы можете использовать регулярное выражение, то есть что-то вроде String.matches("^[\\d\\-\\.]+$"); (если вы не проверяете отрицательные числа или числа с плавающей запятой, вы можете немного упростить).

Не уверен, что это будет быстрее, чем описанный вами метод.

Редактировать: в свете всей этой полемики я решил провести тест и получить некоторые данные о том, насколько быстрым был каждый из этих методов. Не столько в правильности, сколько в том, как быстро они бежали.

Вы можете прочитать о моих результатах в моем блоге . (Подсказка: Джон Скит FTW).

8 голосов
/ 11 декабря 2008

См. java.text.NumberFormat (javadoc).

NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
Number myNumber = nf.parse(myString);
int myInt = myNumber.intValue();
double myDouble = myNumber.doubleValue();
5 голосов
/ 12 декабря 2008

Правильное регулярное выражение в действительности дано в Двойной Javadocs :

Чтобы не вызывать этот метод для недопустимой строки и вызывать исключение NumberFormatException, регулярное выражение ниже можно использовать для отображения строки ввода:

    final String Digits     = "(\\p{Digit}+)";
    final String HexDigits  = "(\\p{XDigit}+)";
    // an exponent is 'e' or 'E' followed by an optionally 
    // signed decimal integer.
    final String Exp        = "[eE][+-]?"+Digits;
    final String fpRegex    =
        ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
         "[+-]?(" + // Optional sign character
         "NaN|" +           // "NaN" string
         "Infinity|" +      // "Infinity" string

         // A decimal floating-point string representing a finite positive
         // number without a leading sign has at most five basic pieces:
         // Digits . Digits ExponentPart FloatTypeSuffix
         // 
         // Since this method allows integer-only strings as input
         // in addition to strings of floating-point literals, the
         // two sub-patterns below are simplifications of the grammar
         // productions from the Java Language Specification, 2nd 
         // edition, section 3.10.2.

         // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
         "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+

         // . Digits ExponentPart_opt FloatTypeSuffix_opt
         "(\\.("+Digits+")("+Exp+")?)|"+

   // Hexadecimal strings
   "((" +
    // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
    "(0[xX]" + HexDigits + "(\\.)?)|" +

    // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
    "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +

    ")[pP][+-]?" + Digits + "))" +
         "[fFdD]?))" +
         "[\\x00-\\x20]*");// Optional trailing "whitespace"

    if (Pattern.matches(fpRegex, myString))
        Double.valueOf(myString); // Will not throw NumberFormatException
    else {
        // Perform suitable alternative action
    }

Это не учитывает локализованные представления, однако:

Чтобы интерпретировать локализованные строковые представления значения с плавающей запятой, используйте подклассы NumberFormat .

3 голосов
/ 11 декабря 2008

Использование мистера Скита:

private boolean IsValidDoubleChar(char c)
{
    return "0123456789.+-eE".indexOf(c) >= 0;
}

public boolean isDouble(String value)
{
    for (int i=0; i < value.length(); i++)
    {
        char c = value.charAt(i);
        if (IsValidDoubleChar(c))
            continue;
        return false;
    }
    try
    {
        Double.parseDouble(value);
        return true;
    }
    catch (NumberFormatException e)
    {
        return false;
    }
}
3 голосов
/ 11 декабря 2008

Используйте StringUtils.isDouble(String) в Apache Commons.

2 голосов
/ 11 декабря 2008

Большинство из этих ответов несколько приемлемых решений. Во всех решениях регулярных выражений проблема неверна во всех случаях, которые могут вас беспокоить.

Если вы действительно хотите убедиться, что строка является действительным числом, я бы использовал ваше собственное решение. Не забывайте об этом, я полагаю, что в большинстве случаев String будет действительным числом и не вызовет исключения. Таким образом, большую часть времени производительность будет идентична производительности Double.valueOf ().

Полагаю, это действительно не ответ, за исключением того, что он проверяет ваш первоначальный инстинкт.

Randy

2 голосов
/ 11 декабря 2008

Я бы использовал Jakarta commons-lang , как всегда! Но я понятия не имею, является ли их реализация быстрой или нет. Он не полагается на исключения, которые могут быть хорошими для производительности ...

1 голос
/ 15 декабря 2008

Я предпочитаю использовать цикл над представлением Strings char [] и использовать метод Character.isDigit (). Если желательна элегантность, я думаю, что это самая читаемая :

package tias;

public class Main {
  private static final String NUMERIC = "123456789";
  private static final String NOT_NUMERIC = "1L5C";

  public static void main(String[] args) {
    System.out.println(isStringNumeric(NUMERIC));
    System.out.println(isStringNumeric(NOT_NUMERIC));
  }

  private static boolean isStringNumeric(String aString) {
    if (aString == null || aString.length() == 0) {
      return false;
    }
    for (char c : aString.toCharArray() ) {
      if (!Character.isDigit(c)) {
        return false;
      }
    }
    return true;
  }

}

1 голос
/ 11 декабря 2008

После ответа Филла я могу предложить еще одно регулярное выражение?

String.matches("^-?\\d+(\\.\\d+)?$");
...