Εισαγωγή στους γενικούς τύπους στην 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[]εάν 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.

Generics

Με τους γενικούς τύπους, η Java δεν έχει κανέναν τρόπο να γνωρίζει κατά το χρόνο εκτέλεσης τις πληροφορίες τύπου των παραμέτρων τύπου, λόγω διαγραφής τύπου. Επομένως, δεν μπορεί να προστατεύσει από τη ρύπανση του σωρού κατά το χρόνο εκτέλεσης.

Ως εκ τούτου, τα γενόσημα είναι αμετάβλητα.

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

Οι παράμετροι τύπου πρέπει να ταιριάζουν ακριβώς, για προστασία από τη ρύπανση του σωρού.

Αλλά εισαγάγετε μπαλαντέρ.

Μπαλαντέρ, συνδιακύμανση και παράβαση

Με χαρακτήρες μπαλαντέρ, οι γενόσημοι μπορούν να υποστηρίξουν τη συνδιακύμανση και την παράβαση.

Ακολουθώντας το προηγούμενο παράδειγμα, έχουμε αυτό που λειτουργεί!

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

Το ερωτηματικό ";" αναφέρεται σε μπαλαντέρ που αντιπροσωπεύει άγνωστο τύπο. Μπορεί να έχει χαμηλότερο όριο, γεγονός που περιορίζει τον άγνωστο τύπο να είναι συγκεκριμένος τύπος ή ο τύπος.

Επομένως, στη γραμμή 2, ? super Integerμεταφράζεται σε «οποιονδήποτε τύπο που είναι ακέραιος τύπος ή ο τύπος του».

Θα μπορούσατε επίσης να οριοθετήσετε το μπαλαντέρ, το οποίο περιορίζει τον άγνωστο τύπο ως συγκεκριμένο τύπο ή τον υποτύπο του, χρησιμοποιώντας ? extends Integer.

Μόνο για ανάγνωση και μόνο για εγγραφή

Η συνδιακύμανση και η αντίθεση παράγουν μερικά ενδιαφέροντα αποτελέσματα. Οι τύποι συνδιακύμανσης είναι μόνο για ανάγνωση, ενώ οι τύποι παραλλαγής είναι μόνο για εγγραφή.

Να θυμάστε ότι οι τύποι συνδιαλλαγής δέχονται υποτύπους, έτσι ArrayList er> 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>.