Чтобы полностью ответить на этот вопрос, мы должны заглянуть под капот Java, чтобы увидеть, что представляют собой лямбды (по крайней мере, на данный момент): синтаксический сахар для реализации интерфейсов с помощью только одного метода. Давайте объясним это на примере Comparator<T>
(который прекрасно подходит для лямбда-выражения) и перейдем к Comparable<T>
(который может быть реализован лямбда-выражением, но не имеет смысла).
Предположим, кто-то дал нам (скомпилировано)
public class Car {
private final int horsePowers;
private final int topSpeed;
private final int numDoors;
private final String color;
// Setters omitted for brevity
}
Мы не можем изменить этот класс, но мы хотим отсортировать автомобили, например, по цвету. Традиционным способом было бы позвонить Collections.sort(List<T> list, Comparator<? super T> c)
. Поскольку мы, как разработчики, часто ленивы (в противном случае мы не будем использовать Copy-Paste-Programming ), мы часто внедряем подобные вещи в анонимный класс :
...
List<Car> cars = ...;
Collections.sort(cars, new Comparator<Car> {
@Override
public int compare(final Car lhs, final Car rhs) {
return lhs.getColor.compareTo(rhs.getColor());
}
});
Это безобразно. Во-первых, это долго, а во-вторых, это неудобно читать. Но, к счастью, боги Java благословили нас лямбда-выражениями , так что мы напишем 1 :
...
List<Car> cars = ...;
Collections.sort(cars, (lhs, rhs) -> lhs.getColor().compareTo(rhs.getColor()));
Это выглядит лучше, не так ли? Это коротко, всесторонне, и приятно читать (по крайней мере, если вы видели это сто раз). Но под капотом это все еще старый анонимный класс. Ничего больше. Некоторые ограничения действительно применяются, хотя к лямбдам. Во-первых, у лямбды нет состояния (переменные экземпляра). Во-вторых, все ссылки, используемые за пределами лямбды (локальные переменные или атрибуты окружающего метода / класса), должны быть final
или эффективно final
(лямбда не может изменять значение). Это станет важным ниже.
Теперь давайте перейдем к Comparable<T>
. В то время как Comparator<T>
определяет, как сравнивать два T
друг с другом, Comparable<T>
говорит, что «этот класс сопоставим с T
(как правило, сам по себе), и вот как его сравнивать с T
». Давайте представим, что наш класс Car
сверху фактически реализует Comparable<Car>
следующим образом:
public class Car implements Comparable<Car> {
...
public int CompareTo(final Car that) {
int diff = this.getHorsePowers() - that.getHorsePowers();
if (diff == 0) {
diff = this.getTopSpeed() - that.getTopSpeed();
}
if (diff == 0) {
diff = this.getNumDoors() - that.getNumDoors();
}
if (diff == 0) {
diff = this.getColor().compareTo(that.getColor());
}
return diff;
}
}
По сути, он сначала сравнивает Car
с их horsePowers
. Если они равны, он сравнивает их по topSpeed
. Если они снова равны, он сравнивает их по количеству дверей и т. Д.
Теперь давайте подведем итоги: лямбда-выражения являются синтаксическим сахаром для реализации интерфейсов с помощью только одного метода, а Comparable<T>
описывает, как класс хочет сравнивать с T
. Теперь вспомните, что я сказал выше о лямбдах и состояниях: лямбды не имеют никакого состояния. Таким образом, сравнивать их с чем-либо в большинстве ситуаций бессмысленно (нет состояния - сравнивать нечего; я говорю «большую часть времени», поскольку могут быть угловые случаи, когда такая вещь возможна). Так что на самом деле очень мало причин для реализации Comparable<T>
с помощью лямбды.
В заключение можно сказать, что хотя можно реализовать Comparable<T>
с помощью лямбды, это редко полезно.
1 Я знаю, что лямбда сравнения обычно записывается как Comparator.comparing(Car::getColor)
, но это помимо того, что я пытаюсь сделать.