Skip to content

Commit daea6f1

Browse files
committed
Improve performance of ParameterizedTestSpiInstantiator
Since Class.getDeclaredConstructors() clones the constructors array AND makes "child copies" of the constructors, it's usually better to avoid repeated calls to getDeclaredConstructors() for a single use case. In addition, it's good to avoid the use of Optional, multiple Streams, and try-catch blocks if feasible. This commit reworks ParameterizedTestSpiInstantiator in order to achieve that by switching to old-school Java constructs like arrays, for-loops, and if-blocks. See #4018 See #4025
1 parent 48cd00d commit daea6f1

File tree

1 file changed

+24
-28
lines changed

1 file changed

+24
-28
lines changed

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestSpiInstantiator.java

+24-28
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,10 @@
1010

1111
package org.junit.jupiter.params;
1212

13-
import static org.junit.platform.commons.util.CollectionUtils.getFirstElement;
14-
1513
import java.lang.reflect.Constructor;
16-
import java.util.Optional;
1714

1815
import org.junit.jupiter.api.extension.ExtensionContext;
1916
import org.junit.platform.commons.JUnitException;
20-
import org.junit.platform.commons.PreconditionViolationException;
2117
import org.junit.platform.commons.util.Preconditions;
2218
import org.junit.platform.commons.util.ReflectionUtils;
2319

@@ -28,46 +24,46 @@ class ParameterizedTestSpiInstantiator {
2824

2925
static <T> T instantiate(Class<T> spiInterface, Class<? extends T> implementationClass,
3026
ExtensionContext extensionContext) {
27+
3128
return extensionContext.getExecutableInvoker() //
3229
.invoke(findConstructor(spiInterface, implementationClass));
3330
}
3431

3532
/**
36-
* Find the "best" constructor for the supplied class.
33+
* Find the "best" constructor for the supplied implementation class.
3734
*
38-
* <p>For backward compatibility, it first checks for a default constructor
39-
* which takes precedence over any other constructor. If no default
40-
* constructor is found, it checks for a single constructor and returns it.
35+
* <p>For backward compatibility, it first checks for a single constructor
36+
* and returns that. If there are multiple constructors, it checks for a
37+
* default constructor which takes precedence over any other constructors.
38+
* Otherwise, this method throws an exception stating that it failed to
39+
* find a suitable constructor.
4140
*/
41+
@SuppressWarnings("unchecked")
4242
private static <T, V extends T> Constructor<? extends V> findConstructor(Class<T> spiInterface,
4343
Class<V> implementationClass) {
4444

4545
Preconditions.condition(!ReflectionUtils.isInnerClass(implementationClass),
4646
() -> String.format("The %s [%s] must be either a top-level class or a static nested class",
4747
spiInterface.getSimpleName(), implementationClass.getName()));
4848

49-
return findDefaultConstructor(implementationClass) //
50-
.orElseGet(() -> findSingleConstructor(spiInterface, implementationClass));
51-
}
52-
53-
@SuppressWarnings("unchecked")
54-
private static <T> Optional<Constructor<T>> findDefaultConstructor(Class<T> clazz) {
55-
return getFirstElement(ReflectionUtils.findConstructors(clazz, it -> it.getParameterCount() == 0)) //
56-
.map(it -> (Constructor<T>) it);
57-
}
49+
Constructor<?>[] constructors = implementationClass.getDeclaredConstructors();
5850

59-
private static <T, V extends T> Constructor<V> findSingleConstructor(Class<T> spiInterface,
60-
Class<V> implementationClass) {
61-
62-
try {
63-
return ReflectionUtils.getDeclaredConstructor(implementationClass);
51+
// Single constructor?
52+
if (constructors.length == 1) {
53+
return (Constructor<V>) constructors[0];
6454
}
65-
catch (PreconditionViolationException ex) {
66-
String message = String.format(
67-
"Failed to find constructor for %s [%s]. "
68-
+ "Please ensure that a no-argument or a single constructor exists.",
69-
spiInterface.getSimpleName(), implementationClass.getName());
70-
throw new JUnitException(message);
55+
// Find default constructor.
56+
for (Constructor<?> constructor : constructors) {
57+
if (constructor.getParameterCount() == 0) {
58+
return (Constructor<V>) constructor;
59+
}
7160
}
61+
// Otherwise...
62+
String message = String.format(
63+
"Failed to find constructor for %s [%s]. "
64+
+ "Please ensure that a no-argument or a single constructor exists.",
65+
spiInterface.getSimpleName(), implementationClass.getName());
66+
throw new JUnitException(message);
7267
}
68+
7369
}

0 commit comments

Comments
 (0)