使用字符串输入解析自定义对象

Parse custom Objects with string input

我想解析我有输入值的对象。该对象有子类。

我不想在要解析字符串时创建实例。 但是我想覆盖方法 'parse'.

class A {
  public A(object param) {
   //code
  }
  public A parse(String input) {
   //code
  }
}

class B extends A {
  public B parse(String input) {
   //code
  }
}

当我解析我想要的对象时,用反射来做:

A newObject =  Class.forName(className).getMethod("parse", myParseText);

有什么好的方法吗

为了获得最大的灵活性,我将解析代码移出到一个单独的解析器中 class。例如下面的代码是我自己在一个项目中使用的解析器 class 的简化实现:

public final class ParseUtil {

    public interface IStringParser<E> {
        public E parse(String s) throws ParseException;
    }

    private ParseUtil() {}

    private static Map<Class<?>, IStringParser<?>> STRING_PARSERS = new ConcurrentHashMap<>();

    public static <E> void registerStringParser(Class<E> c, IStringParser<E> p){
        STRING_PARSERS.put(c, p);
    }

    @SuppressWarnings("unchecked")
    public static <E> IStringParser<E> getStringParser(Class<E> c){
        return (IStringParser<E>) STRING_PARSERS.get(c);
    }

    public static <E> E parse(String s, Class<E> clazz) throws ParseException{
        if (s == null || s.length() == 0 || clazz == null) {
            throw new IllegalArgumentException();
        }

        IStringParser<E> stringParser = getStringParser(clazz);
        if (stringParser == null) {
            throw new ParseException(clazz);
        }
        return stringParser.parse(s);
    }
}

然后可以将任意 classes 的 IStringParser 实现注册到 ParserUtil,如下所示:

ParseUtil.registerStringParser(File.class, new IStringParser<File>() {
    @Override
    public File parse(String s)throws ParseException {
        return new File(s);
    }
});

或使用 lambda:ParseUtil.registerStringParser(File.class, s -> new File(s));

调用者有责任决定如何解析 class 以及 IStringParser 何时为 registered/unregistered。

通过将解析代码从 class 本身移开,稍后只需注册一个不同的 IStringParser 或更改 parse 方法即可轻松更改实现ParseUtilclass。例如在我自己的项目中,我使用以下解析方法,它为众所周知的 Java classes 提供了一些合理的默认值,并使用 Gson 解析没有其他 IStringParser 的对象已注册:

public static <E> E parse(String s, Class<E> clazz) throws ParseException{
    if (s == null || s.length() == 0 || clazz == null) {
        throw new IllegalArgumentException();
    }
    IStringParser<E> stringParser = getStringParser(clazz);
    if (stringParser != null) {
        return stringParser.parse(s);
    }
    if (Number.class.isAssignableFrom(clazz)) {
        // simple number
        try {
            if (clazz == Integer.class) {
                return clazz.cast(Integer.parseInt(s));
            }
            if (clazz == Long.class) {
                return clazz.cast(Long.parseLong(s));
            } 
            if (clazz == Double.class) {
                return clazz.cast(Double.parseDouble(s));
            } 
            if (clazz == Float.class) {
                return clazz.cast(Float.parseFloat(s));
            }
            if (clazz == Short.class) {
                return clazz.cast(Short.parseShort(s));
            }
            if (clazz == Byte.class) {
                return clazz.cast(Byte.parseByte(s));
            }
        } catch (NumberFormatException e) {
            throw new ParseException(clazz, e);
        }
    } 
    if (clazz == String.class) {
        return clazz.cast(s);
    }
    if (clazz == Character.class) {
        if (s.length() == 1) {
            return clazz.cast(s.charAt(0));
        } else{
            throw new ParseException("Unable to parse Character \"" + s + "\"");
        }
    }
    if (clazz == Boolean.class) {
        switch (s) {
        case "true":
            return clazz.cast(Boolean.TRUE);
        case "false":
            return clazz.cast(Boolean.FALSE);
        case "1":
            return clazz.cast(Boolean.TRUE);
        case "0":
            return clazz.cast(Boolean.FALSE);
        default:
            throw new ParseException("Unable to parse boolean \"" + s + "\"");
        }
    }
    if (clazz == Class.class) {
        try {
            return clazz.cast(Class.forName(s));
        } catch (ClassNotFoundException e) {
            throw new ParseException(clazz, e);
        }
    }
    if (Enum.class.isAssignableFrom(clazz)) {
        @SuppressWarnings({ "rawtypes" })
        Class c = (Class)clazz;
        @SuppressWarnings("unchecked")
        Object o = Enum.valueOf(c, s);
        return clazz.cast(o);
    }
    E result = null;
    try {
        // if all else fails use Gson to parse the class
        result = getGSON().fromJson(s, clazz);
    } catch (JsonSyntaxException e) {
        throw new ParseException(clazz. e);
    }

    if (result == null) {
        throw new ParseException(clazz);
    }
    return result;
}