Switch on java.lang.Class

Brian Goetz brian.goetz at oracle.com
Mon Apr 9 13:38:15 UTC 2018


I'm skeptical of this feature, because (a) its not as widely applicable 
as it looks, (b) its error-prone.

Both of these stem from the fact that comparing classes with == excludes 
subtypes.  So it really only works with final classes -- but if we had a 
feature like this, people might mistakenly use it with nonfinal classes, 
and be surprised when a subtype shows up (this can happen even when your 
IDE tells you there are no subtypes, because of dynamic proxies).  And 
all of the examples you show are in low-level libraries, which is a 
warning sign.

Where did these snippets get their Class from?  Good chance, case 1 got 
it from calling Object.getClass().  In which case, they can just pattern 
match on the type of the thing:

     switch (date) {
         case Date d: ...
         case Timestamp t: ...
         default: ...
     }

Case 2 is more likely just operating on types that it got from a 
reflection API.  If you have only a few entries, an if-else will do; if 
you have more entries, a Map is likely to be the better choice.  For 
situations like this, I'd rather invest in map literals or better 
Map.of() builders.

So, I would worry this feature is unlikely to carry its weight, and 
further, may lead to misuse.


On 4/9/2018 1:07 AM, Tagir Valeev wrote:
> Hello!
>
> I don't remember whether switch on java.lang.Class instance was 
> discussed. I guess, this pattern is quite common and it will be useful 
> to support it. Such code often appears in deserialization logic when 
> we branch on desired type to deserialize. Here are a couple of 
> examples from opensource libraries:
>
> 1. com.google.gson.DefaultDateTypeAdapter#read (gson-2.8.2):
>
>     Date date = deserializeToDate(in.nextString());
>     if (dateType == Date.class) {
>       return date;
>     } else if (dateType == Timestamp.class) {
>       return new Timestamp(date.getTime());
>     } else if (dateType == java.sql.Date.class) {
>       return new java.sql.Date(date.getTime());
>     } else {
>       // This must never happen: dateType is guarded in the primary 
> constructor
>       throw new AssertionError();
>     }
>
> Could be rewritten as:
>
>     Date date = deserializeToDate(in.nextString());
>     return switch(dateType) {
>       case Date.class -> date;
>       case Timestamp.class -> new Timestamp(date.getTime());
>       case java.sql.Date.class -> new java.sql.Date(date.getTime());
>       default ->
>         // This must never happen: dateType is guarded in the primary 
> constructor
>         throw new AssertionError();
>     };
>
> 2. 
> com.fasterxml.jackson.databind.deser.std.FromStringDeserializer#findDeserializer 
> (jackson-databind-2.9.4):
>
>     public static Std findDeserializer(Class<?> rawType)
>     {
>         int kind = 0;
>         if (rawType == File.class) {
>             kind = Std.STD_FILE;
>         } else if (rawType == URL.class) {
>             kind = Std.STD_URL;
>         } else if (rawType == URI.class) {
>             kind = Std.STD_URI;
>         } else if (rawType == Class.class) {
>             kind = Std.STD_CLASS;
>         } else if (rawType == JavaType.class) {
>             kind = Std.STD_JAVA_TYPE;
>         } else if // more branches like this
>         } else {
>             return null;
>         }
>         return new Std(rawType, kind);
>     }
>
> Could be rewritten as:
>
>     public static Std findDeserializer(Class<?> rawType)
>     {
>         int kind = switch(rawType) {
>         case File.class -> Std.STD_FILE;
>         case URL.class -> Std.STD_URL;
>         case URI.class -> Std.STD_URI;
>         case Class.cass -> Std.STD_CLASS;
>         case JavaType.class -> Std.STD_JAVA_TYPE;
>         ...
>         default -> 0;
>         };
>         return kind == 0 ? null : new Std(rawType, kind);
>     }
>
> In such code all branches are mutually exclusive. The bootstrap method 
> can generate a lookupswitch based on Class.hashCode, then equals 
> checks, pretty similar to String switch implementation. Unlike String 
> hash codes Class.hashCode is not stable and varies between JVM 
> launches, but they are already known during the bootstrap and we can 
> trust them during the VM lifetime, so we can generate a lookupswitch. 
> The minor problematic point is to support primitive classes like 
> int.class. This cannot be passed directly as indy static argument, but 
> this can be solved with condy.
>
> What do you think?
>
> With best regards,
> Tagir Valeev.
>



More information about the amber-spec-experts mailing list