Sorry for the title :-/
I wrote a parser for a calculator. It works, but I don't like my token hierarchy. The parser should be unaware of the concrete number type, e.g. it should be configurable for Double, BigDecimal etc. So I have a generic token interface
public interface Token<T> { }
public class NumToken<T> {
private final value T;
...
}
public class OperatorToken<T> {
...
}
Now my question is how to handle "structural" Tokens that are the same regardless of the number type, e.g. parentheses and separators. The generic parameter is not only useless in that case, it prevents me from using an enum for that type. However, an enum would be very useful, e.g. it can be used in switch statements.
Right now I have "solved" the problem that way:
public enum CharToken implements Token<Object> {
OPEN('('),
CLOSE(')'),
SEPARATOR(','),
EOL(';');
private final char ch;
private CharToken(char ch) {
this.ch = ch;
}
}
This works, but forces me to write everywhere int the parser things like List<Token<? super T>>
, and to assume that all other types are actually Token<T>
when I cast. I'm clearly using the开发者_运维百科 type system against the grain here: From a type-theoretical point of view I'm hitting the wall because Java has no "bottom" type like "Nothing" in Scala (which would be a perfect fit). Is there a better solution?
[Clarification]
T is the expected number type my parser should work on, not the "content" of the Token. E.g. for OperatorToken<T>
I have a method calc(T op1, T op1)
. And I want to be able to parametrize the whole parser by T, so all Tokens used by it need the same T (well, if you don't cheat with super
as I did), and hence CharToken implements Token<Character>
would be not very useful.
A solution used in the JDK (in e.g. java.util.Collections.emptyList()
) is exploiting erasure - the type parameters don't really exist, as you might recall - and type parameter inference for methods:
@SuppressWarnings("unchecked")
public enum CharToken implements Token { // Note: No type parameter specified!
OPEN('('),
CLOSE(')'),
SEPARATOR(','),
EOL(';');
public static <T> Token<T> open(){ return OPEN; }
public static <T> Token<T> close(){ return CLOSE; }
public static <T> Token<T> separator(){ return SEPARATOR; }
public static <T> Token<T> eol(){ return EOL; }
private final char ch;
private CharToken(char ch) {
this.ch = ch;
}
}
// Usage:
Token<BigDecimal> bdOpen = CharToken.open();
Token<Integer> intOpen = CharToken.open();
Not very pretty looking at, but at least it's easy to use. (And for cleanliness' sake, you might want to encapsulate the enum in a class which only exposes the generic methods.)
There is a Void
type in Java, devised for similar uses. However, IMO the type parameter should rather be Character
, as Bozho suggests.
精彩评论