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.


Reply via email to