2015-01-23 5 views
15

Ich habe einen Code wie folgt:Java Generics: Ist T-Argument kann nicht in int durch Methodenaufruf Umwandlung umgewandelt werden

// This class cannot be changed 
class VendorApi { 
     static void func1(char x) {} 
     static void func1(int x) {} 
     static void func1(float x) {} 
     static void func1(double x) {} 
} 

class Main { 
      static <T> void my_func(T arg) { 
        // much of code, which uses T 
        // ... 
        VendorApi.<T>func1(arg); 
      } 

      public static void main(String args[]) { 
        // call my_func for each type (char, int, float, double) 
        // ... 
        int i = 1; 
        my_func(i); 
        char c = 1; 
        my_func(c); 
      } 
} 

Was ich tun muss, ist jede Funktion VendorApi.func() für jede Art anrufen des Arguments von my_func(). Der gepostete Code kompiliert nicht, er zeigt eine Idee. Wie kann ich es außer Kopieren-Einfügen my_func() für jeden Typ tun?

+5

Es klingt wie Sie für dynamische Überladungsauflösung suchen - oder etwas mehr wie C++ Templating. Keine davon existiert in Java. Eine Option ist Double-dispatch ... eine einzelne Methode, die 'Object' akzeptiert, die basierend auf dem tatsächlichen Typ die richtige' func1' -Überladung auslösen kann. Beachten Sie auch, dass Sie in Java keine tatsächlichen Primitive als generische Typargumente verwenden können. –

Antwort

4

Nicht die sauberste Antwort, aber es wird tun, was Sie fragen.

Sie können testen, ob Ihre generische Argumentklasse mit einem der Typen übereinstimmt, die von VenderApi bereitgestellt und dann umgewandelt werden.

-Code

public class Main { 
    static <T> void my_func(T arg) { 
     if (arg.getClass().equals(Integer.class)) 
      VendorApi.func1((Integer) arg); 
     else if (arg.getClass().equals(Character.class)) 
      VendorApi.func1((Character) arg); 
     else 
      throw new IllegalStateException(
        "cannot perform my_func on object of class " 
          + arg.getClass()); 
    } 

    public static void main(String args[]) { 
     // call my_func for each type (char, int, float, double) 
     // ... 
     int i = 1; 
     my_func(i); 
     char c = 1; 
     my_func(c); 
     String str = "bla"; 
     my_func(str); 
    } 
} 

Ihren Anbieter API

//This class cannot be changed 
public class VendorApi { 
    public static void func1(char x) { 
     System.out.println("i am a char "+x); 
    } 

    public static void func1(int x) { 
     System.out.println("i am a int "+x); 
    } 

    public static void func1(float x) { 
    } 

    public static void func1(double x) { 
    } 
} 

Ausgabe

i am a int 1 
i am a char 
Exception in thread "main" java.lang.IllegalStateException: cannot perform my_func on object of class class java.lang.String 
    at core.Main.my_func(Main.java:10) 
    at core.Main.main(Main.java:23) 
+1

Das funktioniert, aber es wird zum Kopieren-Einfügen führen. Die Funktion my_func() ist keine einzeilige Funktion, daher enthält sie für jeden Typ fast identische Codeteile. –

+2

@GennadyProskurin, das bedeutet, dass Sie 'my_func' umgestalten müssen, so dass es eine Zeile wird, und" identische Teile des Codes "sind in anderen Funktionen :). Das größte Problem ist nicht das, sondern die Tatsache, dass diese Implementierung nicht typsicher ist. Sie wollen nicht, dass eine generische Funktion Laufzeitausnahmen wirft, die sich darüber beschweren, dass der Typ falsch ist, da sie den Zweck von Generika vollständig vereitelt. Sie könnten sie auch einfach wie 'my_func (Object arg)' deklarieren. – Dima

+0

"Teile des Codes" sind nicht identisch, aber "fast" identisch. Sie verwenden Template-Argument, so dass alle Körper von my_func() in jedem if-Zweig mit kleinen Änderungen kopiert werden müssen. Das möchte ich vermeiden. –

3

Was Sie suchen, kann nicht in Java gemacht werden. Dies ist eine Problemumgehung.

// This class cannot be changed 
class VendorApi { 
    static void func1(char x) {} 
    static void func1(int x) {} 
    static void func1(float x) {} 
    static void func1(double x) {} 
} 

class Main { 
    static <T> void my_func(T arg) { 
     // much of code, which uses T 
     // ... 
     if(arg instanceof Character) { 
      VendorApi.func1((Character)arg); 
     } 
     else if (arg instanceof Integer) { 
      VendorApi.func1((Integer)arg); 
     } 
     //And so on... 
    } 

    public static void main(String args[]) { 
     // call my_func for each type (char, int, float, double) 
     // ... 
     int i = 1; 
     my_func(i); 
     char c = 1; 
     my_func(c); 
    } 
} 

Aber ich würde empfehlen, stattdessen Ihr Design zu überdenken.

2

Ich glaube, Sie so etwas wie dies wollen:

static <T> void my_func(T arg) { 
    // much of code, which uses T 
    // ... 
    if(arg instanceof Integer) { 
     VendorApi.func1((Integer) arg) 
    } else if(arg instanceof Double) { 
      ... 
    } else { 
     throw new IllegalArgumentException("..."); 
    } 
3

Wenn Sie nicht ändern können Ihre VendorApi, Ihre beste Wahl scheint es, um in einen allgemeinen Anruf Einwickeln zu werden. Dies ist ähnlich zu dem, was andere vorgeschlagen, aber mit weniger Doppelarbeit, und auch sicher geben (keine Laufzeit Ausnahmen, wenn das Argument eines falschen Typ ist):

class VendorAPIWrapper { 
    static <T extends Number> void func1(T arg) { 
     if(arg instanceof Double) VendorAPI.func1(arg.doubleValue()); 
     else if(arg instanceof Float) VendorAPI.func1(arg.floatValue()); 
     else VendorAPI.func1(arg.intValue()); 
    } 
    static void func1(char arg) { VendorAPI.func1(arg); } 
} 

Sie müssen die Definition von my_func ändern Beschränke den Typparameter auch, und dann kannst du es einfach in VendorAPIWrapper.func1(arg) tun. Das Problem ist, dass Character kein Number ist, also, um typsicher zu sein, benötigen Sie noch zwei Versionen der Funktion, eine für Zahlen und eine andere für Zeichen, es sei denn, Sie sind bereit, Zeichen in Ints oder Bytes vor zu konvertieren Berufung.

+0

"api wrapper" -Idee ist an sich interessant. Wie auch immer, es löst mein Problem nicht, ich sollte sowieso den gesamten Code in jedem if/else-Zweig kopieren. Dieser Code ist kein einzeiliger Aufruf von func1, es ist ein Code, der "arg" -Argument verwendet, daher kann ich den gemeinsamen Teil (ohne "arg") nicht herausrechnen. –

+0

@GennadyProskurin sicher, dass Sie den gemeinsamen Teil ausklammern können.Fügen Sie es einfach als weitere parametrisierte Funktion zur Wrapper-Klasse hinzu. – Dima

4

Eine weitere Option Reflexion und eine Zuordnung zu verwenden wäre:

Map<Class<?>, Method> mapping = new HashMap<>(); 
mapping.put(Integer.class, VendorApi.class.getMethod("func1", int.class)); 
// more mappings here 

Obwohl Sie so viel Code benötigen, wie in einer if/else Konstrukt, diese Zuordnung auch programmatisch gefüllt werden konnte (Sie um eine Schleife laufen konnte VendorApi.class.getMethods()) oder Sie könnten die Konfiguration aus einer Datei lesen. Alles in allem ist eine solche Abbildung flexibler.

Jetzt können Sie es für den Aufruf der API verwenden:

static void callVendorFunc(Object arg) { // no need for generics here 
    mapping.get(arg.getClass()).invoke(null, arg); 
} 

Und Ihre Methode wird, dass:

static <T> void my_func(T arg) { 
    // much of code, which uses T 
    // ... 
    callVendorFunc(arg); 
} 

ich nicht um jede Ausnahme nahmen.Und natürlich ist der Reflexionsansatz etwas weniger performant.

6

Sie könnten func1 in das Verfahren als Consumer<T> passieren:

class VendorApi { 
    static void func1(char x) {} 
    static void func1(int x) {} 
    static void func1(float x) {} 
    static void func1(double x) {} 
} 

class Main { 
     static void my_func(char arg) { my_func(arg, VendorApi::func1); } 
     static void my_func(int arg) { my_func(arg, VendorApi::func1); } 
     static void my_func(float arg) { my_func(arg, VendorApi::func1); } 
     static void my_func(double arg) { my_func(arg, VendorApi::func1); } 
     private static <T> void my_func(T arg, Consumer<T> func1) { 
      // much of code, which uses T 
      // ... 
      func1.accept(arg); 
     } 

     public static void main(String args[]) { 
      // call my_func for each type (char, int, float, double) 
      // ... 
      int i = 1; 
      my_func(i, VendorApi::func1); 
      char c = 1; 
      my_func(c); 
     } 
} 

Dies gibt Ihnen Zeit Typsicherheit kompilieren (nur my_func mit char, nennen int, float und double von außerhalb der Klasse, da die generische Version ist privat) und vermeidet Reflexionen.

Auch my_func sollte myFunc sein, wenn Sie die Namenskonventionen für Java-Methoden befolgen möchten.

1

aufgeteilt einfach Ihre Methode in den generischen Teil und der Invokation Teil:

public class Main { 
    static void my_func(char arg) { 
     my_funcGenericPart(arg); 
     VendorApi.func1(arg); 
    } 
    static void my_func(int arg) { 
     my_funcGenericPart(arg); 
     VendorApi.func1(arg); 
    } 
    static void my_func(float arg) { 
     my_funcGenericPart(arg); 
     VendorApi.func1(arg); 
    } 
    static void my_func(double arg) { 
     my_funcGenericPart(arg); 
     VendorApi.func1(arg); 
    } 
    private static <T> void my_funcGenericPart(T arg) { 
      // much of code, which uses T 
      // ... 

      // the caller will invoke the right VendorApi.func1(arg); 
    } 

    public static void main(String args[]) { 
      // call my_func for each type (char, int, float, double) 
      // ... 
      int i = 1; 
      my_func(i); 
      char c = 1; 
      my_func(c); 
    } 
} 

Beachten Sie, dass Ihre Art Parameter T zwecklos hier ist, können Sie einfach das Argument Ihrer generischen Methode als Object erklären (oder Number) ohne Unterschied.

Dies ist konzeptionell nahe Alex’ answer, erfordert aber nicht Java 8. Aber seine Antwort hat den Vorteil, dass der generische Code wählen kann, an welcher Stelle VendorApi.func1 anrufen, während diese einfache Lösung nur vor oder nach dem generischen Teil aufrufen erlaubt ...

Verwandte Themen