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.