Hi,
I have an enhancement proposal for java.lang.Class.methodToString and
java.lang.Class.getTypeName.
First one is used when NoSuchMethodException is thrown from Class::getMethod
which is in turn widely used in Spring Framework and often throws.
In current implementation we have 2 major problems:
- we create stream for the case when argTypes is not null but empty (which
happens e. g. when Class::getMethod is called without vararg and throws)
- we have torn StringBuilder::append chain
I’ve modified the method to skip creation of Stream for empty array and used
separate StringBuilder for each case. Latter allowed to rid SB completely and
use invokedynamic-based concatenation.
I’ve compared both approaches for 2 cases:
1) argTypes is empty
2) argTypes.length == 1
Benchmark Mode Cnt Score
Error Units
methodToString_noArgs avgt 25 170,986
± 5,708 ns/op
methodToString_noArgs_patched avgt 25 26,883
± 2,906 ns/op
methodToString_1arg avgt 25 183,012
± 0,701 ns/op
methodToString_1arg_patched avgt 25 112,784
± 0,920 ns/op
methodToString_noArgs:·gc.alloc.rate.norm avgt 25 881,600
± 9,786 B/op
methodToString_noArgs_patched:·gc.alloc.rate.norm avgt 25 128,000
± 0,001 B/op
methodToString_1arg:·gc.alloc.rate.norm avgt 25 960,000
± 0,001 B/op
methodToString_1arg_patched:·gc.alloc.rate.norm avgt 25 552,000
± 0,001 B/op
We have the same problem regarding misusage of StringBuilder in Class::
getTypeName:
StringBuilder sb = new StringBuilder();
sb.append(cl.getName());
for (int i = 0; i < dimensions; i++) {
sb.append("[]");
}
return sb.toString();
I suggest to use String::repeat instead of the loop: this again allows to get
rid of StringBuilder and replace mentioned code with
return cl.getName() + "[]".repeat(dimensions);
Here are benchmark results executed for type Object[].class:
Mode Cnt Score Error Units
getTypeName_patched avgt 25 16,037 ± 0,431 ns/op
getTypeName_patched:·gc.alloc.rate.norm avgt 25 64,000 ± 0,001 B/op
getTypeName avgt 25 34,274 ± 1,432 ns/op
getTypeName:·gc.alloc.rate.norm avgt 25 152,000 ± 0,001 B/op
Regards,
Sergei Tsypanov
diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java
--- a/src/java.base/share/classes/java/lang/Class.java
+++ b/src/java.base/share/classes/java/lang/Class.java
@@ -276,8 +276,7 @@
collect(Collectors.joining(",", "<", ">")));
}
- for (int i = 0; i < arrayDepth; i++)
- sb.append("[]");
+ sb.append("[]".repeat(arrayDepth));
return sb.toString();
}
@@ -1588,12 +1587,7 @@
dimensions++;
cl = cl.getComponentType();
} while (cl.isArray());
- StringBuilder sb = new StringBuilder();
- sb.append(cl.getName());
- for (int i = 0; i < dimensions; i++) {
- sb.append("[]");
- }
- return sb.toString();
+ return cl.getName() + "[]".repeat(dimensions);
} catch (Throwable e) { /*FALLTHRU*/ }
}
return getName();
@@ -3417,14 +3411,15 @@
* Helper method to get the method name from arguments.
*/
private String methodToString(String name, Class<?>[] argTypes) {
- StringBuilder sb = new StringBuilder();
- sb.append(getName() + "." + name + "(");
- if (argTypes != null) {
- sb.append(Stream.of(argTypes).map(c -> {return (c == null) ? "null" : c.getName();}).
- collect(Collectors.joining(",")));
+ if (argTypes == null || argTypes.length == 0) {
+ return getName() + '.' + name + "()";
}
- sb.append(")");
- return sb.toString();
+ return getName() + '.' + name
+ + '('
+ + Arrays.stream(argTypes)
+ .map(c -> c == null ? "null" : c.getName())
+ .collect(Collectors.joining(","))
+ + ')';
}
/** use serialVersionUID from JDK 1.1 for interoperability */