W3docs

Interface Queue en Java

Opérations FIFO en Java avec l'interface Queue — offer, poll, peek — et ses implémentations.

Queue<E> est une Collection<E> à laquelle s'ajoute la notion de tête. La tête est le prochain élément à être supprimé ; tous les autres attendent leur tour derrière elle. La sémantique par défaut — et celle qu'utilise presque chaque implémentation du JDK — est FIFO : premier entré, premier sorti. Les producteurs insèrent des éléments à la fin via offer ; les consommateurs les récupèrent en tête via poll. Ce chapitre porte sur le contrat : les six méthodes, les deux styles de gestion des erreurs, la règle sur les valeurs null, et quelle implémentation choisir selon la situation.

Deux façons de faire chaque chose — par conception

Chaque opération de file d'attente existe en deux variantes : l'une qui lève une exception en cas d'échec, et l'autre qui renvoie une valeur spéciale. La Javadoc présente cela sous forme d'une grille 3×2 :

OpérationLève une exception en cas d'échecRenvoie une valeur spéciale
Insertionboolean add(E e) (lève IllegalStateException si pleine)boolean offer(E e) (renvoie false si pleine)
SuppressionE remove() (lève NoSuchElementException si vide)E poll() (renvoie null si vide)
ExamenE element() (lève NoSuchElementException si vide)E peek() (renvoie null si vide)

La colonne « lève une exception » est celle héritée de Collection et List — elle est familière mais peu pratique quand le cas vide/plein est normal (un consommateur attendant la prochaine tâche, une file bornée rejetant un producteur en excès). La colonne « renvoie une valeur spéciale » a été ajoutée pour les files afin que vous puissiez écrire des boucles qui ne s'appuient pas sur des exceptions pour le contrôle de flux :

String item;
while ((item = queue.poll()) != null) {
  process(item);
}

Utilisez la forme retour de valeur par défaut. Recourez à remove/element uniquement lorsqu'une file vide à ce stade serait un véritable bug que vous voudriez signaler par une exception.

Null et Queue ne font pas bon ménage

Parce que poll() et peek() renvoient null pour signifier « vide », autoriser null comme élément réel est une source d'ambiguïté. Chaque Queue généraliste du JDK, à l'exception de LinkedList, rejette nullArrayDeque, PriorityQueue, ArrayBlockingQueue, toutes les files concurrentes. LinkedList vous laisse ajouter un null, mais ce faisant vous avez rompu le contrat : il n'est plus possible de distinguer « la tête est null » de « la file est vide ».

La règle : ne stockez pas null dans une file. Préférez ArrayDeque à LinkedList et le langage l'imposera pour vous.

FIFO est la valeur par défaut — mais pas universelle

Queue n'impose pas FIFO. L'interface définit une tête et des opérations qui en extraient des éléments ; la façon dont la tête est déterminée dépend de l'implémentation. Les deux implémentations de java.util qui ne sont pas FIFO sont :

  • PriorityQueue — la tête est toujours l'élément le plus petit (selon l'ordre naturel ou un Comparator). Les insertions se font là où le tas le dicte, pas en queue.
  • Les variantes bloquantes de java.util.concurrentPriorityBlockingQueue, DelayQueue, etc. — réordonnent par priorité, échéance, etc.

Tout le reste (ArrayDeque, LinkedList, ArrayBlockingQueue, LinkedBlockingQueue, ConcurrentLinkedQueue) est FIFO.

Choisir une implémentation

Pour le cas mono-thread :

  • ArrayDeque — le choix par défaut. Tableau circulaire, sans allocation par élément, rapide. Utilisez-le comme Queue<E> ou comme Deque<E> selon que vous avez besoin d'accéder aux deux extrémités.
  • LinkedList — fonctionne, implémente également List. Choisissez-le uniquement si vous avez vraiment besoin des deux interfaces sur le même objet. Le chapitre sur LinkedList couvre les compromis.
  • PriorityQueue — quand vous voulez « l'élément en attente le plus petit en premier » plutôt que FIFO.

Pour le cas multi-thread (couvert en détail dans la partie concurrence plus loin — indications seulement ici) :

  • ConcurrentLinkedQueue — FIFO sans verrou. Non borné.
  • ArrayBlockingQueue — borné, sauvegardé par un tableau, bloque put quand plein et take quand vide. La file producteur/consommateur classique.
  • LinkedBlockingQueue — optionnellement borné, version à nœuds liés du même principe.
  • PriorityBlockingQueuePriorityQueue concurrent. Non borné.

La plupart du code applicatif utilise l'un des quatre premiers. Les files bloquantes sont la façon de construire des pools de workers et de la contre-pression.

Ce que vous pouvez appeler sur n'importe quelle Queue

Au-delà des six méthodes spécifiques à la tête, une Queue est aussi une Collection — donc size, isEmpty, contains, iterator, forEach, stream, clear, toArray fonctionnent toutes. L'ordre d'itération est l'ordre dans lequel les éléments seraient retirés par poll pour les implémentations FIFO (ArrayDeque, LinkedList), mais pour PriorityQueue l'ordre d'itération n'est pas l'ordre de priorité — il visite le tableau du tas sous-jacent tel quel. Si vous voulez les éléments dans l'ordre de priorité, vous devez vider la file avec poll jusqu'à ce qu'elle soit vide.

Exemple concret : producteur/consommateur en un seul thread

Le programme ci-dessous utilise un ArrayDeque comme file FIFO pour modéliser un petit système de gestion d'impressions : les travaux arrivent au fil du temps (nous le représentons en insérant par lots via offer), et le worker vide chaque lot avec poll. La paire de styles d'erreur est montrée en haut afin que vous puissiez voir exactement quand chaque forme se déclenche.

java— editable, runs on the server

Quelques observations à tirer de l'exécution :

  • poll et peek ont renvoyé silencieusement null lorsque la file était vide ; remove et element ont levé des exceptions. Les deux comportements font partie du contrat — choisissez celui qui correspond à votre cas : « vide » est-il une erreur ou simplement un état ?
  • offer(null) a levé une exception sur ArrayDeque. C'est l'implémentation qui fait respecter la règle dont dépend l'interface.
  • La PriorityQueue a renvoyé 10, 20, 30, 40 par poll — dans l'ordre trié, pas dans l'ordre d'insertion. Même interface Queue, règle de sélection de la tête totalement différente.

Ce qui vient ensuite

La première implémentation non-FIFO mérite son propre chapitre — PriorityQueue est une file sauvegardée par un tas où la tête est toujours l'élément le plus petit. C'est l'ingrédient de base de presque tous les ordonnanceurs « traiter la tâche la plus urgente en premier » du JDK.

Pratique

Pratique
Dans une boucle vous écrivez `while ((job = queue.poll()) != null) { process(job); }`. Si vous utilisiez `queue.remove()` à la place, qu'est-ce qui changerait au niveau du contrat ?
Dans une boucle vous écrivez `while ((job = queue.poll()) != null) { process(job); }`. Si vous utilisiez `queue.remove()` à la place, qu'est-ce qui changerait au niveau du contrat ?
Was this page helpful?