How to pass an anonymous Class (not an instance) as a parameter to a method expecting type Class in Java
I have to call a method that takes a Class
parameter:
public action(Class<? extends SomeInterface> classVaria开发者_Python百科ble);
I am able to do this:
action(new SomeInterface() {
// implement interface
}.getClass());
But, can I get away without instantiating the object and calling the getClass()
?
How would you access an anonymous class without an instance? You would need a name to refer to it somehow, which it by definition does not have (sure it will have some compiler generated name at runtime, but to query that you need to first create it, and you can't create it without instantiating it).
So there is no way to achieve what you want.
(Long form of @PeterLawrey's answer)
The problem is that there is no syntax that defines an inner class without also instantiating it. My personal feeling is that this means it isn't properly supported by the language, and you shouldn't do it.
But technically, it's possible. Even if it is never executed, an anonymous inner class definition/instantiation will still be compiled and result in a class file. So, if you're willing to hack around and "guess" the name generated for the anonymous inner class, you can get its Class<?>
instance with Class.forName
.
import java.util.Arrays;
class X {
@SuppressWarnings("unchecked")
private Class<? extends Runnable> getAnon() {
if (true) { // Circumvent dead code compiler error
String innerName = getClass().getName() + "$1";
System.out.println("Expect autogenerated inner class name to be " + innerName);
try {
return (Class<? extends Runnable>)Class.forName(innerName);
} catch(ClassNotFoundException e) {
throw new RuntimeException("CompileTimeException!", e);
}
}
// Here's our anonymous inner
// And be careful:
// if you stuff it into the else branch of the if above,
// the class file won't get created
new Runnable() {
{
System.out.println("Inner class instantiated!");
}
public void run() {
System.out.println("My outer is \"" + name + "\".");
}
};
// If you had multiple anonymous inner classes, the order would matter...
return null;
}
final String name;
X(String name) {
this.name = name;
}
public static void main(final String... arguments) throws Exception {
Class<? extends Runnable> clazz = new X("This won't be used").getAnon();
System.out.println("Ctors: " + Arrays.toString(clazz.getDeclaredConstructors()));
clazz.getDeclaredConstructor(X.class).newInstance(new X("magic")).run();
}
}
This should print:
Expect autogenerated inner class name to be X$1
Ctors: [X$1(X)]
Inner class instantiated!
My outer is "magic".
Note how only one instance of the inner class is constructed, and the instance of X
that getAnon
is called on is not the one that the anonymous class is instantiated with.
So we can refute some other arguments that said this should be impossible:
- You need a name to refer to the class (@PéterTörök): The
Class
instance stored inclazz
is sufficient. (But we did have to cheat with the generated nameX$1
.) - Inner classes need an outer class for instantiation (@PaŭloEbermann): All variables that the inner class "captures" become parameters to its generated constructor.
this
behaves the same way. (Try adding a parameter togetAnon
and print it fromrun
.)
Actually there's a way to achieve this without relying on reflection hacks, in other words, without needing to guess the generated name.
Basically we can exploit the ternary operator to cast a null instance into an anonymous class, and then call a generic method with that null instance to retrieve the class (by using the generic varargs trick):
class X {
@SuppressWarnings("unchecked")
private static <T> Class<?> getAnon(T instance, T... dummyArray) {
return dummyArray.getClass().getComponentType();
}
public static void main(final String... arguments) throws Exception {
// Apparently there's a compiler bug if we just use "false",
// so I'm using an always-false condition
Class<?> clazz = getAnon(1 < 0 ? new Runnable() { { System.out.println("Inner class instantiated!"); } void run() { } } : null);
System.out.println(clazz);
}
}
This should print the name of the anonymous class, without printing "Inner class instantiated!"
EDIT: Using an always-false condition causes the compiler to not generate the class file for the anonymous class. So you must use a condition that the compiler can't assure to be false (a public boolean variable should work, not sure if private also works)
You should be able to do the following:
public void action(Class<? extends SomeInterface> classVariable);
then call:
action(new SomeInterface() { // implement interface }.getClass());
Example code:
interface I {}
class A implements I
{
}
public void action(Class<? extends I> c)
{
}
public void test()
{
action(new I() {}.getClass());
}
Anonymous classes cannot be safely referred to by name, only but instance. You can predict the name and use that but its not elegant, or reliable.
private void neverCalled() {
SomeInterface si = new SomeInterface() {
// implement interface
};
}
Class anon = Class.forName(getClass()+"$1"); // hopefully its the first!
It is much simpler to give the class a name and use that. Many IDEs allow you to change one into the other very easily.
Maybe you're looking for this?
action(SomeInterface.class);
I think this could be what you're looking for, but this doesn't involve anonymous classes at all.
It might help to have more details on what the method action
does with the class reference.
精彩评论