在Java的for-each循环中,有没有一种优雅的方法来处理最后一个元素?

17

我正在使用Java 6。

假设我有一堆要喂食的猫,而且假设myCats已经排序好了。

for (Cat cat : myCats) {

    feedDryFood(cat);

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

我想特别对待我的最后一只猫。

有没有一种优雅的方法在循环中实现这个?我能想到的唯一方法是计算它们。

稍微退后一步,为了更广泛的情况,有没有编程语言支持在for-each循环中实现这个小功能?


你真正的使用是什么?你可以特别处理第一项吗? - starblue
我不知道它的真正用途,但我肯定从这里的聪明友好的人们学到了很多。 - Russell
有很多糟糕的解决方案却获得了多个赞,这让人非常不安! - user177800
5
请展示比这更好的解决方案。 - FrustratedWithFormsDesigner
15个回答

28

如果你需要这样做,最好的方法可能是使用Iterator。除此之外,你必须计数。迭代器有一个

hasNext()

有一种方法可以确定您是否处于迭代的最后一项。

编辑--为了增加可读性,您可以在基于迭代器的循环内执行以下操作(伪代码):

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

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

通过巧妙地使用变量名称编写自我描述的代码。


2
虽然这确实回答了问题,但我对它唯一的问题是代码并没有非常明确地表达“最后一只猫是最喜欢的并得到金枪鱼”的要求。如果有人阅读代码,他们必须思考为什么迭代器没有下一个项目时猫会得到金枪鱼。 - ColinD
1
@colind,可读性非常重要。我更新了我的答案,说明了如何使代码易读且具有自我说明性。 - hvgotcodes

26

@fbcocq的解决方案

这怎么会是一个糟糕的解决方案呢?只需添加另一个本地变量即可。

Cat lastCat = null;

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

alsoFeedTuna(lastCat);

编辑:首先将其设置为 null,以处理 myCats 没有设置 lastCat 的情况


我不认为这是一个糟糕的解决方案,但它并没有遵循 OP 的问题,他们问是否有一种优雅的方法在循环内部完成。 - Paul Sonier
在我看来,它非常优雅。只需几行代码就能工作,而且没有特殊的行为。 - Peter Lawrey
1
myCats 为空时会发生什么?你不会得到一个异常吗? - Max

9

在for-each循环中没有明确的方法来做到这一点,但一个简单的方法是直接使用迭代器(for-each隐藏了迭代器)。


1
@balusc:我的问题也是! - Paul Sonier
3
有人进行了一次删票狂潮。 - John Vint
有时候我会感觉到,有些人只是想发泄一下情绪,然后为了好玩就开始乱踩负面评价。这就是所谓的“极客失控”。 - Epaga

8

如果有一个特殊行为必须发生在循环的最后一个项目上,那么它应该在循环之外进行,并提供一种让信息逃离循环的方式:

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

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

我建议将这两个语句块移入方法中。

5

直接使用迭代器。

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

4

我认为最好的方法是在猫上设置一个指示器,isFavoured,或者是Cat类的静态成员,它指向最喜欢的猫(但这样只能有一个最喜欢的)。然后,在循环中遍历时查找指示器即可。毕竟,猫并不总是按照相同的顺序进食。 ;)

for (Cat cat : myCats) {

    feedDryFood(cat);

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

或者,您可以将列表转换为数组,这样当您到达最后一个时就很容易知道 - 但如果最后一个不是您最喜欢的呢?

//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]);
}

不清楚哪个更重要:是让最后一只猫得到金枪鱼,还是让最喜欢的猫得到金枪鱼。或者说,根据最后一只猫是最后一只的定义,它是否就成为了最受喜爱的猫呢?请澄清!


2
如果发生这种情况,最后一个会抓他的脸。然后,下一次循环时,你可以确定它将是他最喜欢的。 - bzlm
2
好主意,但当条件不取决于项目本身时,将无法应用。例如,将项目连接到逗号分隔的字符串中。此时,条件确实取决于项目在列表中的位置。 - BalusC
@BalusC:当然,使用分隔符连接字符串是一种特殊情况,最好由专门的API处理...我觉得像这样的位置相关逻辑在大多数其他情况下应该是相对罕见的。在这个例子中,“favorite”似乎是一个明显不应该基于列表中的位置来确定的标识。 - ColinD
3
最后一只猫总是我最喜欢的。当我叫它们排队等着吃东西时,我认为最后一只猫是所有猫中最谦虚的,也应该得到金鱼罐头。 - Russell
@Russell:现在这是一个非常清晰明确的需求(比之前好多了)。在这种情况下,我认为你最好使用迭代器或数组解决方案,因为你不关心哪个人得到它,只要是最后一个就行了。(另外,有人能解释一下为什么会被踩吗?) - FrustratedWithFormsDesigner
显示剩余6条评论

4

关于是否有任何编程语言支持此功能的问题,Perl的Template Toolkit是支持的:

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

好知道,谢谢!这意味着这个功能对一些人来说是有用的。 - Russell

1

for...each 结构不是获取特定行为的正确工具,因为问题的措辞中没有更多 Cat 对象的特征。经典的 Iterator 就是设计用来提供这种类型功能的。我认为这个问题说明了设计存在缺陷,稍后会详细讨论。

假设有一个名为 cats 的排序猫列表。无法保证遍历顺序的 List 不是迭代的好选择,无论如何遍历列表都是如此。

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);
   }
}

一个更好的解决方案是在Cat上拥有一个成员方法Cat.isSpecial(),返回一个boolean。这样更容易理解并且将行为从循环结构中分离出来。然后你可以使用一个for...each结构,并进行简单的测试,这个测试是自包含的和自说明的。
if (cat.isSpecial())
{
  this.feedTuna(cat);
}

我认为问题中的“for..each循环的最后一个元素”是一个误导。这个问题更像是一个设计问题而不是业务逻辑问题,而且由于for...each循环无法容纳规则,这表明需要进行重构。

这种新设计也可以容纳多个“特殊”的猫,而不会对代码造成任何复杂性。

其他更优雅的面向对象解决方案可能是访问者模式责任链模式。访问者模式将从Cat中抽象出谁喜欢什么的逻辑,并将其转移到访问者实现中。责任链也是如此。有一个CatFeeder对象链,让它们决定是否“处理”喂食或将其传递给下一个链。无论哪种方式都会更加松散耦合,内聚性更强。


1

我会在循环外面执行。

foreach 循环的整个语义是对每个对象执行相同的操作。但在这一步中,您要对一个对象进行不同的处理。对我来说,在循环外部执行更合理。

而且,我能想到的唯一将其放在循环内部的方法都非常 hacky...


0
假设 myCats 已经排序,可以将其按照喜爱程度的高低降序排列,而不是升序排列,或者在遍历前反转列表。
for (Cat cat in catsWithFavouriteFirst)
{
    cat.feed(dryFood);
    if (!tunaTin.isEmpty())
    {
        cat.feed(tunaTin.getTunaOut());
    }
}

有人可以解释一下为什么他们对一个完全合理的解决方案进行了负投票吗? - JeremyP
有人批量地给所有帖子点了踩,真糟糕。不管怎样,我为重新排序猫的想法给你点了赞。 - Russell
@Russell:谢谢。我注意到有很多合理的答案被投了很多反对票。 - JeremyP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接