Хороший способ инкапсуляции Integer.parseInt()

У меня есть проект, в котором мы часто используем Integer.parseInt() для преобразования String в int. Когда что-то пойдет не так (например, String - это не число, а буква a или что-то еще), этот метод будет генерировать исключение. Однако, если мне приходится обрабатывать исключения в моем коде повсюду, это начинает выглядеть очень уродливо очень быстро. Я хотел бы поместить это в метод, однако я не знаю, как вернуть чистую ценность, чтобы показать, что преобразование пошло не так.

В С++ я мог бы создать метод, который принял указатель на int и пусть сам метод вернет true или false. Однако, насколько я знаю, это невозможно в Java. Я мог бы также создать объект, содержащий переменную true/false и преобразованное значение, но это тоже не кажется идеальным. То же самое происходит и для глобальной ценности, и это может вызвать некоторые проблемы при многопоточности.

Так есть ли чистый способ сделать это?

Ответ 1

Вы можете вернуть Integer вместо int, возвращая null при сбое разбора.

Стыдно, что Java не обеспечивает способ сделать это, если не исключено внутреннее исключение - вы можете скрыть исключение (поймав его и вернув null), но это все равно может быть проблемой производительности, повторно разбирая сотни тысяч бит данных, предоставленных пользователем.

EDIT: Код для такого метода:

public static Integer tryParse(String text) {
  try {
    return Integer.parseInt(text);
  } catch (NumberFormatException e) {
    return null;
  }
}

Обратите внимание, что я не уверен, что это будет делать, если text равно null. Вы должны учитывать это - если он представляет ошибку (т.е. Ваш код может передавать недопустимое значение, но никогда не должен пропускать null), то бросание исключения является подходящим; если он не представляет ошибку, то вы, вероятно, должны просто вернуть null, как и для любого другого недопустимого значения.

Первоначально этот ответ использовал конструктор new Integer(String); теперь он использует Integer.parseInt и операцию бокса; таким образом, небольшие значения будут помещены в кешированные объекты Integer, что сделает его более эффективным в этих ситуациях.

Ответ 2

Какое поведение вы ожидаете, когда это не число?

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

public static int parseWithDefault(String number, int defaultVal) {
  try {
    return Integer.parseInt(number);
  } catch (NumberFormatException e) {
    return defaultVal;
  }
}

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

Ответ 3

В некоторых случаях вы должны обрабатывать ошибки синтаксического анализа как аварийные ситуации, но в других случаях, таких как конфигурация приложения, я предпочитаю обрабатывать отсутствующие данные со значениями по умолчанию, используя Apache Commons Lang 3 NumberUtils.

int port = NumberUtils.toInt(properties.getProperty("port"), 8080);

Ответ 4

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

if(value.matches("\\d+") {
    Integer.parseInt(value);
}

Ответ 5

Существует Ints.tryParse() в гуавы. Он не генерирует исключение для нечисловой строки, однако он генерирует исключение для пустой строки.

Ответ 6

Может быть, вы можете использовать что-то вроде этого:

public class Test {
public interface Option<T> {
    T get();

    T getOrElse(T def);

    boolean hasValue();
}

final static class Some<T> implements Option<T> {

    private final T value;

    public Some(T value) {
        this.value = value;
    }

    @Override
    public T get() {
        return value;
    }

    @Override
    public T getOrElse(T def) {
        return value;
    }

    @Override
    public boolean hasValue() {
        return true;
    }
}

final static class None<T> implements Option<T> {

    @Override
    public T get() {
        throw new UnsupportedOperationException();
    }

    @Override
    public T getOrElse(T def) {
        return def;
    }

    @Override
    public boolean hasValue() {
        return false;
    }

}

public static Option<Integer> parseInt(String s) {
    Option<Integer> result = new None<Integer>();
    try {
        Integer value = Integer.parseInt(s);
        result = new Some<Integer>(value);
    } catch (NumberFormatException e) {
    }
    return result;
}

}

Ответ 7

После прочтения ответов на вопрос, я думаю, что инкапсуляция или упаковка метода parseInt не нужна, может быть, даже не очень хорошая идея.

Вы можете вернуть "нуль", как предложил Джон, но более или менее заменяя конструкцию try/catch с помощью null-check. Там только небольшая разница в поведении, если вы "забыли" обработку ошибок: если вы не поймаете исключение, то нет назначения, а переменная с левой стороны сохраняет старое значение. Если вы не проверите значение null, вы, вероятно, попадете в JVM (NPE).

Предложение yawn выглядит более элегантно для меня, потому что мне не нравится возвращать null, чтобы сигнализировать о некоторых ошибках или исключительных состояниях. Теперь вам нужно проверить ссылочное равенство с предопределенным объектом, что указывает на проблему. Но, как утверждают другие, если вы снова забыли "проверить", а String не поддается анализу, программа будет продолжена с завернутым int внутри вашего объекта "ERROR" или "NULL".

Решение Nikolay еще более ориентировано на объект и будет работать с методами parseXXX из других классов-оболочек. Но, в конце концов, он просто заменил NumberFormatException на исключение OperationNotSupported - снова вам понадобится try/catch для обработки невоспроизводимых входов.

Итак, это мой вывод, чтобы не инкапсулировать простой метод parseInt. Я бы только инкапсулировал, если бы я мог добавить некоторую (зависимую от приложения) обработку ошибок.

Ответ 8

Вы также можете очень быстро скопировать поведение С++, которое вы хотите просто

public static boolean parseInt(String str, int[] byRef) {
    if(byRef==null) return false;
    try {
       byRef[0] = Integer.parseInt(prop);
       return true;
    } catch (NumberFormatException ex) {
       return false;
    }
}

Вы должны использовать метод следующим образом:

int[] byRef = new int[1];
boolean result = parseInt("123",byRef);

После этого переменная result имеет значение true, если все прошло хорошо, а byRef[0] содержит проанализированное значение.

Лично я буду придерживаться исключения.

Ответ 9

Моя Java немного ржавая, но позвольте мне посмотреть, могу ли я указать вам в правильном направлении:

public class Converter {

    public static Integer parseInt(String str) {
        Integer n = null;

        try {
            n = new Integer(Integer.tryParse(str));
        } catch (NumberFormatException ex) {
            // leave n null, the string is invalid
        }

        return n;
    }

}

Если ваше возвращаемое значение null, у вас плохое значение. В противном случае у вас есть действительный Integer.

Ответ 10

Ответ, данный Jon Skeet, прекрасен, но мне не нравится возвращать объект Integer null. Мне кажется, что это сбивает с толку. С Java 8 есть лучший вариант (на мой взгляд), используя OptionalInt:

public static OptionalInt tryParse(String value) {
 try {
     return OptionalInt.of(Integer.parseInt(value));
  } catch (NumberFormatException e) {
     return OptionalInt.empty();
  }
}

Это делает его явным, что вы должны обрабатывать случай, когда значение не доступно. Я бы предпочел, чтобы в будущем эта функция была добавлена ​​в java-библиотеку, но я не знаю, произойдет ли это когда-нибудь.

Ответ 11

Как насчет форматирования метода parseInt?

Просто, просто скопируйте содержимое в новую утилиту, которая возвращает Integer или Optional<Integer>, и замените броски на возврат. Кажется, в базовом коде нет исключений, но лучше проверить.

Пропуская всю обработку исключений, вы можете сэкономить некоторое время на недопустимых входах. И метод существует с JDK 1.0, поэтому маловероятно, что вам придется много сделать, чтобы поддерживать его в актуальном состоянии.

Ответ 12

Я бы предложил вам рассмотреть такой метод, как

 IntegerUtilities.isValidInteger(String s)

который вы затем реализуете по своему усмотрению. Если вы хотите вернуть результат - возможно, потому, что вы используете Integer.parseInt() в любом случае - вы можете использовать трюк массива.

 IntegerUtilities.isValidInteger(String s, int[] result)

где вы устанавливаете результат [0] на целое значение, найденное в процессе.

Ответ 13

Это несколько похоже на решение Николая:

 private static class Box<T> {
  T me;
  public Box() {}
  public T get() { return me; }
  public void set(T fromParse) { me = fromParse; }
 }

 private interface Parser<T> {
  public void setExclusion(String regex);
  public boolean isExcluded(String s);
  public T parse(String s);
 }

 public static <T> boolean parser(Box<T> ref, Parser<T> p, String toParse) {
  if (!p.isExcluded(toParse)) {
   ref.set(p.parse(toParse));
   return true;
  } else return false;
 }

 public static void main(String args[]) {
  Box<Integer> a = new Box<Integer>();
  Parser<Integer> intParser = new Parser<Integer>() {
   String myExclusion;
   public void setExclusion(String regex) {
    myExclusion = regex;
   }
   public boolean isExcluded(String s) {
    return s.matches(myExclusion);
   }
   public Integer parse(String s) {
    return new Integer(s);
   }
  };
  intParser.setExclusion("\\D+");
  if (parser(a,intParser,"123")) System.out.println(a.get());
  if (!parser(a,intParser,"abc")) System.out.println("didn't parse "+a.get());
 }

Основной метод демонстрирует код. Другой способ реализации интерфейса Parser, очевидно, состоял бы в том, чтобы просто установить "\ D +" из построения, и методы не делают ничего.

Ответ 14

Вы можете опрокинуть свой собственный, но так же просто использовать общедоступный метод StringUtils.isNumeric() . Он использует Character.isDigit() для повторения каждого символа в строке.

Ответ 15

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

Java.util.Scanner keyboard = new Java.util.Scanner(System.in);

public int GetMyInt(){
    int ret;
    System.out.print("Give me an Int: ");
    try{
        ret = Integer.parseInt(keyboard.NextLine());

    }
    catch(Exception e){
        System.out.println("\nThere was an error try again.\n");
        ret = GetMyInt();
    }
    return ret;
}

Ответ 16

Чтобы избежать исключения, вы можете использовать метод Java Format.parseObject. Ниже приведен код упрощенной версии класса Apache Common IntegerValidator.

public static boolean tryParse(String s, int[] result)
{
    NumberFormat format = NumberFormat.getIntegerInstance();
    ParsePosition position = new ParsePosition(0);
    Object parsedValue = format.parseObject(s, position);

    if (position.getErrorIndex() > -1)
    {
        return false;
    }

    if (position.getIndex() < s.length())
    {
        return false;
    }

    result[0] = ((Long) parsedValue).intValue();
    return true;
}

Вы можете использовать тэг AtomicInteger или тэг int[] в зависимости от ваших предпочтений.

Вот мой тест, который его использует -

int[] i = new int[1];
Assert.assertTrue(IntUtils.tryParse("123", i));
Assert.assertEquals(123, i[0]);

Ответ 17

У меня тоже была такая же проблема. Это метод, который я написал, чтобы спросить пользователя о вводе и не принимать входные данные, если это не целое число. Обратите внимание, что я новичок, поэтому, если код не работает должным образом, обвините мою неопытность!

private int numberValue(String value, boolean val) throws IOException {
    //prints the value passed by the code implementer
    System.out.println(value);
    //returns 0 is val is passed as false
    Object num = 0;
    while (val) {
        num = br.readLine();
        try {
            Integer numVal = Integer.parseInt((String) num);
            if (numVal instanceof Integer) {
                val = false;
                num = numVal;
            }
        } catch (Exception e) {
            System.out.println("Error. Please input a valid number :-");
        }
    }
    return ((Integer) num).intValue();
}

Ответ 18

Это ответ на вопрос 8391979: "Есть ли у java int.tryparse, который не генерирует исключение для плохих данных? [duplicate]", который закрыт и связан с этим вопросом.

Edit 2016 08 17: Добавлены методы ltrimZeroes и вызваны в tryParse(). Без начальных нулей в numberString могут появляться ложные результаты (см. Комментарии в коде). В настоящее время существует также открытый статический метод String ltrimZeroes (String numberString), который работает для положительных и отрицательных "чисел" (END Edit)

Ниже вы найдете рудиментарный класс Wrapper (бокс) для int с высокоскоростным оптимизированным методом tryParse() (похожим на С#), который анализирует сама строку и немного быстрее, чем Integer.parseInt(String s) из Java:

public class IntBoxSimple {
    // IntBoxSimple - Rudimentary class to implement a C#-like tryParse() method for int
    // A full blown IntBox class implementation can be found in my Github project
    // Copyright (c) 2016, Peter Sulzer, Fürth
    // Program is published under the GNU General Public License (GPL) Version 1 or newer

    protected int _n; // this "boxes" the int value

    // BEGIN The following statements are only executed at the
    // first instantiation of an IntBox (i. e. only once) or
    // already compiled into the code at compile time:
    public static final int MAX_INT_LEN =
            String.valueOf(Integer.MAX_VALUE).length();
    public static final int MIN_INT_LEN =
            String.valueOf(Integer.MIN_VALUE).length();
    public static final int MAX_INT_LASTDEC =
            Integer.parseInt(String.valueOf(Integer.MAX_VALUE).substring(1));
    public static final int MAX_INT_FIRSTDIGIT =
            Integer.parseInt(String.valueOf(Integer.MAX_VALUE).substring(0, 1));
    public static final int MIN_INT_LASTDEC =
            -Integer.parseInt(String.valueOf(Integer.MIN_VALUE).substring(2));
    public static final int MIN_INT_FIRSTDIGIT =
            Integer.parseInt(String.valueOf(Integer.MIN_VALUE).substring(1,2));
    // END The following statements...

    // ltrimZeroes() methods added 2016 08 16 (are required by tryParse() methods)
    public static String ltrimZeroes(String s) {
        if (s.charAt(0) == '-')
            return ltrimZeroesNegative(s);
        else
            return ltrimZeroesPositive(s);
    }
    protected static String ltrimZeroesNegative(String s) {
        int i=1;
        for ( ; s.charAt(i) == '0'; i++);
        return ("-"+s.substring(i));
    }
    protected static String ltrimZeroesPositive(String s) {
        int i=0;
        for ( ; s.charAt(i) == '0'; i++);
        return (s.substring(i));
    }

    public static boolean tryParse(String s,IntBoxSimple intBox) {
        if (intBox == null)
            // intBoxSimple=new IntBoxSimple(); // This doesn't work, as
            // intBoxSimple itself is passed by value and cannot changed
            // for the caller. I. e. "out"-arguments of C# cannot be simulated in Java.
            return false; // so we simply return false
        s=s.trim(); // leading and trailing whitespace is allowed for String s
        int len=s.length();
        int rslt=0, d, dfirst=0, i, j;
        char c=s.charAt(0);
        if (c == '-') {
            if (len > MIN_INT_LEN) { // corrected (added) 2016 08 17
                s = ltrimZeroesNegative(s);
                len = s.length();
            }
            if (len >= MIN_INT_LEN) {
                c = s.charAt(1);
                if (!Character.isDigit(c))
                    return false;
                dfirst = c-'0';
                if (len > MIN_INT_LEN || dfirst > MIN_INT_FIRSTDIGIT)
                    return false;
            }
            for (i = len - 1, j = 1; i >= 2; --i, j *= 10) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt -= (c-'0')*j;
            }
            if (len < MIN_INT_LEN) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt -= (c-'0')*j;
            } else {
                if (dfirst >= MIN_INT_FIRSTDIGIT && rslt < MIN_INT_LASTDEC)
                    return false;
                rslt -= dfirst * j;
            }
        } else {
            if (len > MAX_INT_LEN) { // corrected (added) 2016 08 16
                s = ltrimZeroesPositive(s);
                len=s.length();
            }
            if (len >= MAX_INT_LEN) {
                c = s.charAt(0);
                if (!Character.isDigit(c))
                    return false;
                dfirst = c-'0';
                if (len > MAX_INT_LEN || dfirst > MAX_INT_FIRSTDIGIT)
                    return false;
            }
            for (i = len - 1, j = 1; i >= 1; --i, j *= 10) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt += (c-'0')*j;
            }
            if (len < MAX_INT_LEN) {
                c = s.charAt(i);
                if (!Character.isDigit(c))
                    return false;
                rslt += (c-'0')*j;
            }
            if (dfirst >= MAX_INT_FIRSTDIGIT && rslt > MAX_INT_LASTDEC)
                return false;
            rslt += dfirst*j;
        }
        intBox._n=rslt;
        return true;
    }

    // Get the value stored in an IntBoxSimple:
    public int get_n() {
        return _n;
    }
    public int v() { // alternative shorter version, v for "value"
        return _n;
    }
    // Make objects of IntBoxSimple (needed as constructors are not public):
    public static IntBoxSimple makeIntBoxSimple() {
        return new IntBoxSimple();
    }
    public static IntBoxSimple makeIntBoxSimple(int integerNumber) {
        return new IntBoxSimple(integerNumber);
    }

    // constructors are not public(!=:
    protected IntBoxSimple() {} {
        _n=0; // default value an IntBoxSimple holds
    }
    protected IntBoxSimple(int integerNumber) {
        _n=integerNumber;
    }
}

Тест/пример программы для класса IntBoxSimple:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class IntBoxSimpleTest {
    public static void main (String args[]) {
        IntBoxSimple ibs = IntBoxSimple.makeIntBoxSimple();
        String in = null;
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        do {
            System.out.printf(
                    "Enter an integer number in the range %d to %d:%n",
                        Integer.MIN_VALUE, Integer.MAX_VALUE);
            try { in = br.readLine(); } catch (IOException ex) {}
        } while(! IntBoxSimple.tryParse(in, ibs));
        System.out.printf("The number you have entered was: %d%n", ibs.v());
    }
}

Ответ 19

Попробуйте с аргументом параметров регулярного выражения и параметров по умолчанию

public static int parseIntWithDefault(String str, int defaultInt) {
    return str.matches("-?\\d+") ? Integer.parseInt(str) : defaultInt;
}


int testId = parseIntWithDefault("1001", 0);
System.out.print(testId); // 1001

int testId = parseIntWithDefault("test1001", 0);
System.out.print(testId); // 1001

int testId = parseIntWithDefault("-1001", 0);
System.out.print(testId); // -1001

int testId = parseIntWithDefault("test", 0);
System.out.print(testId); // 0

если вы используете apache.commons.lang3, а затем NumberUtils:

int testId = NumberUtils.toInt("test", 0);
System.out.print(testId); // 0

Ответ 20

Я хотел бы добавить другое предложение, которое работает, если кто-то специально запрашивает целые числа: просто используйте long и используйте Long.MIN_VALUE для случаев ошибки. Это похоже на подход, который используется для символов в Reader, где Reader.read() возвращает целое число в диапазоне символа или -1, если читатель пуст.

Для Float и Double NaN можно использовать аналогичным образом.

public static long parseInteger(String s) {
    try {
        return Integer.parseInt(s);
    } catch (NumberFormatException e) {
        return Long.MIN_VALUE;
    }
}


// ...
long l = parseInteger("ABC");
if (l == Long.MIN_VALUE) {
    // ... error
} else {
    int i = (int) l;
}

Ответ 21

Вы не должны использовать исключения для проверки ваших значений.

Для одного символа есть простое решение:

Character.isDigit()

Для более длинных значений лучше использовать некоторые утилиты. NumberUtils, предоставляемые Apache, будут отлично работать здесь:

NumberUtils.isNumber()

Пожалуйста, проверьте https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/math/NumberUtils.html

Ответ 22

Если вы используете Java 8 или выше, вы можете использовать библиотеку, которую я только что выпустил: https://github.com/robtimus/try-parse. Он поддерживает int, long и boolean, который не зависит от перехвата исключений. В отличие от Guava Ints.tryParse, он возвращает OptionalInt/OptionalLong/Optional, очень похожий на fooobar.com/questions/80015/... но более эффективный.

Ответ 23

Вы можете использовать Null-Object следующим образом:

public class Convert {

    @SuppressWarnings({"UnnecessaryBoxing"})
    public static final Integer NULL = new Integer(0);

    public static Integer convert(String integer) {

        try {
            return Integer.valueOf(integer);
        } catch (NumberFormatException e) {
            return NULL;
        }

    }

    public static void main(String[] args) {

        Integer a = convert("123");
        System.out.println("a.equals(123) = " + a.equals(123));
        System.out.println("a == NULL " + (a == NULL));

        Integer b = convert("onetwothree");
        System.out.println("b.equals(123) = " + b.equals(123));
        System.out.println("b == NULL " + (b == NULL));

        Integer c = convert("0");
        System.out.println("equals(0) = " + c.equals(0));
        System.out.println("c == NULL " + (c == NULL));

    }

}

В этом примере результат main:

a.equals(123) = true
a == NULL false
b.equals(123) = false
b == NULL true
c.equals(0) = true
c == NULL false

Таким образом, вы всегда можете проверить неудачное преобразование, но по-прежнему работать с результатами в виде экземпляров Integer. Вы также можете настроить значение NULL, представляющее (≠ 0).