Writing a JUnit 5 parameterized test and need to pass functions to the test using Arguments.of(), but there are 2 compile errors that I don't know how to fix. Any help would be appreciated.
The method of(Object...) in the type Arguments is not applicable for the arguments (boolean, String::length)
The target type of this expression must be a functional interface
public static Stream<Arguments> some() {
return Stream.of(Arguments.of(true, String::length));
}
#ParameterizedTest
#MethodSource
public <T> void some(final T input, final Function<String, Integer> length) {
}
The following works as expected.
public void sample() {
some(true, String::length);
}
Wrap the arguments in a helper method
Similar to the answer "wrap it in a class", but possibly less intrusive, is to use a helper method to pass the functional interface as a java.lang.Object.
For example, the first raw method reference, Math::ciel, in this parameterized test:
#ParameterizedTest
#MethodSource("testCases")
void shouldExerciseMethod(Function<Double, Double> method, Double expected) {
assertEquals(expected, method.apply(1.5d), 1.0E-8d);
}
static Stream<Arguments> testCases() {
return Stream.of(Arguments.of(Math::ceil, 2.0d),
Arguments.of(Math::floor, 1.0d));
}
causes this compilation error:
java: method of in interface org.junit.jupiter.params.provider.Arguments cannot be applied to given types;
required: java.lang.Object[]
found: Math::ceil,double
reason: varargs mismatch; java.lang.Object is not a functional interface
which you can get around by passing the arguments through a helper method:
static <T, U> Arguments args(Function<T, U> method, U expected) {
return Arguments.of(method, expected);
}
so:
static Stream<Arguments> testCases() {
return Stream.of(args(Math::ceil, 2.0d),
args(Math::floor, 1.0d));
}
My attempts to make the idiom more general using varargs failed with variations on the same error, so I have ended up overloading it whenever I need another signature.
The function needs to be wrapped in a class.
public static class P {
private final Function<String, Integer> mFunction;
public P(final Function<String, Integer> function) {
mFunction = function;
}
public Function<String, Integer> function() {
return mFunction;
}
}
public static Stream<Arguments> some() {
return Stream.of(Arguments.of(3, "abc", new P(String::length)));
}
#ParameterizedTest
#MethodSource
public <T> void some(final int expect, final String input, final P p) {
assertEquals(expect, p.function().apply(input));
}
I liked #adrian-redgers solution, but I think overloading a method for each signature needed is a bit overkill.
You only really need to convert the functional interface to an object. So the solution I implemented was:
/**
* Helps to use {#link org.junit.jupiter.params.provider.Arguments#of(Object...)}, as functional
* interfaces cannot be converted into an object directly.
*/
public class ArgumentsWrapper {
private ArgumentsWrapper() {
throw new IllegalStateException(
ArgumentsWrapper.class + " util class cannot be instantiated");
}
public static <T, U> Function<T, U> wrap(Function<T, U> function) {
return function;
}
}
Then, it can be used as:
public static Stream<Arguments> testMapAlarmTypeConfigWithLanguage() {
return Stream.of(
// Statically imported ArgumentsWrapper#wrap
Arguments.of(null, wrap(AlarmTypeConfig::getNameInEnglish)),
Arguments.of("en-us", wrap(AlarmTypeConfig::getNameInEnglish)),
Arguments.of("es-es", wrap(AlarmTypeConfig::getNameInSpanish)));
}
Related
Update 2020-12-23
The origin description is a little bit confusing. Kotlin not just allows same signature with getter in sub-class but also in self class too. So this is also allowed:
open class BaseRequest {
val params = mutableMapOf<String, String>()
fun getParams(): List<String> {
return params.values.toList()
}
}
As #Slaw said, this is the behaviour of kotlin compilor and it works since JVM invoke the correct method using address but not "the signature".
I run into a situation that seems Kotlin allows sub-class create the same signature as super-class’s getter.
Generally, functions has same signature and different return type is not allowed. So I’m confused about this situation. I’m not sure whether this is by designed.
Here is a sample:
open class BaseRequest {
val params = mutableMapOf<String, String>()
init {
params["key1"] = "value1"
}
}
class SpecificRequest : BaseRequest() {
init {
params["key2"] = "value2"
}
fun getParams(): List<String> {
return params.values.toList()
}
}
MediatorRequest has a function getParams() which has same signature as it’s super-class but has different return type. While using this function, it seems the sub class and super class has different implement of the same declaration.
fun main() {
val specificRequest = SpecificRequest()
println("specificRequest.params: ${specificRequest.params}")
println("specificRequest.getParams(): ${specificRequest.getParams()}")
println("(specificRequest as BaseRequest).params: ${(specificRequest as BaseRequest).params}")
}
The Output would be like this:
specificRequest.params: {key1=value1, key2=value2}
specificRequest.getParams(): [value1, value2]
(specificRequest as BaseRequest).params: {key1=value1, key2=value2}
If we look at the decompiled Java code, there are two methods has same signature and different return type and this is truly not allowed in Java.
public class BaseRequest {
#NotNull
private final Map params;
#NotNull
public final Map getParams() {
return this.params;
}
/* ... */
}
public final class SpecificRequest extends BaseRequest {
#NotNull
public final List getParams() {
return CollectionsKt.toList((Iterable)this.getParams().values());
}
/* ... */
}
I know the function name is not appropriate but there is a potential risk that if we use the SpecificRequest in .java, we can not visite the Map params until we cast the instance to it’s super class. And this may lead to misunderstanding.
There is a difference between Java the language and the JVM. The Java language does not allow two methods with the same name but different return types to be declared in the same class. This is a restriction of the language. The JVM, however, is perfectly capable of distinguishing between the two methods. And since Kotlin is its own language it does not necessarily have to follow the exact same rules as Java—even when targeting the JVM (and thus compiled to byte-code).
Consider the following Kotlin class:
class Foo {
val bar = mapOf<Any, Any>()
fun getBar() = listOf<Any>()
}
If you compile the class and then inspect the byte-code with javap you'll see:
Compiled from "Foo.kt"
public final class Foo {
public final java.util.Map<java.lang.Object, java.lang.Object> getBar();
public final java.util.List<java.lang.Object> getBar();
public Foo();
}
So the two functions definitely exist, despite having the same name. But if you access the property and call the function you'll see that:
fun test() {
val foo = Foo()
val bar1 = foo.bar
val bar2 = foo.getBar()
}
Becomes:
public static final void test();
descriptor: ()V
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
Code:
stack=2, locals=3, args_size=0
0: new #8 // class Foo
3: dup
4: invokespecial #11 // Method Foo."<init>":()V
7: astore_0
8: aload_0
9: invokevirtual #15 // Method Foo.getBar:()Ljava/util/Map;
12: astore_1
13: aload_0
14: invokevirtual #18 // Method Foo.getBar:()Ljava/util/List;
17: astore_2
18: return
Which shows the byte-code knows which function to call. And the JVM can handle this.
But there is a caveat. The following will fail to compile:
class Foo {
fun getBaz() = mapOf<Any, Any>()
fun getBaz() = listOf<Any>()
}
Why? I'm not positive, but I believe it has to do with the syntax. The Kotlin compiler can always easily tell which function you meant to invoke based on if you used foo.bar or foo.getBar(). But the syntax is the same for calling the two getBaz() functions, which means the compiler can't easily tell which function you meant to invoke in all cases (and so it disallows the above).
Run into some difficulties while using extension functions with existing java api. Here some pseudocode
public class Test {
public Test call() {
return this;
}
public Test call(Object param) {
return this;
}
public void configure1() {
}
public void configure2(boolean value) {
}
}
Kotlin test
fun Test.call(toApply: Test.() -> Unit): Test {
return call()
.apply(toApply)
}
fun Test.call(param: Any, toApply: Test.() -> Unit): Test {
return call(param)
.apply(toApply)
}
fun main(args: Array<String>) {
val test = Test()
//refers to java method; Unresolved reference: configure1;Unresolved reference: configure2
test.call {
configure1()
configure2(true)
}
//refers to my extension function and works fine
test.call(test) {
configure1()
configure2(true)
}
}
Why only function with param works fine ? what’s the difference ?
Kotlin will always give precedence to the classes member functions. Since Test:call(Object) is a possible match, Kotlin selects that method rather than your extension function.
The extension function with the added parameter is resolved the way you expect because the Test class does not have any member functions that would take precedent (no matching signature), so your extension method is selected.
Here is a link to the Kotlin documentation as to how extension functions are resolved: https://kotlinlang.org/docs/reference/extensions.html#extensions-are-resolved-statically
How to use method references to refer to super class methods?
In Java 8 you can do SubClass.super::method.
What would be the syntax in Kotlin?
Looking forward to your response!
Conclusion
Thanks to Bernard Rocha!
The syntax is SubClass::method.
But be careful. In my case the subclass was a generic class. Don't forget to declare it as those:
MySubMap<K, V>::method.
EDIT
It still doesn't work in Kotlin.
Hers's an example in Java 8 of a method reference to a super class method:
public abstract class SuperClass {
void method() {
System.out.println("superclass method()");
}
}
public class SubClass extends SuperClass {
#Override
void method() {
Runnable superMethodL = () -> super.method();
Runnable superMethodMR = SubClass.super::method;
}
}
I'm still not able to do the same in Kotlin...
EDIT
This is an example how I tried to achieve it in Kotlin:
open class Bar {
open fun getString(): String = "Hello"
}
class Foo : Bar() {
fun testFunction(action: () -> String): String = action()
override fun getString(): String {
//this will throw an StackOverflow error, since it will continuously call 'Foo.getString()'
return testFunction(this::getString)
}
}
I want to have something like that:
...
override fun getString(): String {
//this should call 'Bar.getString' only once. No StackOverflow error should happen.
return testFunction(super::getString)
}
...
Conclusion
It's not possible to do so in Kotlin yet.
I submitted a feature report. It can be found here: KT-21103 Method Reference to Super Class Method
As the documentation says you use it like in java:
If we need to use a member of a class, or an extension function, it
needs to be qualified. e.g. String::toCharArray gives us an extension
function for type String: String.() -> CharArray.
EDIT
I think you can achieve what you want doing something like this:
open class SuperClass {
companion object {
fun getMyString(): String {
return "Hello"
}
}
}
class SubClass : SuperClass() {
fun getMyAwesomeString(): String {
val reference = SuperClass.Companion
return testFunction(reference::getMyString)
}
private fun testFunction(s: KFunction0<String>): String {
return s.invoke()
}
}
Don't know if it is possible to get the reference to super class's function, but here is an alternative to what you want to achieve:
override fun getString(): String = testFunction { super.getString() }
According to Bernardo's answer, you might have something like this. It doesn't have remarkable changes.
fun methodInActivity() {
runOnUiThread(this::config)
}
fun config(){
}
What is more, in the incoming 1.2 version you can use just
::config
When passing a lambda or anonymous function to inlined functions as a parameter, it's quite simple, the code is pasted to the calling position, but when passing a local function as a parameter, the result seems different(shown as below). I wonder if it's inlined? Why or why not?
For example:
inline fun foo(arg: () -> Int): Int {
return arg()
}
fun bar(): Int {
return 0
}
fun main(args: Array<String>) {
foo(::bar)
}
And decompiled Java code:
public final class InlinedFuncKt {
public static final int foo(#NotNull Function0 arg) {
Intrinsics.checkParameterIsNotNull(arg, "arg");
return ((Number)arg.invoke()).intValue();
}
public static final int bar() {
return 0;
}
public static final void main(#NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
bar();
}
}
bar() is not declared to be inlined. So why would you expect it to be inlined?!
In other words: it would be simply wrong that the signature of method A affects (implicitly) the signature of another method B.
Your idea would (somehow) affect "semantics" of bar() - just because you used bar() as argument to another method call.
As you can in the decompiled code, kotlin does not inline bar in your case, but it does inline it if it is declared as inline fun bar().
The rule is that lambdas are inlined when they are passed to an inlined function. In all other cases like passing a function reference or a lambda object, no inlining is done.
Exceptions.kt:
#Suppress("NOTHING_TO_INLINE")
inline fun generateStyleNotCorrectException(key: String, value: String) =
AOPException(key + " = " + value)
In kotlin:
fun inKotlin(key: String, value: String) {
throw generateStyleNotCorrectException(key, value) }
It works in kotlin and the function is inlined.
But when used in Java code, It just cannot be inlined,
and still a normal static method call (seen from the decompiled contents).
Something like this:
public static final void inJava(String key, String value) throws AOPException {
throw ExceptionsKt.generateStyleNotCorrectException(key, value);
// when decompiled, it has the same contents as before , not the inlined contents.
}
The inlining that's done by the Kotlin compiler is not supported for Java files, since the Java compiler is unaware of this transformation (see this answer about why reified generics do not work from Java at all).
As for other use cases of inlining (most commonly when passing in a lambda as a parameter), as you've already discovered, the bytecode includes a public static method so that the inline function can be still called from Java. In this case, however, no inlining occurs.
Yes, u can do it
In Kotlin file:
Builder.sendEvent { event ->
YandexMetrica.reportEvent(event)
}
.build();
In Java file:
Builder.sendEvent(new Function1<String, Unit>() {
#Override
public Unit invoke(String event) {
Log.i("TEST", event);
return null;
}
})
.build();