2015-12-02 3 views
1

Dans mon cadre, j'ai une classe comme ceci:Est-ce que MethodHandle peut être utilisé par les frameworks/bibliothèques (au lieu de la réflexion traditionnelle)?

public class Foo<B, V> { 
    private final Method getterMethod; 
    ... 

    public V executeGetter(B bean) { 
     try { 
      return getterMethod.invoke(bean); 
     } catch ... 
    } 
} 

Cette classe est utilisée pour appeler apporteurs de classes créées par les utilisateurs qui ne sont pas disponibles au moment de la compilation de mon cadre. Par exemple, B peut être une classe appelée Person. Grâce au profilage, j'ai découvert que cette méthode est horriblement lente. Le Method.invoke() prend 40% de performance dans le profilage d'échantillonnage (même avec setAccessible(true)), tandis qu'une implémentation non réfléchissante prend seulement une petite fraction de cette performance.

Je tiens à le remplacer est un MethodHandle:

public class Foo<B, V> { 
    private final MethodHandle getterMethodHandle; 
    ... 

    public V executeGetter(B bean) { 
     try { 
      return getterMethodHandle.invoke(bean); 
     } catch ... 
    } 
} 

Mais je reçois cette exception:

java.lang.ClassCastException: Cannot cast [Ljava.lang.Object; to Person 
    at sun.invoke.util.ValueConversions.newClassCastException(ValueConversions.java:461) 
    at sun.invoke.util.ValueConversions.castReference(ValueConversions.java:456) 
    at ...Foo.executeGetter(Foo.java:123) 

même si bean est une instance de Person. Maintenant, la partie trompeuse est qu'il essaie de lancer un Object[] (et non un Object) à Person. Notez que l'enveloppant dans un tableau d'objets (ce qui est une perte de performance) ne permet pas:

return getterMethodHandle.invoke(new Object[]{bean}); // Same exception 

Est-il possible d'obtenir le MethodHandle de travailler dans cette situation?

+0

Pouvez-vous ajouter la pile complète? – BobTheBuilder

+0

Comment avez-vous construit 'getterMethodHandle'? – Tunaki

+0

Stacktrace ajouté. Utiliser JDK 7 pour l'enregistrement. –

Répondre

1

Ce ClassCastException ne se produit que si vous compilez avec la source/niveau cible java 6.

Compile avec la source/niveau cible 7 ou plus pour éviter que ClassCastException.

Réponse trouvée grâce à la réponse de Tagir. (voter sa réponse aussi)

1

L'utilisation de MethodHandles dans le code framework/library est parfaitement bien et je ne vois aucun problème dans votre code. Cet exemple fonctionne très bien:

import java.lang.invoke.MethodHandle; 
import java.lang.invoke.MethodHandles; 
import java.lang.invoke.MethodType; 

public class Foo<B, V> { 
    private final MethodHandle getterMethodHandle; 

    public Foo(MethodHandle mh) { 
     this.getterMethodHandle = mh; 
    } 

    public V executeGetter(B bean) { 
     try { 
      return (V) getterMethodHandle.invoke(bean); 
     } catch(RuntimeException | Error ex) { 
      throw ex; 
     } catch(Throwable t) { 
      throw new RuntimeException(t); 
     } 
    } 

    static class Pojo { 
     String x; 

     public Pojo(String x) { 
      this.x = x; 
     } 

     public String getX() { 
      return x; 
     } 
    } 

    public static void main(String[] args) throws Exception { 
     // I prefer explicit use of findXYZ 
     Foo<Pojo, String> foo = new Foo<>(MethodHandles.lookup() 
       .findVirtual(Pojo.class, "getX", MethodType.methodType(String.class))); 
     // Though unreflect also works fine 
     Foo<Pojo, String> foo2 = new Foo<>(MethodHandles.lookup() 
       .unreflect(Pojo.class.getMethod("getX"))); 

     System.out.println(foo.executeGetter(new Pojo("foo"))); 
     System.out.println(foo2.executeGetter(new Pojo("bar"))); 
    } 
} 

La sortie est:

foo 
bar 

Pour de meilleures performances envisager d'utiliser invokeExact, même si elle ne vous permettra pas les conversions de type automatique comme unboxing.

+0

Très intéressant. En effet, vous ne compilez pas de référence de temps Pojo dans la méthode executeGetter. Je dois étudier pourquoi cela ne fonctionne pas dans mon cas. –

+0

Dans JDK 1.7 avec le niveau source et le niveau cible Java 6, ce code * renvoie la même exception *: 'java.lang.RuntimeException: java.lang.ClassCastException: impossible de lancer [Ljava.lang.Object; à org.optaplanner.core.impl.domain.common.Foo $ Pojo à ... Foo.executeGetter (Foo.java:34) ' –

+0

Et ** avec le même JDK (1.7) mais source et niveau cible Java 7 il fonctionne ** (contrairement à la source/cible Java 6). –