2010-11-08 15 views
17

J'utilise Java 6.Existe-t-il une manière élégante de faire quelque chose au dernier élément d'une boucle for-each en Java?

Supposons que j'ai un groupe de chats à nourrir, et supposons que myCats est trié.

for (Cat cat : myCats) { 

    feedDryFood(cat); 

    //if this is the last cat (my favorite), give her a tuna 
    if (...) 
     alsoFeedTuna(cat); 
} 

et je voulais traiter mon dernier chat spécialement. Y at-il un moyen de le faire élégamment dans la boucle?

La seule façon dont je peux penser est de les compter.

En revenant un peu en arrière pour une image plus large, existe-t-il un langage de programmation qui supporte cette petite fonctionnalité dans une boucle for-each?

+0

Quelle est votre utilisation réelle de ceci? Pouvez-vous traiter le premier article spécialement à la place? – starblue

+0

L'utilisation réelle, je ne sais pas, mais je suis certainement apprendre beaucoup de gens gentils et intelligents ici. – Russell

+0

le nombre de solutions médiocres avec des votes multiples est très inquiétant! –

Répondre

28

Si vous avez besoin pour ce faire, la meilleure approche pourrait être d'utiliser un Iterator. A part ça, vous devez compter. Le iterator a une méthode

hasNext() 

que vous pouvez utiliser pour déterminer si vous êtes sur le dernier élément de vos itérations.

EDIT - Pour une meilleure lisibilité, vous pouvez faire quelque chose comme ce qui suit dans la boucle à base de iterator (psuedo):

Cat cat = iter.next(); 
feedDryFood(cat); 

boolean shouldGetTuna = !iter.hasNext(); 
if (shouldGetTuna) 
    alsoFeedTuna(cat) 

qui est un code assez auto-documenté par l'utilisation intelligente des noms de variables.

+2

Bien que cela répond certainement à la question, le seul problème que j'ai avec elle est qu'il ne rend pas l'exigence "le dernier chat est le favori et obtient le thon" très explicite du code. Quelqu'un qui lit devrait réfléchir à la raison pour laquelle le chat reçoit du thon si l'itérateur n'a pas d'article suivant. – ColinD

+1

@colind, la lisibilité est très importante. J'ai mis à jour ma réponse sur la façon dont je la rendrais lisible et auto-documentée. – hvgotcodes

1

Je le ferais en dehors de la boucle. La sémantique entière d'une boucle foreach est que vous faites la même chose à chaque objet. Dans cette étape, vous traitez un objet différemment. Pour moi, il semble plus raisonnable de le faire en dehors de la boucle.

De plus, les seuls moyens que je pouvais penser à le faire à l'intérieur sont horriblement aki ...

-3
Cat cat; 

for (cat : myCats) { 
    feedDryFood(cat); 
} 

alsoFeedTuna(cat); 
+0

Ah, bon, même si c'est en dehors de la boucle. – Russell

+7

-1: ne compile pas du tout :) Il doit vraiment être déclaré dans la portée de foreach. Le 'Iterator' est votre pari le plus élégant. – BalusC

+1

Mon mauvais, foreach ne permet pas à chat d'être en dehors de sa portée. – deorst

4

Je pense que la meilleure chose serait de mettre un indicateur sur le chat, isFavoured, ou peut-être un membre statique de la classe Cat qui pointe vers le favori (mais cette façon, vous ne pouvez avoir un favori). Ensuite, il suffit de regarder l'indicateur lorsque vous parcourez la boucle. Après tout, les chats ne mangent pas toujours dans le même ordre. ;)

for (Cat cat : myCats) { 

    feedDryFood(cat); 

    if (cat.isFavoured) 
     alsoFeedTuna(cat); 
} 

Alternativement, vous pouvez convertir ensuite la liste à un tableau, puis il serait facile de savoir quand vous arrivez à la dernière - mais si le dernier n'est pas votre favori?

//only a rough idea, may not compile/run perfectly 
catArray = cats.toArray(cats); 
for (int i = 0 ; i < catArray.length(); i++){ 
    feedDryFood(catArray [i]); 

    //check for last cat. 
    if (i == catArray.length()-1) 
     alsoFeedTuna(catArray [i]); 
} 

On ne sait pas qui est plus important: pour le dernier chat pour obtenir le thon ou pour le chat favori pour obtenir le thon. Ou ... est le dernier chat le favori, par définition d'être dernier? Précisez s'il vous plaît!

+2

Si cela arrive, le dernier le grattera au visage. Ensuite, la prochaine fois que vous passerez dans la boucle, vous pouvez être sûr que ce sera son préféré. – bzlm

+0

+1 pour me faire rire au travail. – Russell

+0

Je suis d'accord avec vous sur la propriété 'isFavoured' sur' Cat' ou quelque chose de similaire ... c'est un design beaucoup plus fort que d'avoir une position arbitraire dans une liste indiquant quel chat devrait avoir du thon (sauf si le design est réellement simplement "le dernier chat obtient le thon" pour une raison quelconque). Je ne suis pas d'accord avec un champ statique sur 'Cat' pour cela ou avec la conversion d'un' List' en un tableau ... il n'y a généralement pas de bonne raison de le faire. – ColinD

9

Il n'y a pas de moyen clair de le faire dans la boucle for-each, mais un moyen simple serait d'utiliser directement un itérateur (le for-each cache l'itérateur).

+6

Pourquoi était-ce downvoted? – BalusC

+1

@balusc: ma question aussi! –

+3

Quelqu'un est allé sur un bender downvoting –

0

Y a-t-il une raison pour laquelle vous ne pouvez pas utiliser une construction en boucle normale avec un compteur et un test pour sa position? Ce n'est pas comme si l'utilisation de la syntaxe foreach était inélégante par défaut. Si vous utilisez la construction foreach, alors je serais d'accord avec l'itérateur.

0

et supposons que myCats est trié

Trier dans l'ordre décroissant de favoritisme au lieu de l'ordre croissant, ou inverser la liste avant itérer

for (Cat cat in catsWithFavouriteFirst) 
{ 
    cat.feed(dryFood); 
    if (!tunaTin.isEmpty()) 
    { 
     cat.feed(tunaTin.getTunaOut()); 
    } 
} 
+0

Quelqu'un veut-il expliquer pourquoi ils ont voté une solution parfaitement raisonnable? – JeremyP

+0

Quelqu'un a loti downvoted éventuellement tous les threads. C'est nul. Quoi qu'il en soit, je vous ai mis en tête pour l'idée de re-trier les chats. – Russell

+0

@Russell: merci. J'ai remarqué qu'il semblait y avoir beaucoup de votes négatifs de beaucoup de réponses raisonnables. – JeremyP

4

En ce qui concerne la question de savoir si une les langages de programmation supportent cette fonctionnalité, le Template Toolkit de Perl fait:

[% FOR cat IN cats; feedDryFood(cat); alsoFeedTuna(cat) IF loop.last; END %] 
+0

Bon à savoir, merci! Cela signifie que cette fonctionnalité est utile à certaines personnes après tout. – Russell

26

@ solution de fbcocq

Comment était-ce une mauvaise solution? Ajoutez simplement une autre variable locale.

Cat lastCat = null; 

for (Cat cat : myCats) { 
    feedDryFood(cat); 
    lastCat = cat; 
} 

alsoFeedTuna(lastCat); 

Modifier: set null premier à prendre en charge les cas où myCats ne fixe pas lastCat

+0

Je ne pense pas que ce soit une mauvaise solution, mais cela ne suit pas la question de l'OP, où l'on demande s'il y a une manière élégante de le faire dans la boucle. –

+0

À mon humble avis, c'est assez élégant. Fonctionne en quelques lignes sans comportement particulier. –

+1

Que se passe-t-il lorsque 'myCats' est vide? Ne recevrez-vous pas une exception? – Max

8

Si c'est un comportement spécial qui doit arriver le dernier élément de la boucle, il devrait avoir lieu à l'extérieur la boucle et vous devez fournir moyen de laisser l'information échapper à la boucle:

Cat lastCat = null; 
for (Cat cat : cats) 
{ 
    // do something for each cat 
    lastCat = cat; 
} 

if (lastCat != null) 
{ 
    // do something special to last cat 
} 

Je recommande de transférer les blocs de ces deux déclarations sur les méthodes.

5

Utilisez directement l'itérateur.

Cat cat 
Iterator<Cat> i = myCats.iterator() 
while (i.hasNext()) 
{ 
    cat = i.next() 
    feedDryFood(cat); 
    if (!i.hasNext()) 
    { 
     alsoFeedTuna(cat); // Last cat. 
    } 
} 
0

Quelque chose de très simple, vous pouvez faire cela rend explicite que le dernier chat est le favori et devrait obtenir le thon est d'obtenir simplement le dernier chat par index et lui donner le thon après avoir donné tous les chats aliments secs.

Cat favorite = myCats.get(myCats.size() - 1); 
alsoFeedTuna(favorite); 

C'est rapide et très bien si vous utilisez un List qui implémente RandomAccess comme un ArrayList. Si vous utilisez une structure non RandomAccess comme un LinkedList, ce serait lent, mais vous pouvez voir la liste comme Deque au lieu et à écrire:

Cat favorite = myCats.getLast(); 

Vous avez bien sûr besoin de faire en sorte que myCats ISN » t vide pour le faire.

+2

Pourquoi la downvote? – ColinD

+0

Je n'ai pas fait de downvote, mais peut-être que c'était fait parce que 'myCats(). Size()' pourrait éventuellement être zéro. – Inshallah

+0

@Inshallah: C'est vrai, vous devez vous assurer que 'myCats' n'est pas vide. – ColinD

0
for(Iterator<Cat> iter = cats.iterator() ; iter.hasNext();) 
    { 
     Cat cat = iter.next(); 
     feedDryFood(cat); 
     if(!iter.hasNext()) 
      alsoFeedTuna(cat); 
    } 
1

la construction for...each n'est pas l'outil approprié à utiliser pour obtenir le comportement spécifique que vous êtes après que la question est formulée, sans avoir plus de fonctionnalités dans l'objet Cat. Le Iterator classique est conçu pour fournir ce type de fonctionnalité. Je pense que la question illustre que la conception est défectueuse, plus sur ce dernier. En supposant qu'il existe une liste triée de chats appelée cats. Un List qui ne garantit pas l'ordre de traversée ne serait pas un bon candidat pour l'itération, quelle que soit la manière dont la liste est traversée.

final Iterator<Cat> iterator = cats.iterator(); 
while (iterator.hasNext()) 
{ 
    final Cat cat = iterator.next(); 
    this.feedDryCatFood(cat); 
    // special case, if there are no more cats in the list 
    // feed the last one tuna as well. 
    if (!iterator.hasNext()) 
    { 
     this.alsoFeedTuna(cat); 
    } 
} 

une meilleure solution serait d'avoir une méthode membre sur Cat. Cat.isSpecial() qui renvoie un boolean.Cela serait plus auto-documenté et déplacer le comportement hors de la construction de la boucle. Vous pouvez ensuite utiliser une construction for...each avec un test simple, autonome et auto-documenté.

if (cat.isSpecial()) 
{ 
    this.feedTuna(cat); 
} 

Je pense que le « dernier élément d'une boucle for..each » dans la question est un faux problème. Je pense que le problème est plus un problème de conception qu'un problème de logique métier, et le fait que la boucle for...each ne puisse pas accepter la règle indique qu'un refactoring doit avoir lieu.

Cette nouvelle conception pourrait également accueillir plusieurs chats «spéciaux» sans aucune complication au code.

D'autres solutions orientées objet plus élégantes seraient le Visitor Pattern ou le Chain of Responsibility Pattern. Le modèle de visiteur résumerait la logique de qui est le favori du Cat et de l'implémentation du visiteur. Pareil avec la chaîne de responsabilité. Avoir une chaîne de CatFeeder objets, et laissez-les décider si «gérer» l'alimentation ou le transmettre sur la chaîne. Dans les deux cas, ce serait un couplage plus lâche et une cohésion plus serrée.

0

Je suggère de ne pas utiliser une boucle foreach et de mettre la méthode feedTuna en dehors de la boucle.

int i = 0; 
for(i = 0; i < cat.length; i++){ 
    feedDryFood(cat[i]); 
} 
if(cat.length != 0){ 
    feedTuna(cat[i - 1]); 
} 

Cela évite d'autres affectations ou conditions dans la boucle.