У меня продолжаются дебаты с коллегами об использовании самореферентных дженериков в Java, в которых я утверждаю, что они необоснованны, а другие продолжают верить в их использование. Под самореферентными обобщениями я подразумеваю тип, определенный в терминах самого себя, например:
interface Foo{ ... } interface Bar extends Foo { ... }
На первый взгляд это кажется полезным, поскольку позволяет обеспечить правильную типизацию параллельных иерархий. Например, рассмотрим складскую систему, которая работает с различными типами деталей. Каждая деталь имеет идентификатор, специфичный для конкретного типа:
class Serial{ // stores some identifier specific to P } interface Part
{ Serial
getSerial(); }
Всякий раз, когда мы определяем класс детали, мы объявляем, что он реализует Часть
параметризована самим классом. Это гарантирует, что серийный номер будет того же типа:
class Whatsit implements Part{ private final Serial serial; @Override public Serial getSerial() { return serial; } }
У нас даже может быть база данных деталей, которая использует полиморфный метод, чтобы гарантировать, что вы запросите Что это
вы получаете только Что это
.
interface PartDatabase {> P getPart(Serial
serial); }
Пока все это звучит разумно, и мы даже можем работать с деталями в целом таким образом:
class HomogenousPartCruncher> { private final PartDatabase database; void crunchParts(List
> serials) { serials.forEach(serial -> crunch(database.getPart(serial))); } void crunch(Part part) { // TODO } }
Любой данный экземпляр Однородной части Хрустит
может работать только с одним типом Стороны
однако, тип, с которым он создан. Подсказки в названии. Но как только мы пытаемся использовать объектно ориентированный стиль кодирования, все разваливается:
class HeterogeneousPartCruncher { private final PartDatabase database; void crunchParts(List>> serials) { serials.forEach(serial -> crunch(database.getPart(serial))); } void crunch(Part> part) { // TODO } }
Мы не знаем тип каждого серийного номера
в списке, но это, по крайней мере, часть >
. Поэтому мы можем попытаться получить фактический Часть >
из базы данных с использованием этого серийного номера. Но компилятор жалуется с
error: method getPart in interface PartDatabase cannot be applied to given types; serials.forEach(serial -> crunch2(database.getPart(serial))); ^ required: Serialfound: Serial
reason: inference variable P has incompatible bounds equality constraints: CAP#2,CAP#1 upper bounds: Part where P is a type-variable: P extends Part
declared in method
getPart(Serial
) where CAP#1,CAP#2 are fresh type-variables: CAP#1 extends Part> from capture of ? extends Part> CAP#2 extends Object from capture of ?
Похоже, это предполагает, что границы типа в Последовательном
должны быть расширенной частью
, но если наше приложение использует Последовательный
ы для вещей, отличных от Части
– мы не можем этого сделать. Это также создает циклическую зависимость, которая никогда не является улучшением вашей общей структуры кода.
Единственный способ исправить это – притвориться, что Часть
вообще не является универсальной, и реализуйте crunch Parts()
вот так:
@SuppressWarnings("unchecked") void crunchParts(PartDatabase database, List> serials) { serials.forEach(serial -> crunch(database.getPart(serial))); }
И на самом деле везде, где вы ссылаетесь на Часть
в отношении этого кода, это должно быть Часть >
, включая возвращаемые типы , которые ужасно пахнут кодом . Часть
с таким же успехом может вообще не быть общей:
interface Part { Serial extends Part> getSerial(); } class Whatsit implements Part { @Override public SerialgetSerial() { ... } } } interface PartDatabase { P getPart(Serial
serial); } class HomogenousPartCruncher
{ private final PartDatabase database; void crunchParts(List
> serials) { serials.forEach(serial -> crunch(database.getPart(serial))); } void crunch(P part) { // TODO } } class HeterogeneousPartCruncher { private final PartDatabase database; void crunchParts(List > serials) { serials.forEach(serial -> crunch2(database.getPart(serial))); } void crunch2(Part part) { // TODO } }
Код более чистый и понятный, имеет те же гарантии безопасности типов, но не нуждается в >
подстановочные знаки или @Подавление предупреждений("снято")
везде.
Единственный способ использовать самореферентные дженерики без раздражающих подстановочных знаков и подавления предупреждений о непроверенных приведениях – это для всех – и я имею в виду все – ваш код для работы только с конкретными типами. Это явно не объектно-ориентированный стиль, который я нахожу любопытным в языке, настолько посвященном продвижению объектно-ориентированного программирования, что иерархии классов являются практически единственным инструментом абстракции, который он предлагает.
Оригинал: “https://dev.to/neilgall/self-referential-generics-in-java-53pc”