Въведение в родови типове в Java: ковариация и контравариация

Видове

Java е статично типизиран език, което означава, че първо трябва да декларирате променлива и нейния тип, преди да я използвате.

Например: int myInteger = 42;

Въведете общи типове.

Родови типове

Определение: „ Общият тип е родов клас или интерфейс, който е параметризиран по типове.“

По същество родовите типове ви позволяват да напишете общ родов клас (или метод), който работи с различни типове, позволявайки повторно използване на кода.

Вместо да посочвате objда бъде от intтип, или Stringтип, или друг тип, вие дефинирате Boxкласа да приема параметър тип <; T>. След това можете да nизползвате T, за да представите този родов тип във всяка част във вашия клас.

Сега въведете ковариация и контравариация.

Ковариация и контравариация

Определение

Вариантността се отнася до това как подтипирането между по-сложни типове се свързва с подтипирането между техните компоненти (източник).

Лесно за запомняне (и изключително неформално) определение за ковариация и контравариация е:

  • Ковариация: приема подтипове
  • Контравариантност: приемайте супертипове

Масиви

В Java масивите са ковариантни , което има 2 последици.

Първо, масив от тип T[]може да съдържа елементи от тип Tи неговите подтипове.

Number[] nums = new Number[5];nums[0] = new Integer(1); // Oknums[1] = new Double(2.0); // Ok

На второ място, масив от тип S[]е подтип на T[]if Sе подтип на T.

Integer[] intArr = new Integer[5];Number[] numArr = intArr; // Ok

Важно е обаче да запомните, че: (1) numArrе препратка към референтен тип Number[]към „действителния обект“ intArrот „действителния тип“ Integer[].

Следователно, следващият ред ще се компилира добре, но ще доведе до време на изпълнение ArrayStoreException(поради замърсяването на купчината):

numArr[0] = 1.23; // Not ok

Той създава изключение по време на изпълнение, тъй като Java по време на изпълнение знае, че „действителният обект“ intArrвсъщност е масив от Integer.

Дженерици

При общите типове Java няма начин да знае по време на изпълнение информацията за типа на параметрите на типа поради изтриване на типа. Следователно, той не може да предпази от замърсяване с купчини по време на работа.

Като такива, генеричните лекарства са инвариантни.

ArrayList intArrList = new ArrayList();ArrayList numArrList = intArrList; // Not okArrayList anotherIntArrList = intArrList; // Ok

Типовите параметри трябва да съвпадат точно, за да се предпазят от замърсяване с купчини.

Но въведете заместващи символи.

Заместващи символи, ковариация и контравариация

С заместващите символи е възможно генериците да поддържат ковариация и контравариация.

Променяйки предишния пример, получаваме това, което работи!

ArrayList intArrList = new ArrayList();ArrayList numArrList = intArrList; // Ok

Въпросният знак „?“ се отнася до заместващ символ, който представлява неизвестен тип. Той може да бъде по-ниско ограничен, което ограничава неизвестния тип да бъде определен тип или негов супертип.

Следователно в ред 2 се ? super Integerпревежда „всеки тип, който е тип Integer или неговият супертип“.

Можете също така да използвате горната граница на заместващия знак, който ограничава неизвестния тип да бъде определен тип или негов подтип, като използвате ? extends Integer.

Само за четене и само за писане

Ковариацията и контравариацията дават някои интересни резултати. Ковариантните типове са само за четене, докато контравариантните типове са само за запис.

Не забравяйте, че ковариантните типове приемат подтипове, така че ArrayLister> can contain any object that is either of a Number type or its subtype.

In this example, line 9 works, because we can be certain that whatever we get from the ArrayList can be upcasted to a Number type (because if it extends Number, by definition, it is a Number).

But nums.add() doesn’t work, because we cannot be sure of the “actual type” of the object. All we know is that it must be a Number or its subtypes (e.g. Integer, Double, Long, etc.).

With contravariance, the converse is true.

Line 9 works, because we can be certain that whatever the “actual type” of the object is, it must be Integer or its supertype, and thus accept an Integer object.

But line 10 doesn’t work, because we cannot be sure that we will get an Integer. For instance, nums could be referencing an ArrayList of Objects.

Applications

Therefore, since covariant types are read-only and contravariant types are write-only (loosely speaking), we can derive the following rule of thumb: “Producer extends, consumer super”.

A producer-like object that produces objects of type T can be of type parameter T>, while a consumer-like object that consumes objects oftype T can be of type parameter super T>.

Original text