Ковариантность начинается, когда мы делаем class BuildCircle extends BuildShape
.
(Лучше BuildShape оформить как интерфейс, а не как класс, но шут с ним.)
Ковариантность связана с принципом подстановки Лисков: чтобы потомок вписывался в контракт, установленный предком, он может ужесточать требования к себе (скажем, выдавать более узкий тип) и ослаблять требования к другим (скажем, принимать поток реального времени вроде консоли/сокета — а не только файл, который знает себе длину и позволяет перемотку).
Вот это «выдавать более узкий тип, чем полагает предок» — и есть ковариантность.
Обратное — принимать поток реального времени, а не только файл — называется контравариантность. Насколько мне известно, в Java на уровне языка её нет, но какие-то части ухитряются делать через шаблоны.