Switch on java.lang.Class
Tagir Valeev
amaembo at gmail.com
Tue Apr 10 05:12:59 UTC 2018
Hello!
Does not sound convincing. First, to my experience, it's quite widely
applicable. My first two samples were from what you called a low-level
libraries just because I wanted to grep something well-known. Now I grepped
jdk10 source by `\w+ == \w+\.class` and scanned manually about 10% of the
results and found about 10 places where it's useful (some examples are
shown below). So extrapolating I may assume that this construct can be
applied roughly 100 times in JDK (note that my regexp does not cover
xyz.equals(foo.class) and some developers prefer this style; also different
spacing is not covered). You may surely call the JDK code as "low-level
libraries", but grepping IntelliJ IDEA source code I also see significant
amount of occurrences. Though I don't see why usefulness of the feature in
a low-level libraries should be the warning sign.
In any case I'm pretty sure that switch on class will be more applicable,
than the switch on floats. But you are doing the switch on floats. Why? For
consistency, of course. You want to support all literals in switch. But
class literals are also literals, according to JLS 15.8.2, so it is
inconsistent not to support them (especially taking into account that their
usefulness is not the lowest of all possible literals). Another comparison:
all literals (including class literals) and enum values are acceptable as
annotation values. The same in switch expressions, but excluding the class
literals, which is inconsistent.
I don't buy an error-prone argument either. Is `switch(doubleValue) {case
Math.PI: ...}` error-prone? Why somebody cannot assume that the comparison
should tolerate some delta difference between doubleValue and Math.PI?
Somebody surely can, but that's silly. We know that the switch checks for
equality, it was always so. It will be so for classes as well, and assuming
something different is inconsistent. After all, writing
foo.equals(Bar.class) or foo == Bar.class is allowed in the language,
people use these constructions, and often it's the right thing to do. Of
course their code becomes erroneous sometimes, because in this particular
place the inheritance should be taken into account. But the same is true
for doubleValue == Math.PI comparison: sometimes it's ok, sometimes it's
wrong and some tolerance interval should be checked instead. And when it's
ok, you add a new option to use switch on doubles.
Several code samples found in JDK:
1. javafx.base/javafx/util/converter/LocalDateTimeStringConverter.java:197
(final classes)
if (type == LocalDate.class) {
return (T)LocalDate.from(chronology.date(temporal));
} else if (type == LocalTime.class) {
return (T)LocalTime.from(temporal);
} else {
return (T)LocalDateTime.from(chronology.localDateTime(temporal));
}
2. java.desktop/sun/print/Win32PrintService.java:928 (final classes)
if (category == ColorSupported.class) {
int caps = getPrinterCapabilities();
if ((caps & DEVCAP_COLOR) != 0) {
return (T)ColorSupported.SUPPORTED;
} else {
return (T)ColorSupported.NOT_SUPPORTED;
}
} else if (category == PrinterName.class) {
return (T)getPrinterName();
} else if (category == PrinterState.class) {
return (T)getPrinterState();
} else if (category == PrinterStateReasons.class) {
return (T)getPrinterStateReasons();
} else if (category == QueuedJobCount.class) {
return (T)getQueuedJobCount();
} else if (category == PrinterIsAcceptingJobs.class) {
return (T)getPrinterIsAcceptingJobs();
} else {
return null;
}
3. com.sun.media.sound.SoftSynthesizer#getPropertyInfo (line 926) - final
classes; several blocks like this
if (c == Byte.class)
item2.value = Byte.valueOf(s);
else if (c == Short.class)
item2.value = Short.valueOf(s);
else if (c == Integer.class)
item2.value = Integer.valueOf(s);
else if (c == Long.class)
item2.value = Long.valueOf(s);
else if (c == Float.class)
item2.value = Float.valueOf(s);
else if (c == Double.class)
item2.value = Double.valueOf(s);
4. java.awt.Component#getListeners (interfaces!), Window#getListeners,
List#getListeners, JComponent#getListeners, etc. are similar
public <T extends EventListener> T[] getListeners(Class<T>
listenerType) {
EventListener l = null;
if (listenerType == ComponentListener.class) {
l = componentListener;
} else if (listenerType == FocusListener.class) {
l = focusListener;
} else if (listenerType == HierarchyListener.class) {
l = hierarchyListener;
} else if (listenerType == HierarchyBoundsListener.class) {
l = hierarchyBoundsListener;
} else if (listenerType == KeyListener.class) {
l = keyListener;
} else if (listenerType == MouseListener.class) {
l = mouseListener;
} else if (listenerType == MouseMotionListener.class) {
l = mouseMotionListener;
} else if (listenerType == MouseWheelListener.class) {
l = mouseWheelListener;
} else if (listenerType == InputMethodListener.class) {
l = inputMethodListener;
} else if (listenerType == PropertyChangeListener.class) {
return (T[])getPropertyChangeListeners();
}
return AWTEventMulticaster.getListeners(l, listenerType);
}
5. java.beans.XMLEncoder#primitiveTypeFor (final classes)
if (wrapper == Boolean.class) return Boolean.TYPE;
if (wrapper == Byte.class) return Byte.TYPE;
if (wrapper == Character.class) return Character.TYPE;
if (wrapper == Short.class) return Short.TYPE;
if (wrapper == Integer.class) return Integer.TYPE;
if (wrapper == Long.class) return Long.TYPE;
if (wrapper == Float.class) return Float.TYPE;
if (wrapper == Double.class) return Double.TYPE;
if (wrapper == Void.class) return Void.TYPE;
return null;
6. javax.swing.plaf.synth.SynthTableUI.SynthTableCellRenderer#configureValue
(mix of abstract, non-final and final classes)
private void configureValue(Object value, Class<?> columnClass) {
if (columnClass == Object.class || columnClass == null) { //
case Object.class, null!
setHorizontalAlignment(JLabel.LEADING);
} else if (columnClass == Float.class || columnClass ==
Double.class) {
if (numberFormat == null) {
numberFormat = NumberFormat.getInstance();
}
setHorizontalAlignment(JLabel.TRAILING);
setText((value == null) ? "" :
((NumberFormat)numberFormat).format(value));
}
else if (columnClass == Number.class) {
setHorizontalAlignment(JLabel.TRAILING);
// Super will have set value.
}
else if (columnClass == Icon.class || columnClass ==
ImageIcon.class) {
setHorizontalAlignment(JLabel.CENTER);
setIcon((value instanceof Icon) ? (Icon)value : null);
setText("");
}
else if (columnClass == Date.class) {
if (dateFormat == null) {
dateFormat = DateFormat.getDateInstance();
}
setHorizontalAlignment(JLabel.LEADING);
setText((value == null) ? "" :
((Format)dateFormat).format(value));
}
else {
configureValue(value, columnClass.getSuperclass()); // note
this: recursively going to superclass automatically
}
}
With best regards,
Tagir Valeev.
On Mon, Apr 9, 2018 at 8:38 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> 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.
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/amber-spec-experts/attachments/20180410/af2e72f3/attachment-0001.html>
More information about the amber-spec-experts
mailing list