2

I have a method which essentially handles casting for config types, however upon specifying a generic type (such as List), it becomes a problem of how to handle the specific type. In an ideal world, something such as using a type witness:

List<String> someVal = MyConfig.SOME_VAL.<List<String>>.as(List.class);'

(The full as code):

/**
 * Attempts to return the {@link Config} value as a casted type. If the
 * value cannot be casted it will attempt to return the default value. If
 * the default value is inappropriate for the class, the method will
 * throw a {@link ClassCastException}.
 * 
 * @since 0.1.0
 * @version 0.1.0
 * 
 * @param <T> The type of the casting class
 * @param c The class type to cast to
 * @return A casted value, or {@code null} if unable to cast. If the passed
 *         class parameter is of a primitive type or autoboxed primitive,
 *         then a casted value of -1 is returned, or {@code false} for
 *         booleans. If the passed class parameter is for {@link String},
 *         then {@link Object#toString()} is called on the value instead
 */
default public <T> T as(Class<T> c) {
    Validate.notNull(c, "Cannot cast to null");
    Validate.isTrue(Primitives.unwrap(c) != void.class, "Cannot cast to a void type");
    Object o = this.get();
    if (o == null) {
        T back = Reflections.defaultPrimitiveValue(c);
        if (back != null) { //catch for non-primitive classes
            return back;
        }
    }
    if (c == String.class) {
        return (T) String.valueOf(o);
    }
    if (c.isInstance(o)) {
        return c.cast(o);
    }
    if (c.isInstance(this.getDefault())) {
        return c.cast(this.getDefault());
    }
    throw new ClassCastException("Unable to cast config value");
}

So essentially that leaves me with a two-part question: Why can't type witnesses be used for generics on a class (such as List(raw) -> List<String>), and how can I go about supporting retrieving a class with generic bounding without doing extraneous casting? The first point particularly baffles me, since this is perfectly legal:

List<String> test = new ArrayList<>();
test = MyConfig.FRIENDLY_MOBS.as(test.getClass());

Despite it returning a raw-typed list

Rogue
  • 11,105
  • 5
  • 45
  • 71
  • That example **is not** a raw-typed list. `new ArrayList();` is a raw-type using the [diamond operator `<>`](http://www.javaworld.com/article/2074080/core-java/jdk-7--the-diamond-operator.html) makes it **not** a [raw-type](http://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html). – Elliott Frisch Mar 08 '15 at 19:46
  • 1
    @ElliottFrisch The return from the `#as` method is raw, not the specific list I am setting the raw-typed list to (for which I double-checked). – Rogue Mar 08 '15 at 19:48
  • I think the answer to the first part is [type erasure](http://docs.oracle.com/javase/tutorial/java/generics/erasure.html) and the second is probably [reflection](http://en.wikipedia.org/wiki/Reflection_%28computer_programming%29#Java). – Elliott Frisch Mar 08 '15 at 19:53

2 Answers2

1

That line is really evil (type erasure / raw type) as there is no check whatsoever whether the Collection type really contains strings.

test = MyConfig.FRIENDLY_MOBS.as(test.getClass());

I think the easiest solution is to write an as method that takes the class object of both the collection type as well as the element class. See this following example (in static scope, so you have to adjust it):

static List<String> words = Arrays.asList("Hello", "Bonjour", "engine");

static public <E, Coll extends Collection<E>> Coll as(Class<? extends Collection> collClass, Class<E> elemClass) {
    if (collClass.isInstance(words)) {
        Collection coll = collClass.cast(words);
        for (Object o : coll)
            if (!elemClass.isInstance(o))
                throw new ClassCastException("BAM");
        return (Coll)coll;
    }

    return null;
}

Now the following behaviour is found:

final List<String> list = as(List.class, String.class); // works
final List<String> list = as(List.class, Integer.class); // compile error: type check
final List<Integer> list = as(List.class, String.class); // compile error: type check
final List<Integer> list = as(List.class, Integer.class); // ClassCastException

As for other attempts: iirc Jackson had some magic TypeToken stuff going on that allowed to capture types such as List<String>. It somehow abused Enum<T> I think...

uberwach
  • 1,119
  • 7
  • 14
  • Hm on a second look, seems that my own implementation in this form doesn't have the compile-time error like your example, though I'm not really seeing a major difference here: http://pastie.org/pastes/10010510/text – Rogue Mar 08 '15 at 21:14
  • It's different and you *want* the compile-time errors to happen rather than having invalid programs pass the compiler and lead to trouble somewhere... It is something you will learn the hard way otherwise. – uberwach Mar 09 '15 at 07:59
  • that's my point, I want the compile time errors and I'm not getting them with that implementation :( – Rogue Mar 09 '15 at 17:19
  • because you changed stuff in your implementation... sigh – uberwach Mar 10 '15 at 12:52
  • Right, [however using either version of code doesn't work](http://pastebin.com/raw.php?i=1Y0hUE9A). They work when tested alone as a static method (using some random List), but not in the Config class itself. – Rogue Mar 10 '15 at 18:36
  • Just as a point of reference, you can find [my entire Config class on Github](https://github.com/CodeLanx/CodelanxLib/blob/master/src/main/java/com/codelanx/codelanxlib/config/Config.java#L101) – Rogue Mar 10 '15 at 21:16
1

Your idea of type witnesses is indeed the way to go, but you need a better type witness that does not only capture the raw type (here List) but also its generic parameters. This is not easy in Java because in most places the generic parameters are not available at runtime due to type erasure. Java's reflection API uses interfaces that are subinterfaces of Type as a runtime representation of generic types, but these are unsuited for your purpose because they do not provide any compile-time type information.

However, with a trick it is possible to achieve what you want. The trick is based on the fact that if a class (example: MyClass) inherits from a generic type (example: List<String>), there is no type erasure. You can at runtime retrieve the information that MyClass inherits from List<String> (using the method Class.getGenericSuperclass()).

Subclassing the actual type we want to pass would however be very inflexible (for example this would not work for final classes). Thus we create a special class (often called TypeToken) from which we can inherit. The TypeToken class has a generic parameter, and in the subclass we specify the type we want to pass as this parameter. Of course creating a special class for every different value you want to pass would usually be quite cumbersome, but fortunately we can use anonymous classes to make this easy.

Putting it all together, a solution could look like the following.

Definition of our type token:

public abstract class TypeToken<T> {}

Definition of method as:

public <T> T as(TypeToken<T> typeToken) {
  Type targetType = typeToken.getClass().getGenericSuperclass();
  // use targetType here, e.g.
  if (targetType instanceof ParameterizedType) { ... }

And calling it:

 List<Integer> list = MyConfig.SOME_VAL.as(new TypeToken<List<String>>() {});

Notice the {} that declares the anonymous subclass and avoids the type erasure.

Even better would be to use an existing class as the type token, for example the class TypeToken of the great Guava library (if you do not know this library yet, also look at what else it offers and consider using it!). This class also provides additional helper methods that make it easer to use the token in the as method (directly using Type instances can be difficult). The Guava wiki has more information on its TypeToken class.

If you are worried about creating too many classes, you can of course easily provide a few default instances for common cases like TypeToken<List<String>> etc. Guava's TypeToken also has an of(Class<T>) method that can be used for non-generic types, so the subclasses would be restricted to cases where it is actually necesary.

Other projects also use this trick, for example Guice with class TypeLiteral (explanation), Gson (TypeToken), and Jackson (TypeReference). So I would not worry too much about the amount of subclasses, given that they do not clutter your source code.

Philipp Wendler
  • 11,184
  • 7
  • 52
  • 87
  • Declaring a new anonymous class for every config retrieval seems a bit... extreme. Is there a way that I can adapt @uberwach's solution in some way? I can easily have it work just setting it up in a testing class (with a directly call from `main`, however it doesn't work the same when implemented into `Config` – Rogue Mar 16 '15 at 14:49
  • 1
    I wouldn't worry too much about the anonymous classes (also see my edit). That is the price you pay if you want to not only allow a certain subset of possible hard-coded types but an arbitrary type. If @uberwach's solution works in a simple case but not in your project, please provide a minimal example where it does not work (possibly as a new question). – Philipp Wendler Mar 16 '15 at 16:34
  • interesting, it seems that I wasn't able to use @uberwach's solution because I used `Class` in the parameter vs. `Class extends Collection>`, which seems odd that that would cause the issue of it not causing a compile-time error for the second type (seems like it'd be the opposite). I rewarded you the bounty (as it lead to the end solution), though I'd be interested to know if you know the finer reasons as to why that distinction exists. – Rogue Mar 18 '15 at 15:29