Es gibt einen Konsens , dass die Verwendung von Schnittstellen besser ist als die Verwendung von Klassen. Ich bin mir sicher: Eine Bibliotheksmethode, die ArrayList
anstelle von List
akzeptiert, wäre ein Mist.
Es besteht auch Einigkeit darüber, dass die Leistung immer gleich ist. Hier ist mein Benchmark zu unterscheiden. Es gibt 1 bis 4 Implementierungen von sowohl einer Schnittstelle als auch einer abstrakten Klasse. Wenn mehr als zwei Implementierungen verwendet werden, beginnt die Leistung zu divergieren. Ich suche nach einer Erklärung für dieses Verhalten (und auch für den Ursprung des falschen Konsenses).
Es besteht Einigkeit darüber, dass die Verwendung von Schnittstellen besser ist als die Verwendung von Klassen.
Das ist zu einfach. Sowohl Schnittstellen als auch abstrakte Klassen haben Vorteile übereinander.
Die Antwort, auf die Sie verlinken, schlägt vor, Variablen als java.util.List und nicht als java.util.ArrayList zu deklarieren, wo dies möglich ist. Es ist richtig, dass die Verwendung von List Ihnen mehr Flexibilität bietet, um später eine andere Implementierungsklasse zu wählen. Daher ist es eine gute Sache, wenn Sie keine ArrayList-spezifischen Methoden (z. B. .trimToCapacity()
) benötigen. Dieser Hinweis hat jedoch nichts mit Interfaces oder Klassen im Allgemeinen zu tun und wäre genauso wahr, wenn java.util.List eine abstrakte Klasse wäre.
Es gibt auch einen Konsens, dass die Leistung immer die gleiche ist.
Der populäre Rat lautet, dass man sich keine Gedanken über Leistungsunterschiede zwischen Klassen und Interfaces machen sollte und stattdessen zwischen ihnen basierend auf guten Programmierungsgrundsätzen wählen sollte. Dies ist ein guter Rat, um zu verhindern, dass Programmierer sich über unwichtige Leistungsunterschiede ärgern; Es wird jedoch manchmal missverstanden, dass es einen nein Unterschied gibt, was nicht wahr ist. Dort ist ein kleiner Unterschied: Klassen sind schneller.
Beim Methodenaufruf durch eine Klasse gibt es eine vtable mit einem festen Offset in der Klasse und den Zeiger Die gewünschte Methodenimplementierung findet sich bei einem bekannten Offset innerhalb dieser Tabelle, so dass der Sprung zum Ziel recht einfach ist. Während eine Klasse nur eine Oberklasse erweitern kann, kann eine Klasse jedoch eine beliebige Anzahl von Schnittstellen implementieren, sodass Methodenaufrufe über eine Schnittstelle komplizierter sind. Bei Schnittstellenaufrufen muss sie zunächst die Schnittstellenliste der Klasse nachschlagen, um die gewünschte Schnittstelle zu finden, bevor sie die Methodenimplementierung in der Tabelle dieser Schnittstelle nachschlagen kann.
Wenn mehr als zwei Implementierungen verwendet werden, beginnt die Leistung zu divergieren.
Unabhängig davon, ob Klassen oder Schnittstellen verwendet werden, verursacht der polymorphe Aufruf eine Pipeline-Flushung auf der CPU, da die CPU das Sprungziel nicht im Voraus erkennen kann und dies hohe Kosten verursacht. Wenn die Call-Site zur Laufzeit oligomorph ist ( oligo bedeutet "wenig"), steigt die Leistung stark an, da eine gute JVM diese Fälle besonders behandelt. Für den monomorphen Fall kann die JVM direkt zur Einzelzielmethode springen oder sie sogar inline machen. Für den dimorphen Fall implementiert es o.f();
als ob durch (nicht gültige Syntax): if (o.getClass() == A.class) A::f(o) else B::f(o);
.
Ich bin mir eigentlich nicht sicher, warum der dimorphe Fall in Ihrem Benchmark so schnell zu sein scheint wie der monomorphe Fall - sollte der Verzweigungs-Prädiktor der CPU die Hälfte der Zeit nicht mit zufälligen Daten verfälschen? Vielleicht gibt es andere Feinheiten bei der Arbeit ...
Siehe auch:
Tags und Links java performance interface abstract-class