Одні кажуть, що мова йде про взаємозв'язок типів і підтипів, інші кажуть, що це перетворення типів, а інші кажуть, що він використовується для визначення того, чи метод буде перезаписаний чи перевантажений.
Все вищеперераховане.
В основі цих термінів описано, як на підтипове вплив впливають перетворення типів. Тобто, якщо A
і B
є типи, f
це перетворення типу, а ≤ відношення підтипу (тобто A ≤ B
означає, що A
є підтипом B
), маємо
f
є коваріантним, якщо це A ≤ B
означаєf(A) ≤ f(B)
f
є протилежним, якщо це A ≤ B
означаєf(B) ≤ f(A)
f
є інваріантним, якщо жодне з перерахованих вище не відповідає
Розглянемо приклад. Нехай f(A) = List<A>
де List
оголошено
class List<T> { ... }
Є f
коваріантного, контраваріантен або інваріант? Коваріантний означатиме , що List<String>
є підтипом List<Object>
, контраваріантних що List<Object>
є підтипом List<String>
і інваріантної , що ні є підтип інших, тобто List<String>
і List<Object>
є незворотними типами. На Яві останнє вірно, ми говоримо (дещо неофіційно), що дженерики є інваріантними.
Ще один приклад. Нехай f(A) = A[]
. Є f
коваріантного, контраваріантен або інваріант? Тобто String [] є підтипом Object [], Object [] - підтипом String [], чи не є підтипом іншого? (Відповідь: У Java масиви є коваріантними)
Це все ще було досить абстрактно. Щоб зробити це більш конкретним, давайте розглянемо, які операції в Java визначаються з точки зору відношення підтипу. Найпростіший приклад - це призначення. Заява
x = y;
буде компілюватися, лише якщо typeof(y) ≤ typeof(x)
. Тобто ми щойно дізналися, що заяви
ArrayList<String> strings = new ArrayList<Object>();
ArrayList<Object> objects = new ArrayList<String>();
не компілюється в Java, але
Object[] objects = new String[1];
буде.
Іншим прикладом, коли значення має відношення підтипу, є вираз методу виклику:
result = method(a);
Неофіційно кажучи, це твердження оцінюється шляхом присвоєння значення a
першому параметру методу, потім виконання тіла методу, а потім присвоєння методам повернення значення result
. Як і звичайне призначення в останньому прикладі, "права рука" повинна бути підтипом "лівої сторони", тобто це твердження може бути дійсним лише тоді, коли typeof(a) ≤ typeof(parameter(method))
і returntype(method) ≤ typeof(result)
. Тобто, якщо метод оголошений:
Number[] method(ArrayList<Number> list) { ... }
жоден із наступних виразів не складе:
Integer[] result = method(new ArrayList<Integer>());
Number[] result = method(new ArrayList<Integer>());
Object[] result = method(new ArrayList<Object>());
але
Number[] result = method(new ArrayList<Number>());
Object[] result = method(new ArrayList<Number>());
буде.
Ще один приклад, коли значення підтипу є важливим. Поміркуйте:
Super sup = new Sub();
Number n = sup.method(1);
де
class Super {
Number method(Number n) { ... }
}
class Sub extends Super {
@Override
Number method(Number n);
}
Неофіційно час виконання буде переписано на:
class Super {
Number method(Number n) {
if (this instanceof Sub) {
return ((Sub) this).method(n); // *
} else {
...
}
}
}
Для компіляції позначеного рядка параметр методу методу переосмислення повинен бути супертипом параметру методу заміненого методу, а тип повернення - підтипом методу, що перекрито. Формально кажучи, він f(A) = parametertype(method asdeclaredin(A))
повинен бути принаймні протилежним, а якщо f(A) = returntype(method asdeclaredin(A))
повинен бути принаймні коваріантним.
Зверніть увагу на "принаймні" вище. Це мінімальні вимоги, які виконує будь-яка розумна статично типова безпечна об'єктно-орієнтована мова програмування, але мова програмування може вибрати більш сувору. У випадку Java 1.4 типи параметрів та типи повернення методів повинні бути однаковими (за винятком стирання типу) при переосмисленні методів, тобто parametertype(method asdeclaredin(A)) = parametertype(method asdeclaredin(B))
при переопределенні. Оскільки Java 1.5, ковариантні типи повернення дозволені при переопределенні, тобто наступне буде компілюватися в Java 1.5, але не в Java 1.4:
class Collection {
Iterator iterator() { ... }
}
class List extends Collection {
@Override
ListIterator iterator() { ... }
}
Я сподіваюся, що я все накрив, а точніше - почухав поверхню. І все-таки сподіваюся, що це допоможе зрозуміти абстрактне, але важливе поняття дисперсії типу.