问题
I've written the following code to determine the data-type of input entered by the user.
Update: Removed parsing into Float
since a Double
value can also be parsed into Float
at cost of some precision as mentioned by @DodgyCodeException
import java.util.Scanner;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Main {
public static void main(String[] args) {
Scanner src = new Scanner(System.in);
String input;
System.out.println("Enter input:");
input = src.nextLine();
System.out.println("You entered:" + getDataType(input));
}
private static String getDataType(Object input) {
try {
JSONObject obj = new JSONObject((String) input);
return "JSONObject";
} catch (JSONException ex) {
try {
JSONArray array = new JSONArray(input);
return "JSONArray";
} catch (JSONException ex0) {
try {
Integer inti = Integer.parseInt((String) input);
return "Integer";
} catch (NumberFormatException ex1) {
try {
Double dub = Double.parseDouble((String) input);
return "Double";
} catch (NumberFormatException ex3) {
return "String";
}
}
}
}
}
}
}
I've to run this repeatedly hundreds of time and I've read catching Exception
is an expensive operation.
Is there any better way to achieve this?
回答1:
My approach would be to let the framework do its thing, and use it to parse the input a generic way:
Object obj = new org.json.JSONTokener(input).nextValue();
if (obj instanceof JSONArray)
return "JSONArray";
if (obj instanceof JSONObject)
return "JSONObject";
if (obj instanceof Integer)
return "Integer"
...
Perhaps use a switch
statement or a Map
instead of a long list of if
constructs. And catch JSONException
for input that is not valid JSON.
Could perhaps be simplified to return obj.getClass()
or obj.getClass().getSimpleName()
for almost identical functionality to what you have now.
回答2:
Honestly, I don't like the idea of determining the type by parser's exceptions. I would build a pattern for each format and iterate over all of them to check if the given string matches one of them.
It's similar how the Scanner
class works, but it's quite complicated. It's difficult to write the regular expressions which should cover all the possible cases.
But if the regexes are prepared, the algorithm is straightforward:
public class Main {
private final Map<String, String> patterns = Map.of(
"Integer", integerPattern()
);
public String getDataType(String input) {
for (Map.Entry<String, String> entry : patterns.entrySet()) {
if (Pattern.matches(entry.getValue(), input)) {
return entry.getKey();
}
}
return "Unknown";
}
}
Here is an interesting part - an example of integerPattern()
. I have taken it from Scanner
and simplified it a bit:
private String integerPattern() {
String radixDigits = "0123456789abcdefghijklmnopqrstuvwxyz".substring(0, 10);
String digit = "((?i)[" + radixDigits + "]|\\p{javaDigit})";
String groupedNumeral = "(" + "[\\p{javaDigit}&&[^0]]" + digit + "?" + digit + "?(" +
"\\," + digit + digit + digit + ")+)";
String numeral = "((" + digit + "++)|" + groupedNumeral + ")";
String javaStyleInteger = "([-+]?(" + numeral + "))";
String negativeInteger = "\\-" + numeral + "";
String positiveInteger = "" + numeral + "";
return "(" + javaStyleInteger + ")|(" +
positiveInteger + ")|(" +
negativeInteger + ")";
}
I am pretty sure you can google all the regexes or find them in here.
But what if there is a "lighter" solution or you simply don't want to use a regex approach?
You can mix up different techniques by introducing a Map<String, Predicate<String>>
:
class Main {
private final Map<String, Predicate<String>> predicates = Map.of(
"Integer", this::isInteger,
"JSON", this::isJSON
);
public String getDataType(String input) {
for (Map.Entry<String, Predicate<String>> predicate : predicates.entrySet()) {
if (predicate.getValue().test(input)) {
return predicate.getKey();
}
}
return "Unknown";
}
private boolean isJSON(String input) {
return LibraryClass.isJSON(input);
}
private boolean isInteger(String input) {
try {
Integer.valueOf(input);
} catch (NumberFormatException e) {
return false;
}
return true;
}
}
I believe it's more readable, it decreases the level of nesting and encapsulates different logic in separate methods.
回答3:
The long and short of it is:
- There's no sensible way to encapsulate this exact functionality while not catching those exceptions.
- If you're talking about running that method "hundreds" of times, it really shouldn't be a performance bottleneck. If it is, then benchmark it, work out where the bottlenecks are in that method, and then address those specifically.
For completion though, there's similar things you can accomplish without catching those exceptions - though please bear in mind I've done no benchmarks, and these results may or may not be faster in your case!
For working out if something's JSON, there's a mayBeJSON() method in JSON-lib, but it's a rather outdated library (last updated 2010.)
If you want to know whether something will fit in an int specifically, then there's no other sensible way than catching the NFE off parseInt()
really. However, if you just want to know whether something is a number, you could just use a regex:
str.matches("\\d+"); //Returns true if it's a whole positive number
str.matches("-?\\d+"); //Returns true if it's a whole number
(Other regexes are easily calculated / found online for decimal numbers, numbers with exponents, etc.)
Or you could stream it, and check each character individually:
str.chars().allMatch(c->Character.isDigit(c));
Alternatively, you may wish to prepend or replace your exception checks with NumberUtils.isCreateable() from Apache Commons (isParseable()
, and other methods on that class may also be of use.)
回答4:
I want to know whether input is a json/jsonarray or not.
You need to handle the JSONException
to determine the JSON type and then you can use NumberFormat parse() method to determine the other types as shown below:
private static String getDataType(String input) {
try {
return getJsonObjectType(input);
} catch (JSONException ex1) {
try {
return getJsonArrayType(input);
} catch (JSONException ex2) {
return getNonJsonType(input);
}
}
}
private static String getJsonArrayType(String input) throws JSONException {
new JSONArray(input);
return "JSONArray";
}
private static String getJsonObjectType(String input) throws JSONException {
new JSONObject((String) input);
return "JSONObject";
}
private static String getNonJsonType(String input) {
try {
return getNumberType(input);
} catch(ParseException ex3) {
return "String";
}
}
private static String getNumberType(String input) throws ParseException {
Number number = NumberFormat.getInstance().parse(input);
if(number instanceof Long) {
if(number.longValue() < Integer.MAX_VALUE) {
return "int";
} else {
return "long";
}
} else {
if(number.doubleValue() < Float.MAX_VALUE) {
return "float";
} else {
return "double";
}
}
}
来源:https://stackoverflow.com/questions/48683126/determine-user-input-data-type-dynamically-in-java