La meilleure pratique de «programmation aux interfaces» s’applique-t-elle aux variables locales?

On m’a dit que programmer des interfaces pour des variables locales est inutile et ne devrait pas être fait car cela nuit aux performances et n’offre aucun avantage.

public void foo() { ArrayList numbers = new ArrayList(); // do list-y stuff with numbers } 

au lieu de

 public void foo() { List numbers = new ArrayList(); // do list-y stuff with numbers } 

J’ai l’impression que l’impact sur les performances est négligeable, mais il est vrai que l’utilisation de la sémantique List de ArrayList ne présente aucun avantage. Existe-t-il de bonnes raisons d’aller dans un sens ou dans l’autre?

Prenez ce cours par exemple:

 public class Tmp { public static void main(Ssortingng[] args) { ArrayList numbers = new ArrayList(); numbers.add(1); } } 

Il comstack jusqu’à ceci:

 Comstackd from "Tmp.java" public class Tmp extends java.lang.Object{ public Tmp(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public static void main(java.lang.Ssortingng[]); Code: 0: new #2; //class java/util/ArrayList 3: dup 4: invokespecial #3; //Method java/util/ArrayList."":()V 7: astore_1 8: aload_1 9: iconst_1 10: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 13: invokevirtual #5; //Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 16: pop 17: return } 

Alors que cette classe:

 public class Tmp { public static void main(Ssortingng[] args) { List numbers = new ArrayList(); numbers.add(1); } } 

comstack jusqu’à ceci:

 Comstackd from "Tmp.java" public class Tmp extends java.lang.Object{ public Tmp(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public static void main(java.lang.Ssortingng[]); Code: 0: new #2; //class java/util/ArrayList 3: dup 4: invokespecial #3; //Method java/util/ArrayList."":()V 7: astore_1 8: aload_1 9: iconst_1 10: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 13: invokeinterface #5, 2; //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 18: pop 19: return } 

Vous verrez que la seule différence est la première (à l’aide d’une ArrayList ) appelle invokevirtual et l’autre (à l’aide de List utilise) invokeinterface . invokeinterface est en fait un cheveu plus lent que invokevirtual (~ 38% selon ce type ). Ceci est apparemment dû aux optimisations que la machine virtuelle Java peut effectuer lors de la recherche dans les tables de méthodes virtuelles d’une classe concrète par rapport aux tables de méthodes d’une interface. Donc, ce que vous dites est en réalité vrai. Les invocations d’interface sont plus lentes que les invocations de classes concrètes.

Cependant, vous devez considérer le type de vitesse dont nous parlons. Pour 100 000 000 d’appels, la différence réelle était de 0,03 seconde. Il faut donc une tonne d’invocations pour que la vitesse diminue considérablement.

D’autre part, comme @ChinBoon le souligne, le codage sur une interface facilite considérablement l’utilisation de votre code, en particulier si votre code renvoie une List sorting. Ainsi, dans la grande majorité des cas, la facilité de codage dépasse de loin la dépense de performance relative.


Ajouté après avoir lu le commentaire de @ MattQuigley et y avoir réfléchi pendant le trajet de retour

Tout ce que cela signifie, c’est que vous ne devez pas trop vous inquiéter de cela. Tout gain ou pénalité de performance sera probablement très faible.

N’oubliez pas que l’utilisation de l’interface pour vos types de retour et vos parameters sur les méthodes est une très bonne idée. Cela vous permet, à vous et à quiconque utilise votre code, d’utiliser la mise en oeuvre de List convient le mieux à leurs besoins. Mon exemple montre également que, si vous utilisez une méthode qui retourne List , 99% du temps, il n’est pas préférable de la lancer dans une classe concrète uniquement pour améliorer ses performances. Le temps nécessaire au casting sera probablement supérieur au gain de performance.

Cela étant dit, cet exemple montre également que, pour une variable locale , il vaut mieux utiliser une classe concrète plutôt qu’une interface. Si les seules méthodes que vous utilisez sont des méthodes de la List , vous pouvez désactiver l’implémentation de classes sans effets secondaires. De plus, vous aurez access aux méthodes spécifiques à l’implémentation si vous en avez besoin. De plus, il y a une amélioration mineure des performances.

tl; dr

Utilisez toujours des interfaces pour les types de retour et les parameters sur les méthodes. C’est une bonne idée d’utiliser des classes concrètes pour les variables locales. Cela entraîne des gains de performances mineurs et le changement d’implémentation est gratuit si les seules méthodes que vous utilisez sont utilisées dans l’interface. En fin de compte, vous ne devriez pas trop vous inquiéter. (Sauf les types de retour et les parameters. C’est important.)

La meilleure pratique de «programmation aux interfaces» s’applique-t-elle aux variables locales?

Même si la scope d’une telle variable est inférieure à celle d’une variable membre, la plupart des avantages du codage par rapport aux interfaces en ce qui concerne les variables membres s’appliquent également aux variables locales.

Sauf si vous avez besoin de méthodes spécifiques à ArrayList, optez pour une liste.

J’ai l’impression que l’impact sur les performances est négligeable, mais il est vrai que l’utilisation de la sémantique List de ArrayList ne présente aucun avantage.

Je n’ai jamais entendu dire que les appels de méthodes d’interface seraient plus lents que d’appeler des méthodes sur un type de classe.

Utiliser ArrayList au lieu de List tant que type statique me semble être une optimisation prématurée. Comme d’habitude, privilégiez la lisibilité et la maintenabilité aux optimisations jusqu’à ce que vous définissiez votre programme dans son ensemble.

TLDR: Les invocations de méthodes sont plus lentes sur les interfaces / classes abstraites et il n’ya pratiquement aucun obstacle à la modification ultérieure de l’implémentation d’une variable locale, ce qui fait de la plupart des arguments un point discutable.

Il y a beaucoup de réponses ici qui disent aveuglément de déclarer des variables locales comme interfaces sans comprendre pourquoi. Tout d’abord, l’appel de la méthode est plus lent – environ 3x, comme je me souviens dans les tests. La méthode add dans le premier bloc de code est 3 fois plus lente que le second. Vous pouvez le tester vous-même avec un simple test qui mesure le temps écoulé dans une boucle for.

 List numbers = new ArrayList(); numbers.add(1); // slower ArrayList numbers = new ArrayList(); numbers.add(1); // faster 

Deuxièmement, la question concerne les variables locales et non les principes de conception orientés object des interfaces en général. Il existe de bonnes raisons OO qu’une méthode de classe publique renvoie une interface ou une classe abstraite (List) au lieu de l’implémentation (ArrayList). Vous pouvez lire sur ces raisons ailleurs, mais cette question ne porte pas sur cela, mais sur les variables locales. 99,999% des fois que vous ne changerez jamais l’implémentation de cette variable locale d’une ArrayList à un Vector . Cependant, dans les 0,001% de fois que vous faites réellement, il n’y a aucun obstacle à le faire – il vous suffit de copier et coller le mot Vector sur le mot ArrayList et vous avez terminé. Tout le monde semble penser qu’un tel changement est difficile. Ce n’est pas.

Troisièmement, une LinkedList n’est pas la même chose qu’un ArrayList . Vous choisissez l’un ou l’autre pour une raison quelconque (temps nécessaire pour accéder à / append / insérer / supprimer). La déclarer en tant que ArrayList est une affirmation qui vous permet de savoir que cette variable locale est destinée à un access aléatoire rapide. L’argument selon lequel il s’agit d’une optimisation prématurée est stupide: il est optimisé d’une manière ou d’une autre dès qu’il est instancié.

Utilisez le type le plus abstrait qui vous permet de faire le travail. Si vous avez seulement besoin d’une List , utilisez-la. Si vous devez limiter le type à quelque chose de plus concret, utilisez-le plutôt. Par exemple, dans certains cas, utiliser List serait trop concret et utiliser Collection serait préférable. Autre exemple, dans certaines situations, une personne voudra peut-être exiger une Map ConcurrentMap au lieu d’une Map classique.

Surtout si la méthode renvoie la liste, le codage de l’interface vous aurait été utile si vous deviez changer l’implémentation en une liste chaînée un jour. Et c’est un avantage, vous n’auriez pas besoin de changer de méthode pour renvoyer une liste chaînée ou un arraylist.

voici une suggestion: déclarer la liste comme finale, mais avec l’interface.

 final List numbers = new ArrayList(); 

Si je devais deviner (et que je ne suis pas un expert du compilateur Java lui-même), mais en rendant la variable finale, le compilateur devrait pouvoir supprimer l’invokeinterface en faveur de invokevirtual, car il sait que le type ne le fera pas. changer une fois que la variable est déclarée.

Oui @ Matthew, c’est bien mieux les meilleures pratiques en utilisant

 List numbers = new ArrayList 

parce que vous pouvez utiliser les méthodes de la classe ArrayList et de la classe List. J’espère vous aider.