Today, I discovered that using Collections.synchronizedXXX doesn't play well with reflection.
Here's a simple example:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Weird{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("Hello World");
List<String> wrappedList = Collections.synchronizedList(list);
printSizeUsingReflection(list);
printSizeUsingReflection(wrappedList);
}
private static void printSizeUsingReflection(List<String> list) {
try {
System.out.println(
"size = " + list.getClass().getMethod("size").invoke(list));
} catch (Exception e) {
e.printStackTrace();
}
}
}
The first call to printSizeUsingReflection prints the size (i.e. "1"), the second call results in:
java.lang.IllegalAccessException: Class Weird can not access a member of class
java.util.Collections$SynchronizedCollection with modifiers "public"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
at java.lang.reflect.Method.invoke(Method.java:588)
at Weird.printSizeUsingReflection(Weird.java:18)
at Weird.main(Weird.java:13)
This is a little surprising and annoying. Is there a good workaround? I know there is a thread-safe List implementation in java.util.concurrent, but that implementation seems slower than using Collections.synchronizedList().
The reason for the exception being thrown is that the class obtained by the invocation
list.getClass()
in the line
System.out.println("size = " + list.getClass().getMethod("size").invoke(list));
returns the actual type - java.util.Collections$SynchronizedCollection. The SynchronizedCollection class happens to be an inner class of the Collections class, which you cannot access, hence the exception.
The best way to bypass this exception is to invoke the size method on a more suitable class/interface - usually this happens to be the implementation class (since that would definitely contain the method declaration or definition), but in our case we need to invoke size() on a public type given that obj.getClass() has returned a non-public type. To be specific, we need to invoke size() on the java.util.Collection (super)interface or the java.util.List interface.
The following statements will print out "size = 1" in the console:
System.out.println("size = " + Collection.class.getMethod("size").invoke(list));
System.out.println("size = " + List.class.getMethod("size").invoke(list));
Update
In case you were wondering whether the size() method invoked via reflection is synchronized or not, the answer is yes, it is synchronized. Despite invoking the size() method on the super type - Collection/List, the size() method in the SynchronizedCollection inner class gets invoked, and this happens to be synchronized. This is an implementation detail though, guaranteed to work since the collection is wrapped.
Besides, it it not a good idea to use reflection when the supertype - the Collection interface contains the size() method.
Get the method from Collection.class
(more generally iterate up super classes (and interfaces) to find something public). Or just don't use reflection.
java.util.Collections$SynchronizedCollection is non-public class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With