1. Обзор
В этом уроке мы более подробно рассмотрим ковариантный возвращаемый тип в Java. Прежде чем рассматривать ковариацию с точки зрения типа возвращаемого значения, давайте посмотрим, что это значит.
2. Ковариация
Ковариацию можно рассматривать как соглашение о том, как принимается подтип, когда определен только супертип.
Рассмотрим пару основных примеров ковариации:
List<? extends Number> integerList = new ArrayList<Integer>();
List<? extends Number> doubleList = new ArrayList<Double>();
Таким образом , ковариация означает, что мы можем получить доступ к определенным элементам, определенным через их супертип . Однако нам не разрешено помещать элементы в ковариантную систему , поскольку компилятор не сможет определить фактический тип универсальной структуры.
3. Ковариантный тип возврата
Ковариантный тип возвращаемого значения — когда мы переопределяем метод — позволяет возвращаемому типу быть подтипом типа переопределенного метода .
Чтобы применить это на практике, давайте возьмем простой класс Producer
с методом product()
.
По умолчанию он возвращает строку
как объект
, чтобы обеспечить гибкость для дочерних классов:
public class Producer {
public Object produce(String input) {
Object result = input.toLowerCase();
return result;
}
}
В результате Object
в качестве типа возвращаемого значения мы можем иметь более конкретный тип возвращаемого значения в дочернем классе. Это будет ковариантный возвращаемый тип, который будет производить числа из последовательностей символов:
public class IntegerProducer extends Producer {
@Override
public Integer produce(String input) {
return Integer.parseInt(input);
}
}
4. Использование структуры
Основная идея ковариантных возвращаемых типов — поддержка замены Лискова .
Например, давайте рассмотрим следующий сценарий производителя:
@Test
public void whenInputIsArbitrary_thenProducerProducesString() {
String arbitraryInput = "just a random text";
Producer producer = new Producer();
Object objectOutput = producer.produce(arbitraryInput);
assertEquals(arbitraryInput, objectOutput);
assertEquals(String.class, objectOutput.getClass());
}
После изменения на IntegerProducer
бизнес-логика, которая фактически производит результат, может остаться прежней:
@Test
public void whenInputIsSupported_thenProducerCreatesInteger() {
String integerAsString = "42";
Producer producer = new IntegerProducer();
Object result = producer.produce(integerAsString);
assertEquals(Integer.class, result.getClass());
assertEquals(Integer.parseInt(integerAsString), result);
}
Однако мы по-прежнему ссылаемся на результат через объект.
Всякий раз, когда мы начинаем использовать явную ссылку на IntegerProducer,
мы можем получить результат как целое число
без понижающего приведения:
@Test
public void whenInputIsSupported_thenIntegerProducerCreatesIntegerWithoutCasting() {
String integerAsString = "42";
IntegerProducer producer = new IntegerProducer();
Integer result = producer.produce(integerAsString);
assertEquals(Integer.parseInt(integerAsString), result);
}
Хорошо известным сценарием является метод клонирования
Object#
, который по умолчанию возвращает объект .
Всякий раз, когда мы переопределяем метод clone()
, возможность ковариантных возвращаемых типов позволяет нам иметь более конкретный возвращаемый объект, чем сам объект
.
5. Вывод
В этой статье мы увидели, что такое ковариантные и ковариантные возвращаемые типы и как они ведут себя в Java.
Как всегда, код доступен на GitHub .