2013-04-20 12 views
19

Ich habe ein schlecht erstellten Containerobjekt, das zusammen Wert verschiedenen Java-Typen hält (String, Boolean etc ..)Java-Generika und typecasting

public class BadlyCreatedClass { 
    public Object get(String property) { 
     ...; 
    } 
}; 

Und wir extrahieren Werte von dem auf diese Weise

String myStr = (String) badlyCreatedObj.get("abc"); 
Date myDate = (Date) badlyCreatedObj.get("def"); 

Ich bin gezwungen, einen neuen Code mit diesem Objekt zu schreiben und ich versuche zu sehen, ob es einen sauberen Weg gibt, dies zu tun. Genauer gesagt, welches der folgenden Verfahren wird bevorzugt?

Explicit Guss

String myStr = (String) badlyCreatedObj.get("abc") 
Date myDate = (Date) badlyCreatedObj.get("def"); 

Die Verwendung generischer Guss

public <X> X genericGet(String property) { 

} 

public String getString(String property) { 
return genericGet(property); 
} 

public Date getDate(String property) { 
return genericGet(property); 
} 

Mit Class.cast

<T> T get(String property, Class<T> cls) { 
    ; 
} 

Ich habe durch viele verwandte Fragen über SO gegangen Java generic function: how to return Generic type, Java generic return type alle von ihnen scheinen zu sagen, die solche Typumwandlung ist gefährlich, obwohl ich nicht viel Unterschied zwischen den drei sehe, angesichts dieser Methode, die Sie bevorzugen würden?

Dank

Antwort

3

eine schnelle Antwort zu geben, ohne sich um gute Programmierpraxis in tief zu gehen ...

Ich würde verwenden:

private <X> X genericGet(String property) { 

} 

public String getString(String property) { 
//... checks on property (String specific)... 
Object obj = genericGet(property); 
//... checks if obj is what is expected and if good return it 
return obj; 
} 

public Date getDate(String property) { 
//... checks on property (Date specific)... 
Object obj = genericGet(property); 
//... checks if obj is what is expected and if good return it 
return obj 
} 

make Mitteilung an den privaten genericGet. Auf diese Weise kann ich prüfen, ob die get-Eigenschaft darauf wartet, sie zu empfangen und korrekt zu verarbeiten.

Ich könnte Prüfungen in der GetString abhängig von der Eigenschaft hinzufügen, um sicherzustellen, dass die Antwort ein String-Objekt sein wird.

Ich könnte andere Überprüfungen für getDate in der Eigenschaft, um sicherzustellen, dass es ein Datum sein wird, wie zurückgegeben wird.

Etc ...

1

ich lieber generische Besetzung verwenden. Warum?

  • Die explizite Umwandlung ist immer schwieriger zu warten. Wenn Sie den Code lesen, wissen Sie nicht, was diese Methode zurückgibt. Darüber hinaus ist die Wahrscheinlichkeit, dass die Methode in falscher Weise verwendet wird und einige ClassCastException während Runnimme auftreten, ziemlich hoch.

  • Classcast ist noch schwieriger zu warten. Meiner Meinung nach erschafft man auf diese Weise etwas, was man als Spaghetti-Code bezeichnen könnte.

  • Wenn Sie Methoden wie getString und getDate erstellen, geben Sie sehr klare Schnittstelle zu Ihrer Klasse. Außerdem ist es immer möglich, Objekte anderer Klassen als String und Date zu erhalten, da Sie auch die generische Methode angeben.

  • 1

    Wie bereits erwähnt, sind alle oben genannten Ansätze gefährlich und können während der Laufzeit zu ClassCastException s führen.

    Wenn es wirklich notwendig ist, würde ich die ‚generic cast‘ Ansatz bevorzugen, da sie die Schnittstelle explizit und Selbst expanatory macht (machen die genericGet privat in diesem Fall). Natürlich müssen Sie in Ihrem Container für jeden class Code erstellen. Der Vorteil von 'Class.cast' ist, dass Sie diese Methoden nicht benötigen.

    Fazit: Wenn es eine genau definierte Anzahl von Klassen im Container gibt, würde ich mit "generic cast" gehen. Wenn Sie eine endlose Anzahl von Klassen unterstützen müssen, würde ich mit 'Class.cast' gehen

    Update: 'Explicit Cast' hat einen Vorteil - der Anrufer (Benutzer des Containers) bekommt eine Erinnerung dass es ein Klassenrisiko gibt!

    nur eine Meinung ...

    3

    persönlich viele verschiedene Objekte in einem einzigen Ort zu halten und dann überprüft, was Sie zurückkehren wollen scheint ein bisschen falsch. Möglicherweise können Sie Inhaber in dieser BadlyCreatedClass speichern.

    Etwas wie:

    class Holder { 
        private long id; 
        private String name; 
        private Date dateofBirth; 
    
        //getters and setters 
    } 
    

    Dann auf id basiert retrieve, damit kein Gießen erforderlich.

    Sie könnten uns auch sagen, was Sie zu tun versuchen.

    1

    Da alle Optionen eine Typumwandlung beinhalten, sind sie alle irgendwie "unsicher" und können mit ClassCastExceptions fehlschlagen.

    Ich würde auf jeden Fall empfehlen, Hilfsmethoden wie getString(), getDate() für häufige Typen zu verwenden, die oft in diesem Objekt gespeichert sind. Diese Methoden sind bei allen drei Optionen nützlich, da sie den Code reduzieren, den der Benutzer des Objekts schreiben muss.

    Aber Sie brauchen immer noch einen Weg, um "ungewöhnliche" Typen von Ihrem Objekt zu erhalten. Dafür würde ich mit der expliziten Besetzung oder der Klassenbesetzung gehen. Der Grund dafür ist, dass ich denke, dass diese beiden Wege die am häufigsten verwendeten sind. Selbst wenn der generische Methodenaufruf funktionieren würde, denke ich, Methodenaufrufe wie obj.genericGet<String>("myproperty"); sind etwas, das nicht jedem Java-Entwickler bekannt ist. Es wird selten in der Praxis gesehen.

    Persönlich ist würde hinzufügen, eine getObject() Methode zu den Hilfsmethoden, auch wenn ich nicht möchte, um den Typ umgewandelt, um den Benutzer des Objekts zu übertragen. Der Vorteil davon ist, dass Sie eine konsistente Schnittstelle haben würden, und das ist, was ich erwarten würde, wenn ich Methoden wie getString() oder getDate() sehe.

    3

    Der generische Cast-Ansatz bewirkt, dass der Compiler eine ungeprüfte Warnung ausgibt. Eine ungeprüfte Warnung weist darauf hin, dass die Cast-Frage zur Laufzeit nicht (vollständig) überprüft wird, d. H. Sie kann erfolgreich sein, selbst wenn der Wert nicht vom richtigen Typ ist. Dies kann dazu führen, dass eine Variable einen Wert enthält, der nicht mit ihrem deklarierten Typ kompatibel ist, eine Situation, die die Java-Sprachspezifikation heap pollution aufruft.

    Das folgende Programm veranschaulicht dies:

    class Holder { 
        Object value; 
    
        Holder(Object value) { 
         this.value = value; 
        } 
    
        <T> T get() { 
         return (T) value; 
        } 
    } 
    
    class C<T> { 
        T value; 
    
        C(Holder h) { 
         value = h.get(); 
        } 
    } 
    
    public class Test { 
        public static void main(String [] args) throws IOException { 
         Holder holder = new Holder("Hello"); 
         C<Integer> c = new C<Integer>(holder); 
         System.out.println("I just put a String into a variable of type Integer"); 
    
         // much later, possibly in a different part of your program 
         c.value.longValue(); // throws ClassCastException 
        } 
    } 
    

    Daher empfehle ich dringend ein getestetes Besetzung mit. Sowohl die normale Besetzung (Ihre erste Annäherung) als auch die reflektierende Besetzung (Ihre dritte Herangehensweise) werden überprüft. Der Reflection Cast funktioniert jedoch nicht mit parametrisierten Typen (List<String>.class kompiliert nicht ...).

    Die einfachste und flexibelste sichere Lösung ist daher der normale Guss.

    Verwandte Themen