Last active
May 18, 2022 03:01
-
-
Save keturn/180a57f2f6069470556137bd06b4025d to your computer and use it in GitHub Desktop.
Java: Get a lambda's parameter types
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.ObjectOutputStream; | |
import java.io.OutputStream; | |
import java.io.Serializable; | |
import java.lang.invoke.MethodType; | |
import java.lang.invoke.SerializedLambda; | |
import java.lang.reflect.Method; | |
import java.security.AccessController; | |
import java.security.PrivilegedActionException; | |
import java.security.PrivilegedExceptionAction; | |
import java.util.ArrayList; | |
import java.util.List; | |
class SpyingOutputObjectStream extends ObjectOutputStream { | |
private final List<Object> emittedObjects = new ArrayList<>(); | |
SpyingOutputObjectStream(OutputStream out) throws IOException { | |
super(out); | |
enableReplaceObject(true); | |
} | |
@Override | |
protected Object replaceObject(Object obj) { | |
emittedObjects.add(obj); | |
return null; | |
} | |
List<Object> getEmittedObjects() { | |
return List.copyOf(emittedObjects); | |
} | |
/** | |
* This technique, obtuse as it is, does not require overriding anything marked private. | |
*/ | |
static SerializedLambda getSerializedLambdaFromOutputStream(Serializable obj) { | |
List<Object> outputObjects; | |
try (var output = new SpyingOutputObjectStream(new ByteArrayOutputStream())) { | |
try { | |
output.writeObject(obj); | |
} finally { | |
outputObjects = output.getEmittedObjects(); | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
return (SerializedLambda) outputObjects.get(0); | |
} | |
/** | |
* This relies on {@link Method#setAccessible} on a private final field. | |
* <p> | |
* Is that bad? You might think so, but {@link Serializable} documentation says that | |
* Serializable objects may have that method <em>even though</em> it is not defined in | |
* the interface. So it's probably okay? | |
*/ | |
static SerializedLambda getSerializedLambdaDirectly(Serializable obj) { | |
Method m; | |
try { | |
m = obj.getClass().getDeclaredMethod("writeReplace"); | |
} catch (NoSuchMethodException e) { | |
throw new RuntimeException(e); | |
} | |
try { | |
PrivilegedExceptionAction<?> act = () -> { | |
m.setAccessible(true); | |
return m.invoke(obj); | |
}; | |
Object replacement = AccessController.doPrivileged(act); | |
return (SerializedLambda) replacement; | |
} catch (PrivilegedActionException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
static List<Class<?>> getLambdaParameters(SerializedLambda serializedLambda) { | |
var methodType = MethodType.fromMethodDescriptorString( | |
serializedLambda.getImplMethodSignature(), | |
serializedLambda.getClass().getClassLoader() | |
); | |
return methodType.parameterList(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment