2009-09-17 4 views
5

Aujourd'hui, j'ai découvert que l'utilisation de Collections.synchronizedXXX ne fonctionne pas correctement avec la réflexion.Solution de contournement pour les wrappers de collections Java qui interrompent la réflexion

Voici un exemple simple:

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(); 
    } 
    } 
} 

Le premier appel à printSizeUsingReflection imprime la taille (c.-à "1"), les résultats du deuxième appel à:

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) 

Ceci est un peu surprenant et ennuyeux. Y a-t-il une bonne solution de contournement? Je sais qu'il existe une implémentation de liste thread-safe dans java.util.concurrent, mais cette implémentation semble plus lente que l'utilisation de Collections.synchronizedList().

Répondre

5

La raison de l'exception levée est que la classe obtenue par l'invocation

list.getClass() 

dans la ligne

System.out.println("size = " + list.getClass().getMethod("size").invoke(list)); 

retourne le type réel - java.util.Collections $ SynchronizedCollection. La classe SynchronizedCollection se trouve être une classe interne de la classe Collections, à laquelle vous ne pouvez pas accéder, d'où l'exception. Le meilleur moyen de contourner cette exception est d'invoquer la méthode size sur une classe/interface plus appropriée - en général, il s'agit de la classe d'implémentation (puisque cela contiendrait certainement la déclaration ou la définition de la méthode), mais dans notre cas nous devons appeler size() sur un type public étant donné que obj.getClass() a retourné un type non-public. Pour être précis, nous devons appeler size() sur l'interface java.util.Collection (super) ou l'interface java.util.List.

Les énoncés suivants imprimer "size = 1" dans la console: méthode

System.out.println("size = " + Collection.class.getMethod("size").invoke(list)); 
System.out.println("size = " + List.class.getMethod("size").invoke(list)); 

Mise à jour

Dans le cas où vous vous demandiez si la taille() invoquée par réflexion est synchronisé ou non, la réponse est oui, elle est synchronisée. Malgré l'appel de la méthode size() sur le super type - Collection/List, la méthode size() de la classe interne SynchronizedCollection est invoquée, et celle-ci est synchronisée. Ceci est un détail d'implémentation, garanti pour fonctionner puisque la collection est enveloppée. En outre, il n'est pas une bonne idée d'utiliser la réflexion lorsque the supertype - the Collection interface contains the size() method.

3

Obtenir la méthode de Collection.class (plus généralement, itérer les super classes (et les interfaces) pour trouver quelque chose de public). Ou n'utilisez simplement pas la réflexion.

+0

+1 n'utilisez simplement pas de réflexion. 'list.size()' fonctionne bien dans ces situations. – akf

+0

J'éviterais la réflexion si je le pouvais. Dans ce cas, j'ai un intercepteur AOP mis en place par Google Guice, et j'essaie d'enregistrer des choses utiles sur les arguments de la méthode - malheureusement, les types de classe sont essentiellement le seul que je connaisse sur les arguments. Merci pour le conseil Collection.class .. fonctionne comme un charme! –

0

Êtes-vous sûr les choix java.util.concurrent seraient plus lents (ou, assez lent pour s'inquiéter?).

je suppose que vos 2 choix il y a:

  1. LinkedBlockingQueue
  2. CopyOnWriteArrayList

Je pense que pour certains cas d'utilisation, ces mises en œuvre peut effectivement être plus rapide . Et en remarque, si vous en avez besoin, les collections de java.util.concurrent peuvent gérer les modifications simultanées.

Dans la section Concurrent Collections de « Java Concurrency en pratique »:

CopyOnWriteArrayList est une concurrente remplacement d'une liste synchronisée qui offre une meilleure concurrence dans certains situations communes et élimine le besoin verrouiller ou copier la collection pendant l'itération. (De même, CopyOnWriteArraySet est un remplacement simultané pour un ensemble synchronisé.)

1

java.util.Collections $ SynchronizedCollection est classe non publique.

Questions connexes