关于java:封装Integer.parseInt()的好方法

Good way to encapsulate Integer.parseInt()

我有一个项目,我们经常使用Integer.parseInt()将字符串转换为int。当出现错误时(例如,String不是数字,而是字母a或其他),这个方法会抛出异常。但是,如果我必须在我的代码中处理任何地方的异常,那么这看起来很难看。我想把它放到一个方法中,但是,我不知道如何返回一个干净的值来显示转换出错了。

在C++中,我可以创建一个方法,它接受一个int的指针,并让方法本身返回true或false。然而,据我所知,这在Java中是不可能的。我还可以创建一个包含真/假变量和转换值的对象,但这看起来也不理想。全局值也是一样的,这可能会给多线程带来一些麻烦。

那么有没有一个干净的方法来做到这一点?


您可以返回Integer,而不是int,在解析失败时返回null

很遗憾,Java没有提供这样的方法,但是内部没有抛出一个异常——可以隐藏异常(通过捕获并返回null),但是如果解析成千上万个用户提供的数据位,它仍然可能是一个性能问题。

编辑:此类方法的代码:

1
2
3
4
5
6
7
public static Integer tryParse(String text) {
  try {
    return Integer.parseInt(text);
  } catch (NumberFormatException e) {
    return null;
  }
}

请注意,如果text为空,我不确定这会有什么效果。您应该考虑-如果它代表一个bug(即,您的代码可能会传递一个无效的值,但不应该传递空值),那么抛出一个异常是适当的;如果它不代表一个bug,那么您可能只需返回空值,就像其他任何无效值一样。

最初这个答案使用了new Integer(String)构造函数;现在它使用Integer.parseInt和装箱操作;这样,小值最终会被装箱到缓存的Integer对象中,从而在这些情况下更高效。


当它不是一个数字时,你期望它有什么样的行为?

例如,如果在输入不是数字的情况下经常使用默认值,那么这样的方法可能很有用:

1
2
3
4
5
6
7
public static int parseWithDefault(String number, int defaultVal) {
  try {
    return Integer.parseInt(number);
  } catch (NumberFormatException e) {
    return defaultVal;
  }
}

当无法解析输入时,可以为不同的默认行为编写类似的方法。


在某些情况下,您应该将解析错误作为快速失败的情况来处理,但在其他情况下,例如应用程序配置,我更喜欢使用ApacheCommonsLang3NumberUtils使用默认值来处理丢失的输入。

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


为避免处理异常,请使用正则表达式确保所有数字都在第一位:

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


瓜瓦岛有埃多克斯1〔4〕。它不会在非数字字符串上引发异常,但会在空字符串上引发异常。


也许你可以用这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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;
}

}


在阅读了问题的答案后,我认为封装或包装parseint方法是不必要的,甚至可能不是一个好主意。

正如jon建议的那样,您可以返回"null",但这或多或少会将try/catch构造替换为空检查。如果您"忘记"错误处理,那么在行为上就有一点不同:如果您没有捕获异常,就没有赋值,并且左侧变量保持它的旧值。如果不测试空值,可能会受到JVM(NPE)的攻击。

雅文的建议在我看来更优雅,因为我不喜欢返回空值来表示某些错误或异常状态。现在,您必须使用一个预先定义的对象来检查引用相等性,这表示存在问题。但是,正如其他人所说,如果您再次"忘记"检查并且字符串是不可分析的,那么程序将继续在您的"错误"或"空"对象中使用包装的int。

Nikolay的解决方案更加面向对象,也将与其他包装类的parsexxx方法一起使用。但最后,他用OperationNotSupported异常替换了NumberFormatException——同样,您需要一个Try/Catch来处理不可分析的输入。

所以,我的结论是不封装普通的parseint方法。如果我也能添加一些(依赖于应用程序的)错误处理,我就只封装它。


您还可以复制非常简单的C++行为。

1
2
3
4
5
6
7
8
9
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;
    }
}

您将使用如下方法:

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

在那之后,变量result如果一切正常,并且byRef[0]包含解析的值,则为真。

就我个人而言,我会坚持抓住例外。


我的Java有点生疏了,但是让我看看我是否能给你指出正确的方向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


如何分叉parseint方法?

很简单,只需复制粘贴内容到返回IntegerOptional的新实用程序,并用返回替换throw即可。似乎底层代码中没有异常,但最好检查一下。

通过跳过整个异常处理过程,您可以在无效输入上节省一些时间。这个方法自JDK1.0以来就存在,所以您不太可能需要做很多事情来保持它的最新。


乔恩·斯基特给出的答案很好,但我不喜欢给出一个null整数对象。我觉得这个很难用。由于Java 8有一个更好的选择(在我看来),使用EDCOX1的6个词:

1
2
3
4
5
6
7
public static OptionalInt tryParse(String value) {
 try {
     return OptionalInt.of(Integer.parseInt(value));
  } catch (NumberFormatException e) {
     return OptionalInt.empty();
  }
}

这使得您必须明确地处理没有可用值的情况。我希望这种函数将来会被添加到Java库中,但我不知道这是否会发生。


如果您使用Java 8或UP,您可以使用我刚刚发布的库:HTTPS://GITHUBCOM/ROBTIMU/TY-PARSE。它支持int、long和boolean,不依赖于捕获异常。与guava的ints.typarse不同,它返回optionalInt/optionalLong/optional,与https://stackoverflow.com/a/38451745/1180351非常相似,但效率更高。


我建议你考虑一下

1
 IntegerUtilities.isValidInteger(String s)

然后根据需要实施。如果您希望返回结果(可能是因为您使用了integer.parseInt()),那么可以使用数组技巧。

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

将结果[0]设置为进程中找到的整数值。


这与尼古拉的解决方案有些相似:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 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());
 }

主方法演示代码。另一种实现解析器接口的方法显然是从构造中设置"d+",并且让这些方法不做任何事情。


您可以自己滚动,但使用commons-lang的StringUtils.isNumeric()方法也很容易。它使用character.isdigit()迭代字符串中的每个字符。


为了避免异常,可以使用Java的EDCOX1引用1方法。下面的代码基本上是ApacheCommon的integervalidator类的简化版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}

根据您的喜好,您可以使用AtomicIntegerint[]数组技巧。

这是我用的测试-

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

我也有同样的问题。这是我编写的一个方法,它要求用户输入,除非输入是整数,否则不接受输入。请注意,我是一个初学者,所以如果代码没有按预期工作,请怪我的经验不足!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
}


这是对问题8391979的回答,"Java是否有一个不为坏数据抛出异常的T.TyPARSE?[重复]"已关闭并链接到此问题。

编辑2016 08 17:添加了ltrimzeroes方法并在triparse()中调用它们。如果没有数字字符串中的前导零,可能会产生错误的结果(请参见代码中的注释)。现在也有公共静态字符串ltrimzeroes(string numberstring)方法,它适用于正负"数字"(end edit)。

在下面,您可以找到一个用于INT的基本包装器(装箱)类,它具有一个高度优化的TyPARSESER()方法(类似于C语言),它解析字符串本身,并且比Java:PARSETINT(String S)稍微快一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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的测试/示例程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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());
    }
}

尝试使用正则表达式和默认参数参数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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:

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


如果有人特别要求整数,我想提出另一个可行的建议:简单地使用long,对于错误情况使用long.min_值。这类似于用于读卡器中字符的方法,其中reader.read()返回一个字符范围内的整数,如果读卡器为空,则返回-1。

对于float和double,NaN也可以以类似的方式使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

您不应该使用异常来验证您的值。

对于单个字符,有一个简单的解决方案:

1
Character.isDigit()

对于较长的值,最好使用一些实用程序。Apache提供的numberUtils在这里非常有效:

1
NumberUtils.isNumber()

请查看https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/math/numberutils.html


我处理这个问题的方法是递归的。例如,从控制台读取数据时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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("
There was an error try again.
"
);
        ret = GetMyInt();
    }
    return ret;
}

您可以使用这样的空对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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的结果是:

1
2
3
4
5
6
a.equals(123) = true
a == NULL false
b.equals(123) = false
b == NULL true
c.equals(0) = true
c == NULL false

通过这种方式,您可以始终测试转换是否失败,但仍将结果作为整数实例使用。您可能还需要调整数字null表示(≠0)。