При возврате реализации интерфейса или класса, который находится в высокой иерархии, практическое правило заключается в том, что объявленный тип возврата должен быть наивысшим уровнем, обеспечивающим минимальную функциональность, которую вы готовы гарантировать вызывающей стороне, и что звонящий разумно нуждается. Например, предположим, что вы действительно возвращаете ArrayList. ArrayList реализует List и Collection (среди прочего). Если вы ожидаете, что вызывающая сторона должна использовать функцию get (int x), то она не сработает для возврата Collection, вам нужно будет вернуть List или ArrayList. Пока вы не видите причин, по которым вы когда-либо изменили бы свою реализацию на использование чего-то другого, кроме списка - скажем, набора - тогда правильный ответ - вернуть список. Я не уверен, есть ли в ArrayList какая-либо функция, которой нет в List, но если она есть, применимы те же аргументы. С другой стороны, когда вы возвращаете Список вместо Коллекции, вы в какой-то степени заблокировали свою реализацию. Чем меньше вы вкладываете в свой API, тем меньше вы ограничиваете будущие улучшения.
(На практике я почти всегда возвращаю Список в таких ситуациях, и он никогда не сжигал меня. Но я, вероятно, действительно должен вернуть Коллекцию.)