【问题标题】:Is there an elegant way of doing something to the last element of a for-each loop in Java?有没有一种优雅的方式来处理 Java 中 for-each 循环的最后一个元素?
【发布时间】:2010-11-08 16:24:47
【问题描述】:

我正在使用 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 循环中支持这个小功能?

【问题讨论】:

  • 你这个的真正用途是什么?你能把第一个项目特殊对待吗?
  • 真正的用途,我不知道,但我肯定会从这里的好人那里学到很多东西,聪明的人。
  • 多票赞成的糟糕解决方案的数量非常令人不安!
  • @fuzzy lollipop:那么请告诉我们一个更好的解决方案。

标签: java programming-languages foreach


【解决方案1】:

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

hasNext()

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

编辑——为了提高可读性,您可以在基于迭代器的循环(伪)中执行以下操作:

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

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

通过巧妙地使用变量名,这是相当自记录的代码。

【讨论】:

  • 虽然这肯定回答了这个问题,但我唯一遇到的问题是它没有使代码中“最后一只猫是最喜欢的并且得到金枪鱼”的要求非常明确。如果迭代器没有下一项,阅读的人必须考虑为什么给猫提供金枪鱼。
  • @colind,可读性非常重要。我更新了关于如何使其具有可读性和自我记录的答案。
【解决方案2】:

@fbcocq 的解决方案

这是一个糟糕的解决方案吗?只需添加另一个局部变量。

Cat lastCat = null;

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

alsoFeedTuna(lastCat);

编辑:首先设置 null 以处理 myCats 未设置 lastCat 的情况

【讨论】:

  • 我不认为这是一个糟糕的解决方案,但它没有遵循 OP 的问题,他们询问是否有一种优雅的方法可以在循环内完成。
  • 恕我直言,它非常优雅。无需特殊行为,只需几行代码即可工作。
  • myCats 为空时会发生什么?你不会得到一个例外吗?
【解决方案3】:

在 for-each 循环中没有明确的方法来执行此操作,但一种直接的方法是直接使用迭代器(for-each 隐藏了迭代器)。

【讨论】:

  • @balusc:也是我的问题!
  • 有人投了反对票
  • 是的,有时我会觉得有些人只是想放开一些流,然后为了好玩而大肆投票。极客狂人。
【解决方案4】:

如果这是循环中最后一项必须发生的特殊行为,那么它应该发生在循环之外,您应该提供让信息逃离循环的方法:

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.
        }
    }
    

    【讨论】:

      【解决方案6】:

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

      for (Cat cat : myCats) {
      
          feedDryFood(cat);
      
          if (cat.isFavoured) 
              alsoFeedTuna(cat);
      }
      

      或者,您可以将 list 转换为数组,然后很容易知道何时到达最后一个 - 但如果最后一个不是您最喜欢的呢?

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

      不清楚哪个更重要:让最后一只猫得到金枪鱼,还是让最喜欢的猫得到金枪鱼。或者……最后一只猫是最喜欢的,根据最后的定义?请澄清!

      【讨论】:

      • 如果发生这种情况,最后一个会抓他的脸。然后,下一次循环,你可以确定它会是他的最爱。
      • 我同意你关于Cat 上的isFavoured 属性或类似的东西......这是一个比在列表中任意位置指示应该得到哪只猫更强大的设计金枪鱼(除非设计实际上只是出于某种原因“最后一只猫得到金枪鱼”)。我不同意 Cat 上的静态字段或将 List 转换为数组......通常没有任何好的理由这样做。
      • 好主意,但当条件不依赖于项目本身时将不适用。例如。将项目连接到逗号分隔的字符串。然后,条件实际上取决于项目在列表中的位置。
      • @BalusC:当然,用分隔符连接字符串是一种特殊情况,最好由 API 处理......我觉得像这样的位置相关逻辑应该是 在大多数其他情况下相对较少。在这个例子中,“最喜欢的”似乎是一个绝对不应该基于列表中的位置的名称。
      • 最后一只猫永远是我的最爱。当我告诉他们在我喂它们之前排队时,我认为最后一只猫是最卑微的,应该得到金枪鱼。
      【解决方案7】:

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

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

      【讨论】:

      • 很高兴知道,谢谢!这意味着这个功能毕竟对某些人有用。
      【解决方案8】:

      我会在循环之外做。

      foreach 循环的整个语义是您对每个对象执行相同的操作。在这一步中,尽管您以不同的方式对待一个对象。对我来说,在循环之外做这件事似乎更明智。

      另外,我能想到的唯一方法是在里面做这件事太骇人听闻了......

      【讨论】:

        【解决方案9】:

        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 循环无法适应规则这一事实表明应该进行重构。

        这种新设计还可以容纳多只“特殊”猫,而不会使代码复杂化。

        其他更优雅的面向对象解决方案是Visitor PatternChain of Responsibility Pattern。访问者模式将从Cat 中抽象出谁最喜欢什么的逻辑并进入访问者实现。与责任链相同。拥有CatFeeder 对象链,让它们决定是“处理”喂食还是将其传递到链下。无论哪种方式都将是更松散的耦合和更紧密的内聚。

        【讨论】:

          【解决方案10】:

          您是否有任何理由不能使用带有计数器和位置测试的正常循环构造?默认情况下,不使用 foreach 语法并不是不优雅的。如果使用 foreach 构造,那么我会同意使用迭代器。

          【讨论】:

            【解决方案11】:
                for( Iterator<Cat> iter = cats.iterator() ; iter.hasNext(); )
                {
                    Cat cat = iter.next();
                    feedDryFood(cat);
                    if(!iter.hasNext())
                        alsoFeedTuna(cat);
                }
            

            【讨论】:

              【解决方案12】:

              假设 myCats 已排序

              按偏好降序排列而不是升序排列,或者在迭代之前倒序排列

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

              【讨论】:

              • 有人想解释为什么他们对一个完全合理的解决方案投了反对票吗?
              • 有人批量否决了所有线程。太糟糕了。无论如何,我赞成你重新分类猫的想法。
              • @Russell:谢谢。我注意到似乎有很多合理的答案投了很多反对票。
              【解决方案13】:

              你可以做的很简单的事情就是明确最后一只猫是最喜欢的,应该得到金枪鱼,就是简单地按索引得到最后一只猫,然后在给所有猫干粮后给它金枪鱼。

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

              如果您使用实现RandomAccessList(例如ArrayList),这将很快而且很好。如果您使用非RandomAccess 结构,例如LinkedList,这会很慢,但您可以将列表视为Deque,然后写:

              Cat favorite = myCats.getLast();
              

              您当然需要确保myCats 不为空才能执行此操作。

              【讨论】:

              • 我没有投反对票,但可能是因为myCats().size() 可能为零。
              • @Inshallah:没错,您需要确保myCats 不为空。
              【解决方案14】:

              我建议不要使用 foreach 循环并将 feedTuna 方法放在循环之外。

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

              这避免了循环内的任何额外赋值或条件。

              【讨论】:

                【解决方案15】:
                Cat cat;
                
                for (cat : myCats) {
                  feedDryFood(cat);
                }
                
                alsoFeedTuna(cat);
                

                【讨论】:

                • -1: 根本不编译 :) 它确实必须在 foreach 的范围内声明。 Iterator 是您最优雅的选择。
                • 我的错,foreach 不允许 cat 超出其范围。
                • 答案下方有一个delete 链接。
                • 我知道,我们都在学习,不妨留在这里留给后代。
                • 为发帖人辩护:我很确定确实存在允许这样做的语言。
                猜你喜欢
                • 1970-01-01
                • 2010-10-24
                • 2011-09-18
                • 1970-01-01
                • 1970-01-01
                • 2016-11-20
                • 1970-01-01
                • 1970-01-01
                • 2011-06-07
                相关资源
                最近更新 更多