Есть ли элегантный способ сделать что-то с последним элементом цикла for-each в Java? - PullRequest
17 голосов
/ 08 ноября 2010

Я использую 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?

Ответы [ 15 ]

28 голосов
/ 08 ноября 2010

Если вам нужно сделать это, лучшим подходом может быть использование итератора. Кроме этого, вы должны рассчитывать. Итератор имеет

hasNext()

метод, который вы можете использовать, чтобы определить, находитесь ли вы на последнем пункте ваших итераций.

РЕДАКТИРОВАТЬ - Чтобы повысить удобочитаемость, вы можете сделать что-то вроде следующего в цикле на основе итератора (psuedo):

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

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

это довольно самодокументируемый код с помощью умного использования имен переменных.

26 голосов
/ 08 ноября 2010

@ решение fbcocq

Как это было плохое решение?Просто добавьте еще одну локальную переменную.

Cat lastCat = null;

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

alsoFeedTuna(lastCat);

Редактировать: сначала установить null, чтобы позаботиться о случаях, когда myCats не устанавливает lastCat

9 голосов
/ 08 ноября 2010

Нет четкого способа сделать это в цикле for-each, но простым способом было бы просто использовать итератор напрямую (for-each скрывает итератор).

8 голосов
/ 08 ноября 2010

Если это особое поведение, которое должно происходить с последним элементом в цикле, то это должно происходить вне цикла, и вы должны предоставить способ, позволяющий информации выходить из цикла:

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

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

Я бы порекомендовал переместить блоки этих двух операторов в методы.

5 голосов
/ 08 ноября 2010

Используйте итератор напрямую.

Cat cat
Iterator<Cat> i = myCats.iterator()
while (i.hasNext())
{
    cat = i.next()
    feedDryFood(cat);
    if (!i.hasNext())
    {
        alsoFeedTuna(cat); // Last cat.
    }
}
4 голосов
/ 08 ноября 2010

Что касается вопроса о том, поддерживает ли эта функция какие-либо языки программирования, Perl's Template Toolkit поддерживает:

[% FOR cat IN cats; feedDryFood(cat); alsoFeedTuna(cat) IF loop.last; END %]
4 голосов
/ 08 ноября 2010

Я думаю, что лучше всего было бы установить индикатор для кошки, 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]);
}

Непонятно, что важнее: для последнего кота, чтобы получить тунца, ИЛИ для любимого кота, чтобы получить тунца.Или ... последний кот любимый по определению последний?Пожалуйста, уточните!

1 голос
/ 09 ноября 2010
Конструкция

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 голос
/ 08 ноября 2010

Я бы сделал это вне цикла.

Вся семантика цикла foreach заключается в том, что вы делаете то же самое с каждым объектом. На этом этапе вы рассматриваете один объект по-другому. Мне кажется более разумным делать это вне цикла.

Плюс, единственные способы, которыми я мог придумать, сделать это внутри, это ужасно смешно ...

0 голосов
/ 25 ноября 2010

Я предлагаю не использовать цикл foreach и не помещать метод feedTuna вне цикла.

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

Это исключает любые дополнительные присваивания или условия внутри цикла.

...