I've a java spark streaming app (using spark 3.0.1) where I need to call custom transformations on streaming data. These custom transformations are defined inside a class as methods and given to us as a jar file. We need to wrap these methods in UDFs and call those in our spark code. A sample set of transformations can be defined as follows. Please remember it comes as a jar.
import java.io.Serializable;
public class CustomTransformations implements Serializable {
public String f1(String input) {
return input + "_1";
}
public String f2(String input) {
return input + "_2";
}
public String f3(String input) {
return input + "_3";
}
}
Let's assume somewhere (e.g. json or config file) we have a map of the transformations and the corresponding method names (Strings), so that given the transformation, we can wrap the corresponding method in a UDF and invoke it. I created the following class for this purpose.
import java.lang.reflect.Method;
import static org.apache.spark.sql.functions.udf;
public class Creator {
public static UserDefinedFunction getUDF(CustomTransformations ct, String funcName)
throws NoSuchMethodException {
Method method = ct.getClass().getDeclaredMethod(funcName);
return udf(
(UDF1<String, Object>) method::invoke, DataTypes.StringType);
}
}
So far no compilation error. But now the issue is, if I call this method getUDF from the spark code, it shows a NoSuchMethodException. E.g. my spark code is something like following.
public class SampleSparkJob {
public static void main(String[] args) {
SparkSession.Builder sparkSessionBuilder = SparkSession.builder()
.master("local[2]")
.appName("sample-streaming");
CustomTransformations ct = new CustomTransformations();
try (SparkSession spark = sparkSessionBuilder.getOrCreate()) {
Dataset<Row> df1 = MyKafkaConnectors.readFromKafka();
// this is where I get the exceptions
Dataset<Row> df2 = df1
.withColumn("value", Creator.getUDF(ct, "f1").apply(col("value")))
.withColumn("value", Creator.getUDF(ct, "f2").apply(col("value")))
.withColumn("value", Creator.getUDF(ct, "f3").apply(col("value")));
StreamingQuery query = MyKafkaConnectors.WriteToKafka(df2);
query.awaitTermination();
} catch (TimeoutException | StreamingQueryException | NoSuchMethodException e) {
e.printStackTrace();
}
}
}
This is the error that I get:
java.lang.NoSuchMethodException: <pkgname>.CustomTransformations.f1()
at java.base/java.lang.Class.getDeclaredMethod(Class.java:2475)
at Creator.getUDF(Creator.java:14)
at SampleSparkJob.main(SampleSparkJob.java:29)
The package-name is correct. Clearly, the client's CustomTransformations class has a method f1. So I'm not able to understand why it's showing this error. Any help is appreciated.
Your code has two problems, both unrelated to spark.
First, NoSuchMethodException:CustomTransformations.f1() is telling you that no method f1 without any parameters does exist. This is true.
You need to specify parameter types to getDeclaredMethod (String in this case).
Second, a method cannot be called on his own with invoke, you need to pass the "owner" or "this" object to invoke.
Then Creator fixed looks like this:
public class Creator {
public static UserDefinedFunction getUDF(CustomTransformations ct, String funcName)
throws NoSuchMethodException {
Method method = ct.getClass().getDeclaredMethod(funcName, String.class);
return udf(
(UDF1<String, Object>) (s -> method.invoke(ct, s)), DataTypes.StringType);
}
}
And it will work just fine.
I've only one point to add to the precise answer given by #fonkap. As the java.lang.reflect.Method is not serializable, we need to bypass referencing that object in our getUDF method. The Creator class will be something like the following.
import static org.apache.spark.sql.functions.udf;
public class Creator implements Serializable {
public static UserDefinedFunction getUDF(CustomTransformation ct, String funcName) {
return udf((UDF1<String, Object>) (s -> ct.getClass().getDeclaredMethod(funcName,
String.class).invoke(ct, s)),
DataTypes.StringType);
}
}
Related
I am trying to do when().thenReturn() but facing issues. Following is the sample code that I came up with for SOers:
import java.util.List;
public class Sample {
public void function(List<SampleA> list) {
for (SampleA s : list) {
List<SampleB> nameList = s.v1;
for (SampleB m : nameList) {
SampleC value = m.getV2();
if (value != null) {
doSomething(value);
} else {
LOGGER.warn("No valid value");
}
}
}
}
}
public class SampleA{
List<SampleB> v1;
}
public class SampleB{
SampleC v2;
public SampleC getV2(){
return this.v2;
}
}
This might be silly but I just want to understand why can't I do:
SampleB sampleB = new SampleB();
when(sampleB.getV2()).thenReturn(any(SampleC.class));
I'm guessing that you are trying to test Sample.function and are trying to work out how to mock SampleC values. If so, then your code should probably look something like:
#Test
void testFunction() {
SampleC mockC = mock(SampleC.class);
SampleB mockB = mock(SampleB.class);
SampleA mockA = mock(SampleA.class);
when(mockB.getV2()).thenReturn(mockC);
when(mockA.getV1()).thenReturn(List.of(mockB));
Sample sample = new Sample();
sample.function(List.of(mockA));
// verify doSomething was called with mockC as its argument
when(mockB.getV2()).thenReturn(null);
sample.function(List.of(mockA));
// verify "No valid value" was logged
}
I've added a getV1 method rather than direct access to the field because my fingers refuse to write the code that directly accesses a public field :-)
If you're struggling with verifying that doSomething was called then that's where argument matching comes in. Ideally you'd have a SomethingDoer that you inject into your sample:
interface SomethingDoer {
void doSomething(SampleC sample);
}
class Sample {
private final SomethingDoer somethingDoer;
public Sample(SomethingDoer somethingDoer) {
this.somethingDoer = somethingDoer;
}
}
Then you'd add the following to your test:
SomethingDoer doer = mock(SomethingDoer.class);
Sample sample = new Sample(doer);
...
verify(doer).doSomething(mockC);
Also note that the code above is poor style: the tests should be split up with the setup code in a #BeforeEach method. I've just put it in a single test to keep the answer simple.
any(…) is an ArgumentMatcher. It is used to match arguments. You cannot use it to return instances of classes. What would "any SampleC" be?
You'd use it with mock objects, like so:
SampleInterface sample = Mockito.mock(SampleInterface.class);
when(sample.function(any(List.class))).thenReturn(new ArrayList<>());
// or: .thenAnswer(a -> new ArrayList<>()); to return a new instance for each call
you can't use ArgumentMatchers inside thenReturn or thenAnswer. You have to specify exactly what to return. For Example:
SampleB sampleB = mock(SampleB.class)
when(sampleB.getV2()).thenReturn(new SampleC());
I am trying to build a parameterized Junit Test in order to test each occurrence of a LinkedHashMap within an ArrayList of LinkedHashMaps. This is because each LinkedHashMap represents a test case.
The map is built/stored by a object within my TestSuite class, which utilizes the #BeforeClass annotation, as per below:
#RunWith(Suite.class)
public class TestSuite {
public static MapBuilder mapBuilder = new MapBuilder ();
#ClassRule
public static ExternalResource testResource = new ExternalResource();
#Override
protected void before () throws IOException {
mapBuilder.buildMap();
}
}
The .buildMap() method of Mapbuilder is expensive, so I do not wish to build it for every Junit Test, hence the need for parameterized class, as per below :
#RunWith(Parameterized.class)
public class TestCasesA {
private LinkedHashMap<Integer, Integer> currentMap;
public TestCasesA (LinkedHashMap<Integer, Integer> currentMap) {
this.currentMap = currentMap;
}
#Parameters
public static Collection<Object[]> dataFeed () {
Object[] objArray = TestSuite.MapBuilder.returnBuiltMap().toArray();
return Arrays.asList(new Object[][] {objArray});
}
#Test
// Some test which uses currentMap
}
However, when i run this i keep facing the below issue:
java.lang.IllegalArgumentException: wrong number of arguments at java.lang.reflect.Constructor.newInstance(Unknown Source)
I've been scouring Stack Overflow and other sites about this all day, but i still cannot work it out.
Not sure if this matters or not but my LinkedHashMap may contain between 1 upto around 32 key/value entries (Could contain more, but very unlikely).
I'v followed the plentiful examples online where you manually type what you want to return, and this works fine, but manually typing in is not suitable for what i'm trying to achieve.
I've even done a enhanced for loop within TestCasesB, called the .returnBuiltMap() method and printed out each iteration of the map just to prove my Test Cases can "see" the built map.
Can anyone help?
For reference, I am using Java 7 and Junit 4.
I've been researching this further myself due to lack of any answers (Uploading the wrong code in my original post was a bad move on my part admittedly).
I finally found the answer today - #BeforeClass runs after #Parameters, as discussed in great detail here > https://github.com/junit-team/junit4/issues/527
My above code was always going fail as my data set-up was performed after #Parameters ran, so #Parameters had nothing to feed into the designated constructor.
It seems you have forgotten to add the constructor, something like:
#RunWith(Parameterized.class)
public class TestCasesA {
private Integer paramA, paramB;
#Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 1, 1 },
{ 2, 2 }
});
}
public TestCasesA (Integer paramA, Integer paramB){
this.paramA = paramA;
this.paramB = paramB;
}
#Test
public void testYourParams() {
assertEquals(paramA, paramB);
}
}
I was also fasing this error:
java.lang.IllegalArgumentException: wrong number of arguments
after I removed paramether from constructor, but then I found that I also must delete it from #Parameterized.Parameters in
companior object. See the code below:
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
#ExperimentalCoroutinesApi
#RunWith(Parameterized::class)
class MyAppClass1UseCaseTest(
private val expectedFirstParam: String,
expectedSecondParam: Boolean //If you remove paramether from here, then you also have to remove param from copmanion // object below
) {
#Test
fun useCaseTest1() = runBlockingTest {
}
companion object {
#JvmStatic
#Parameterized.Parameters(name = "firstParam {0}, secondParam {1}") //you also need to remove param from here
fun data(): Collection<Array<Any?>> {
return listOf(
arrayOf("stringValue1", true), //and remove param from here
arrayOf("stringValue1", false)
)
}
}
}
I would like to create a lambda function in Java 8, get it's classname and then later instantiate the function again from its classname.
This is what I try:
import java.util.function.Consumer;
public class SimpleLambda
{
public static void call(String aLambdaClassName, String aArg) throws Exception
{
Class<Consumer<String>> lClass = (Class<Consumer<String>>) Class.forName(aLambdaClassName);
Consumer<String> newlamba = lClass.newInstance();
newlamba.accept(aArg);
}
public static void main(String[] args) throws Exception
{
{
// Attempt with a static method as lambda
Consumer<String> lambda = Host::action;
String classname = lambda.getClass().getName();
call(classname, "Hello world");
}
{
// Attempt with a locally defined lambda
Consumer<String> lambda = (s) -> { System.out.println(s); };
String classname = lambda.getClass().getName();
call(classname, "Hello world");
}
}
}
class Host {
public static void action(String aMessage) {
System.out.println(aMessage);
}
}
However, with this code (in both variants, using the static method reference and using the locally declared lambda), I get an exception:
Exception in thread "main" java.lang.ClassNotFoundException: mypackage.SimpleLambda$$Lambda$1/471910020
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:264)
at mypackage.SimpleLambda.main(SimpleLambda.java:12)
I would have expected that at I can at least re-instantiate the static method reference... nope, apparently not.
I have been using a similar approach with Groovy Closures and that worked nicely. So am I just doing something wrong with the Java 8 lambdas, or is it not possible to instantiate lambdas by name? I found some hints on the net that lambdas can be (de)serialized, so I would expect it should also be possible to instantiate them by name.
Well, it is a special property of Oracle’s JRE/OpenJDK to use “anonymous classes”, which can’t be accessed by name at all. But even without this, there is no reason why this ought to work:
Class.forName(String) tries to resolve the class via the caller’s ClassLoader. So even if lambda expressions were implemented using ordinary classes, there were not accessible if loaded via a different ClassLoader
Class.newInstance() only works if there is a public no-arg constructor. You can’t assume that there is a no-arg constructor nor that it is public
The assumption that the entire function’s logic has to reside in a single class is wrong. A counter-example would be java.lang.reflect.Proxy which generates interface implementations delegating to an InvocationHandler. Trying to re-instantiate such a proxy via its class name would fail, because you need the to pass the actual InvocationHandler instance to the proxy’s constructor. In principle, the JRE specific lambda expression implementation could use a similar pattern
Considering the points above, it should be clear that you can’t say that it worked with inner classes in general. There are a lot of constraints you have to fulfill for that.
Regarding Serialization, it works for serializable lambda expressions, because the persistent form is completely detached from the runtime implementation class, as described in this answer. So the name of the generated class is not contained in the serialized form and the deserializing end could have an entirely different runtime implementation.
Store the lambda instances in Map, keyed on the instance name. You can make the map globally available trough a singleton wrapper class (just watch out for synchronization issues).
class LambdaMap {
private HashMap<String, Consumer<String>> theMap;
private LambdaMap() {
theMap = new HashMap<>();
}
private static class INSTANCE_HOLDER {
private static LambdaMap INSTANCE = new LambdaMap();
}
public static LambdaMap getInstance() {
return INSTANCE_HOLDER.INSTANCE;
}
public Consumer<String> put(String key, Consumer<String> value) {
return theMap.put(key, value);
}
public static void Call(String aLambdaClassName, String aArg) {
Consumer<String> func = getInstance().theMap.get(aLambdaClassName);
if (func != null) {
func.accept(aArg);
}
}
}
class Host {
public static void action(String aMessage) {
System.out.println("Goodbye, " + aMessage);
}
}
public class GlobalLambdas {
public static void main(String[] args) {
LambdaMap.getInstance().put("print greeting", s -> {
System.out.println("Hello, " + s);
});
LambdaMap.getInstance().put("print goodbye", Host::action);
LambdaMap.Call("print greeting", "John");
LambdaMap.Call("print goodbye", "John");
}
}
run:
Hello, John
Goodbye, John
Please have a look at the following code:
Method methodInfo = MyClass.class.getMethod("myMethod");
This works, but the method name is passed as a string, so this will compile even if myMethod does not exist.
On the other hand, Java 8 introduces a method reference feature. It is checked at compile time. It is possible to use this feature to get method info?
printMethodName(MyClass::myMethod);
Full example:
#FunctionalInterface
private interface Action {
void invoke();
}
private static class MyClass {
public static void myMethod() {
}
}
private static void printMethodName(Action action) {
}
public static void main(String[] args) throws NoSuchMethodException {
// This works, but method name is passed as a string, so this will compile
// even if myMethod does not exist
Method methodInfo = MyClass.class.getMethod("myMethod");
// Here we pass reference to a method. It is somehow possible to
// obtain java.lang.reflect.Method for myMethod inside printMethodName?
printMethodName(MyClass::myMethod);
}
In other words I would like to have a code which is the equivalent of the following C# code:
private static class InnerClass
{
public static void MyMethod()
{
Console.WriteLine("Hello");
}
}
static void PrintMethodName(Action action)
{
// Can I get java.lang.reflect.Method in the same way?
MethodInfo methodInfo = action.GetMethodInfo();
}
static void Main()
{
PrintMethodName(InnerClass.MyMethod);
}
No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.
Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.
Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.
In my case I was looking for a way to get rid of this in unit tests:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
As you can see someone is testing Method getAPoint and checks that the coordinates are as expected, but in the description of each assert was copied and is not in sync with what is checked. Better would be to write this only once.
From the ideas by #ddan I built a proxy solution using Mockito:
private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) {
final String methodName = getMethodName(object.getClass(), getter);
assertEquals(getter.apply(object), expected, methodName);
}
#SuppressWarnings("unchecked")
private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) {
final Method[] method = new Method[1];
getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> {
method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
})));
return method[0].getName();
}
No I can simply use
assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
and the description of the assert is guaranteed to be in sync with the code.
Downside:
Will be slightly slower than above
Needs Mockito to work
Hardly useful to anything but the usecase above.
However it does show a way how it could be done.
Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.
You can add safety-mirror to your classpath and do like this:
Method m1 = Types.createMethod(Thread::isAlive) // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods
The library also offers a getName(...) method:
assertEquals("isEmpty", Types.getName(String::isEmpty));
The library is based on Holger's answer: https://stackoverflow.com/a/21879031/6095334
Edit: The library have various shortcomings which I am slowly becoming aware of.
See fx Holger's comment here: How to get the name of the method resulting from a lambda
There may not be a reliable way, but under some circumstances:
your MyClass is not final, and has an accessible constructor (limitation of cglib)
your myMethod is not overloaded, and not static
The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptor to report the Method while the method reference is invoked in a following trial run.
Example code:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
You will see the following output:
public boolean java.util.ArrayList.contains(java.lang.Object)
While:
public class MethodReferenceUtils {
#FunctionalInterface
public static interface MethodRefWith1Arg<T, A1> {
void call(T t, A1 a1);
}
public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) {
return findReferencedMethod(clazz, t -> methodRef.call(t, null));
}
#SuppressWarnings("unchecked")
private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) {
AtomicReference<Method> ref = new AtomicReference<>();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
#Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ref.set(method);
return null;
}
});
try {
invoker.accept((T) enhancer.create());
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
Method method = ref.get();
if (method == null) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
return method;
}
}
In the above code, MethodRefWith1Arg is just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgs for referencing your other methods.
If you can make the interface Action extend Serializable, then this answer from another question seems to provide a solution (at least on some compilers and runtimes).
We have published the small library reflection-util that can be used to capture a method name.
Example:
class MyClass {
private int value;
public void myMethod() {
}
public int getValue() {
return value;
}
}
String methodName = ClassUtils.getMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"
String getterName = ClassUtils.getMethodName(MyClass.class, MyClass::getValue);
System.out.println(getterName); // prints "getValue"
Implementation details: A Proxy subclass of MyClass is created with ByteBuddy and a call to the method is captured to retrieve its name.
ClassUtils caches the information such that we do not need to create a new proxy on every invocation.
Please note that this approach is no silver bullet and there are some known cases that don’t work:
It doesn’t work for static methods.
It doesn’t work if the class is final.
We currently do not support all potential method signatures. It should work for methods that do not take an argument such as a getter method.
You can use my library Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Another solution using Mockito:
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>compile</scope>
</dependency>
Test code:
public class MethodUtilTest {
#Test
void testMethodNameGetter() {
final Method method = MethodUtil.getMethodFromGetter(DummyClass.class, DummyClass::getTestString);
Assertions.assertEquals("getTestString", method.getName());
}
#Test
void testMethodNameSetter() {
final Method method = MethodUtil.getMethodFromSetter(DummyClass.class, DummyClass::setTestString);
Assertions.assertEquals("setTestString", method.getName());
}
}
java code:
public class MethodUtil {
public static <T> Method getMethodFromGetter(final Class<T> clazz, final Function<T, ?> getter) {
return captureMethodOnInvocation(clazz, getter::apply);
}
public static <T, V> Method getMethodFromSetter(final Class<T> clazz, final BiConsumer<T, V> setter) {
return captureMethodOnInvocation(clazz, (T mock) -> setter.accept(mock, ArgumentMatchers.any()));
}
private static <T> Method captureMethodOnInvocation(final Class<T> clazz, final Consumer<T> invokeMock) {
try {
final AtomicReference<Method> methodReference = new AtomicReference<>();
final InvocationListener invocationListener = new InvocationListener() {
#Override
public void reportInvocation(final MethodInvocationReport methodInvocationReport) {
final Method method = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
methodReference.set(method);
}
};
final MockSettings mockSettings = Mockito.withSettings().invocationListeners(invocationListener);
final T mock = Mockito.mock(clazz, mockSettings);
invokeMock.accept(mock);
return methodReference.get();
} catch (final Exception e) {
throw new RuntimeException("Method could not be captured at runtime.", e);
}
}
}
So, I play with this code
import sun.reflect.ConstantPool;
import java.lang.reflect.Method;
import java.util.function.Consumer;
public class Main {
private Consumer<String> consumer;
Main() {
consumer = this::test;
}
public void test(String val) {
System.out.println("val = " + val);
}
public void run() throws Exception {
ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass());
for (int i = 0; i < oa.getSize(); i++) {
try {
Object v = oa.getMethodAt(i);
if (v instanceof Method) {
System.out.println("index = " + i + ", method = " + v);
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
output of this code is:
index = 30, method = public void Main.test(java.lang.String)
And as I notice index of referenced method is always 30.
Final code may look like
public Method unreference(Object methodRef) {
ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass());
try {
Object method = constantPool.getMethodAt(30);
if (method instanceof Method) {
return (Method) method;
}
}catch (Exception ignored) {
}
throw new IllegalArgumentException("Not a method reference.");
}
Be careful with this code in production!
Try this
Thread.currentThread().getStackTrace()[2].getMethodName();
Please have a look at the following code:
Method methodInfo = MyClass.class.getMethod("myMethod");
This works, but the method name is passed as a string, so this will compile even if myMethod does not exist.
On the other hand, Java 8 introduces a method reference feature. It is checked at compile time. It is possible to use this feature to get method info?
printMethodName(MyClass::myMethod);
Full example:
#FunctionalInterface
private interface Action {
void invoke();
}
private static class MyClass {
public static void myMethod() {
}
}
private static void printMethodName(Action action) {
}
public static void main(String[] args) throws NoSuchMethodException {
// This works, but method name is passed as a string, so this will compile
// even if myMethod does not exist
Method methodInfo = MyClass.class.getMethod("myMethod");
// Here we pass reference to a method. It is somehow possible to
// obtain java.lang.reflect.Method for myMethod inside printMethodName?
printMethodName(MyClass::myMethod);
}
In other words I would like to have a code which is the equivalent of the following C# code:
private static class InnerClass
{
public static void MyMethod()
{
Console.WriteLine("Hello");
}
}
static void PrintMethodName(Action action)
{
// Can I get java.lang.reflect.Method in the same way?
MethodInfo methodInfo = action.GetMethodInfo();
}
static void Main()
{
PrintMethodName(InnerClass.MyMethod);
}
No, there is no reliable, supported way to do this. You assign a method reference to an instance of a functional interface, but that instance is cooked up by LambdaMetaFactory, and there is no way to drill into it to find the method you originally bound to.
Lambdas and method references in Java work quite differently than delegates in C#. For some interesting background, read up on invokedynamic.
Other answers and comments here show that it may currently be possible to retrieve the bound method with some additional work, but make sure you understand the caveats.
In my case I was looking for a way to get rid of this in unit tests:
Point p = getAPoint();
assertEquals(p.getX(), 4, "x");
assertEquals(p.getY(), 6, "x");
As you can see someone is testing Method getAPoint and checks that the coordinates are as expected, but in the description of each assert was copied and is not in sync with what is checked. Better would be to write this only once.
From the ideas by #ddan I built a proxy solution using Mockito:
private<T> void assertPropertyEqual(final T object, final Function<T, ?> getter, final Object expected) {
final String methodName = getMethodName(object.getClass(), getter);
assertEquals(getter.apply(object), expected, methodName);
}
#SuppressWarnings("unchecked")
private<T> String getMethodName(final Class<?> clazz, final Function<T, ?> getter) {
final Method[] method = new Method[1];
getter.apply((T)Mockito.mock(clazz, Mockito.withSettings().invocationListeners(methodInvocationReport -> {
method[0] = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
})));
return method[0].getName();
}
No I can simply use
assertPropertyEqual(p, Point::getX, 4);
assertPropertyEqual(p, Point::getY, 6);
and the description of the assert is guaranteed to be in sync with the code.
Downside:
Will be slightly slower than above
Needs Mockito to work
Hardly useful to anything but the usecase above.
However it does show a way how it could be done.
Though I haven't tried it myself, I think the answer is "no," since a method reference is semantically the same as a lambda.
You can add safety-mirror to your classpath and do like this:
Method m1 = Types.createMethod(Thread::isAlive) // Get final method
Method m2 = Types.createMethod(String::isEmpty); // Get method from final class
Method m3 = Types.createMethod(BufferedReader::readLine); // Get method that throws checked exception
Method m4 = Types.<String, Class[]>createMethod(getClass()::getDeclaredMethod); //to get vararg method you must specify parameters in generics
Method m5 = Types.<String>createMethod(Class::forName); // to get overloaded method you must specify parameters in generics
Method m6 = Types.createMethod(this::toString); //Works with inherited methods
The library also offers a getName(...) method:
assertEquals("isEmpty", Types.getName(String::isEmpty));
The library is based on Holger's answer: https://stackoverflow.com/a/21879031/6095334
Edit: The library have various shortcomings which I am slowly becoming aware of.
See fx Holger's comment here: How to get the name of the method resulting from a lambda
There may not be a reliable way, but under some circumstances:
your MyClass is not final, and has an accessible constructor (limitation of cglib)
your myMethod is not overloaded, and not static
The you can try using cglib to create a proxy of MyClass, then using an MethodInterceptor to report the Method while the method reference is invoked in a following trial run.
Example code:
public static void main(String[] args) {
Method m = MethodReferenceUtils.getReferencedMethod(ArrayList.class, ArrayList::contains);
System.out.println(m);
}
You will see the following output:
public boolean java.util.ArrayList.contains(java.lang.Object)
While:
public class MethodReferenceUtils {
#FunctionalInterface
public static interface MethodRefWith1Arg<T, A1> {
void call(T t, A1 a1);
}
public static <T, A1> Method getReferencedMethod(Class<T> clazz, MethodRefWith1Arg<T, A1> methodRef) {
return findReferencedMethod(clazz, t -> methodRef.call(t, null));
}
#SuppressWarnings("unchecked")
private static <T> Method findReferencedMethod(Class<T> clazz, Consumer<T> invoker) {
AtomicReference<Method> ref = new AtomicReference<>();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new MethodInterceptor() {
#Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
ref.set(method);
return null;
}
});
try {
invoker.accept((T) enhancer.create());
} catch (ClassCastException e) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
Method method = ref.get();
if (method == null) {
throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", clazz));
}
return method;
}
}
In the above code, MethodRefWith1Arg is just a syntax sugar for you to reference an non-static method with one arguments. You can create as many as MethodRefWithXArgs for referencing your other methods.
If you can make the interface Action extend Serializable, then this answer from another question seems to provide a solution (at least on some compilers and runtimes).
We have published the small library reflection-util that can be used to capture a method name.
Example:
class MyClass {
private int value;
public void myMethod() {
}
public int getValue() {
return value;
}
}
String methodName = ClassUtils.getMethodName(MyClass.class, MyClass::myMethod);
System.out.println(methodName); // prints "myMethod"
String getterName = ClassUtils.getMethodName(MyClass.class, MyClass::getValue);
System.out.println(getterName); // prints "getValue"
Implementation details: A Proxy subclass of MyClass is created with ByteBuddy and a call to the method is captured to retrieve its name.
ClassUtils caches the information such that we do not need to create a new proxy on every invocation.
Please note that this approach is no silver bullet and there are some known cases that don’t work:
It doesn’t work for static methods.
It doesn’t work if the class is final.
We currently do not support all potential method signatures. It should work for methods that do not take an argument such as a getter method.
You can use my library Reflect Without String
Method myMethod = ReflectWithoutString.methodGetter(MyClass.class).getMethod(MyClass::myMethod);
Another solution using Mockito:
pom.xml:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>compile</scope>
</dependency>
Test code:
public class MethodUtilTest {
#Test
void testMethodNameGetter() {
final Method method = MethodUtil.getMethodFromGetter(DummyClass.class, DummyClass::getTestString);
Assertions.assertEquals("getTestString", method.getName());
}
#Test
void testMethodNameSetter() {
final Method method = MethodUtil.getMethodFromSetter(DummyClass.class, DummyClass::setTestString);
Assertions.assertEquals("setTestString", method.getName());
}
}
java code:
public class MethodUtil {
public static <T> Method getMethodFromGetter(final Class<T> clazz, final Function<T, ?> getter) {
return captureMethodOnInvocation(clazz, getter::apply);
}
public static <T, V> Method getMethodFromSetter(final Class<T> clazz, final BiConsumer<T, V> setter) {
return captureMethodOnInvocation(clazz, (T mock) -> setter.accept(mock, ArgumentMatchers.any()));
}
private static <T> Method captureMethodOnInvocation(final Class<T> clazz, final Consumer<T> invokeMock) {
try {
final AtomicReference<Method> methodReference = new AtomicReference<>();
final InvocationListener invocationListener = new InvocationListener() {
#Override
public void reportInvocation(final MethodInvocationReport methodInvocationReport) {
final Method method = ((InvocationOnMock) methodInvocationReport.getInvocation()).getMethod();
methodReference.set(method);
}
};
final MockSettings mockSettings = Mockito.withSettings().invocationListeners(invocationListener);
final T mock = Mockito.mock(clazz, mockSettings);
invokeMock.accept(mock);
return methodReference.get();
} catch (final Exception e) {
throw new RuntimeException("Method could not be captured at runtime.", e);
}
}
}
So, I play with this code
import sun.reflect.ConstantPool;
import java.lang.reflect.Method;
import java.util.function.Consumer;
public class Main {
private Consumer<String> consumer;
Main() {
consumer = this::test;
}
public void test(String val) {
System.out.println("val = " + val);
}
public void run() throws Exception {
ConstantPool oa = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(consumer.getClass());
for (int i = 0; i < oa.getSize(); i++) {
try {
Object v = oa.getMethodAt(i);
if (v instanceof Method) {
System.out.println("index = " + i + ", method = " + v);
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) throws Exception {
new Main().run();
}
}
output of this code is:
index = 30, method = public void Main.test(java.lang.String)
And as I notice index of referenced method is always 30.
Final code may look like
public Method unreference(Object methodRef) {
ConstantPool constantPool = sun.misc.SharedSecrets.getJavaLangAccess().getConstantPool(methodRef.getClass());
try {
Object method = constantPool.getMethodAt(30);
if (method instanceof Method) {
return (Method) method;
}
}catch (Exception ignored) {
}
throw new IllegalArgumentException("Not a method reference.");
}
Be careful with this code in production!
Try this
Thread.currentThread().getStackTrace()[2].getMethodName();