views:

278

answers:

4

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().

+3  A: 

Get the method from Collection.class (more generally iterate up super classes (and interfaces) to find something public). Or just don't use reflection.

Tom Hawtin - tackline
+1 just don't use reflection. `list.size()` works well in these situations.
akf
I'd avoid reflection if I could. In this case, I have an AOP interceptor set up by Google Guice, and I'm trying to log useful things about method arguments -- unfortunately, class types are basically the only think I know about the arguments.Thank you for the Collection.class tip.. works like a charm!
Leo P
+4  A: 

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.

Vineet Reynolds
A: 

Are you sure the java.util.concurrent choices would be slower (or, slower enough to worry about?).

I guess your 2 choices there are:

  1. LinkedBlockingQueue
  2. CopyOnWriteArrayList

I think for certain use cases, these implementations may actually be faster. And as a side note, if you eventually need it, the collections in java.util.concurrent can handle concurrent modification.

From the Concurrent Collections section of "Java Concurrency In Practice":

CopyOnWriteArrayList is a concurrent replacement for a synchronized List that offers better concurrency in some common situations and eliminates the need to lock or copy the collection during iteration. (Similarly, CopyOnWriteArraySet is a concurrent replacement for a synchronized Set.)

dirtyvagabond
+1  A: 

java.util.Collections$SynchronizedCollection is non-public class.

adatapost