גנריות בקוטלין

1. סקירה כללית

במאמר זה נבחן את ה- טיפוסים גנריים בשפת קוטלין.

הם דומים מאוד לאלו משפת ג'אווה, אך יוצרי שפת קוטלין ניסו להפוך אותם לקצת יותר אינטואיטיביים ומובנים על ידי הצגת מילות מפתח מיוחדות כמו הַחוּצָה ו ב.

2. יצירת שיעורים פרמטרים

בואו נגיד שאנחנו רוצים ליצור מחלקה פרמטרית. אנו יכולים לעשות זאת בקלות בשפת קוטלין באמצעות סוגים כלליים:

class ParameterizedClass (ערך שווי פרטי: A) {fun getValue (): A {return value}}

אנו יכולים ליצור מופע של מחלקה כזו על ידי הגדרת סוג פרמטר במפורש בעת שימוש בבנאי:

val parameterizedClass = ParameterizedClass ("string-value") val res = parameterizedClass.getValue () assertTrue (res is String)

למרבה המזל, קוטלין יכול להסיק את הסוג הגנרי מסוג הפרמטר, כך שנוכל להשמיט זאת בעת השימוש בבנאי:

val parameterizedClass = ParameterizedClass ("value-string") val res = parameterizedClass.getValue () assertTrue (res is String)

3. קוטלין הַחוּצָה ו ב מילות מפתח

3.1. ה הַחוּצָה מילת מפתח

בואו נגיד שאנחנו רוצים ליצור מחלקת מפיקים שתפיק תוצאה מסוג T. לפעמים; אנו רוצים להקצות ערך מיוצר להפניה שהיא מסוג supertype מסוג T.

כדי להשיג זאת באמצעות קוטלין, אנחנו צריכים להשתמש ב-הַחוּצָה מילת מפתח בסוג הגנרי. המשמעות היא שנוכל להקצות התייחסות זו לכל אחד מסוגי העל שלה. את ערך החוץ ניתן לייצר רק על ידי המחלקה הנתונה אך לא לצרוך אותה:

class ParameterisedProducer (ערך פרטי פרטי: T) {fun get (): T {return value}}

הגדרנו א ParameterizedProducer מחלקה שיכולה לייצר ערך מסוג T.

הַבָּא; אנו יכולים להקצות מופע של ה- ParameterizedProducer בכיתה להתייחסות שהיא סוג על זה:

val parameterizedProducer = ParameterizedProducer ("מחרוזת") val ref: ParameterizedProducer = parameterizedProducer assertTrue (ref is ParameterizedProducer)

אם הסוג ט בתוך ה יצרן פרמטרי הכיתה לא תהיה הַחוּצָה סוג, ההצהרה הנתונה תייצר שגיאת מהדר.

3.2. ה ב מילת מפתח

לפעמים יש לנו מצב הפוך שמשמעותו שיש לנו התייחסות לסוג ט ואנחנו רוצים להיות מסוגלים להקצות אותו לסוג המשנה של ט.

אנחנו יכולים להשתמש ב- ב מילת מפתח על הסוג הגנרי אם ברצוננו להקצות אותה להתייחסות לסוג המשנה שלה. ה במילת מפתח ניתן להשתמש רק בסוג הפרמטר הנצרך, ולא מיוצר:

class ParameterizedConsumer {fun toString (value: T): String {return value.toString ()}}

אנו מצהירים כי א toString () השיטה תצרוך רק ערך מסוג ט.

לאחר מכן נוכל להקצות הפניה לסוג מספר להתייחסות לסוג המשנה שלו - לְהַכפִּיל:

val parameterizedConsumer = ParameterizedConsumer () val ref: ParameterizedConsumer = parameterisedConsumer assertTrue (ref is ParameterizedConsumer)

אם הסוג ט בתוך ה ParameterizedCounsumer לא יהיה ה ב סוג, ההצהרה הנתונה תייצר שגיאת מהדר.

4. הקלד תחזיות

4.1. העתק מערך של סוגים שונים למערך של סוגי על

בואו נגיד שיש לנו מערך מסוג כלשהו, ​​ואנחנו רוצים להעתיק את כל המערך למערך של כל סוּג. זוהי פעולה חוקית, אך כדי לאפשר למהדר לקמפל את הקוד שלנו עלינו לשים לב לפרמטר הקלט עם ה- הַחוּצָה מילת מפתח.

זה מאפשר למהדר לדעת שארגומנט הקלט יכול להיות מכל סוג שהוא תת-סוג של ה- כל:

עותק מהנה (מ: Array, ל-: Array) {assert (from.size == to.size) עבור (i in from.indices) ל- [i] = מ- [i]}

אם ה מ הפרמטר אינו של בחוץ כלשהו סוג, לא נוכל להעביר מערך של Int הקלד כטענה:

val ints: Array = arrayOf (1, 2, 3) val any: Array = arrayOfNulls (3) copy (ints, any) assertEquals (any [0], 1) assertEquals (any [1], 2) assertEquals (any [ 2], 3)

4.2. הוספת אלמנטים של תת-סוג למערך של סוג העל שלו

בואו נגיד שיש לנו את המצב הבא - יש לנו מערך של כל סוג שהוא סוג על של Int ואנחנו רוצים להוסיף Int אלמנט למערך זה. עלינו להשתמש ב- ב מילת המפתח כסוג של מערך היעד כדי להודיע ​​למהדר שנוכל להעתיק את Int ערך למערך זה:

מילוי מהנה (dest: Array, value: Int) {dest [0] = value}

לאחר מכן נוכל להעתיק ערך של ה- Int הקלד למערך כל:

אובייקטים val: מערך = arrayOfNulls (1) מילוי (אובייקטים, 1) assertEquals (אובייקטים [0], 1)

4.3. תחזיות כוכבים

ישנם מצבים בהם לא אכפת לנו מסוג הערך הספציפי. בואו נגיד שאנחנו רק רוצים להדפיס את כל האלמנטים של המערך וזה לא משנה מה סוג האלמנטים במערך זה.

כדי להשיג זאת, אנו יכולים להשתמש בהקרנת כוכבים:

fun printArray (array: Array) {array.forEach {println (it)}}

לאחר מכן נוכל להעביר מערך מכל סוג שהוא ל- printArray () שיטה:

מערך val = arrayOf (1,2,3) printArray (array)

כאשר אנו משתמשים בסוג הייחוס של הקרנת כוכבים, אנו יכולים לקרוא ערכים ממנו, אך איננו יכולים לכתוב אותם מכיוון שהוא יגרום לשגיאת אוסף.

5. אילוצים כלליים

בואו נגיד שאנחנו רוצים למיין מערך של אלמנטים, וכל סוג אלמנט צריך ליישם a ניתן להשוות מִמְשָׁק. אנו יכולים להשתמש במגבלות הכלליות כדי לציין דרישה זו:

כֵּיף  מיין (רשימה: רשימה): רשימה {return list.sorted ()}

בדוגמה הנתונה, הגדרנו שכל האלמנטים ט צריך ליישם את ניתן להשוות מִמְשָׁק. אחרת, אם ננסה להעביר רשימת אלמנטים שאינם מיישמים ממשק זה, זה יגרום לשגיאת מהדר.

הגדרנו א סוג פונקציה שלוקחת כטיעון רשימה של אלמנטים המיישמים להשוואה, כדי שנוכל להתקשר ל מְמוּיָן() שיטה עליו. בואו נסתכל על מקרה הבדיקה לשיטה זו:

val listOfInts = listOf (5,2,3,4,1) val sorted = sort (listOfInts) assertEquals (מיון, listOf (1,2,3,4,5))

אנחנו יכולים להעביר בקלות רשימה של שקעים בגלל ה Int סוג מיישם את ניתן להשוות מִמְשָׁק.

5.1. גבולות עליונים מרובים

בעזרת סימון סוגר הזווית, אנו יכולים להכריז על מקסימום גבול עליון אחד. אם פרמטר סוג זקוק למספר גבולות עליונים גנריים, עלינו להשתמש בנפרד איפה סעיפים עבור אותו פרמטר מסוג מסוים. לדוגמה:

מיון מהנה (xs: List) שבו T: CharSequence, T: Comparable {// מיין את האוסף במקום}

כפי שמוצג לעיל, הפרמטר ט חייב ליישם את CharSequence ו ניתן להשוות ממשקים בו זמנית. באופן דומה, אנו יכולים להכריז על שיעורים עם גבולות עליונים כלליים מרובים:

class StringCollection (xs: List) כאשר T: CharSequence, T: השוואה {// הושמט}

6. גנריות בזמן ריצה

6.1. הקלד מחיקה

כמו ב- Java, גם הגנריות של קוטלין מָחוּק בזמן ריצה. זה, מופע של מחלקה כללית אינו שומר על פרמטרי הסוג שלו בזמן הריצה.

לדוגמא, אם ניצור א מַעֲרֶכֶת והכנסו אליו כמה מחרוזות, בזמן ריצה אנחנו מסוגלים לראות את זה רק כ מַעֲרֶכֶת.

בואו ניצור שניים סטים עם שני פרמטרים מסוגים שונים:

ספרי val: Set = setOf ("1984", "עולם חדש אמיץ") פריימים ראשוניים: Set = setOf (2, 3, 11)

בזמן הריצה, מידע הסוג עבור מַעֲרֶכֶת ו מַעֲרֶכֶת יימחק ואנחנו רואים את שניהם פשוטים סטים. לכן, למרות שאפשר בהחלט לגלות בזמן הריצה שהערך הוא a מַעֲרֶכֶת, אנחנו לא יכולים לדעת אם זה א מַעֲרֶכֶת של מיתרים, מספרים שלמים או משהו אחר: המידע הזה נמחק.

אז איך המהדר של קוטלין מונע מאיתנו להוסיף a ללא מחרוזת לתוך מַעֲרֶכֶת? או כשאנחנו מקבלים אלמנט מ- a מַעֲרֶכֶת, איך זה יודע שהאלמנט הוא a חוּט?

התשובה היא פשוטה. המהדר הוא זה שאחראי למחיקת מידע הסוג אבל לפני כן, זה באמת יודע את ספרים משתנה מכיל חוּט אלמנטים.

לכן, בכל פעם שנקבל ממנו אלמנט, המהדר היה משליך אותו אל חוּט או כשאנחנו נוסיף אלמנט לתוכו, המהדר יקליד את הקלט.

6.2. פרמטרים מסוג Reified

בואו נהנה יותר עם הגנריות וניצור פונקציית הרחבה לסינון אוסף אלמנטים המבוססים על סוגם:

כיף Iterable.filterIsInstance () = מסנן {זה T} שגיאה: לא ניתן לבדוק למשל מהסוג שנמחק: T

ה "זה לא" חלק, עבור כל רכיב אוסף, בודק אם האלמנט הוא מופע מסוג ט, אך מכיוון שמידע הסוג נמחק בזמן הריצה, איננו יכולים לשקף את פרמטרי הסוג בדרך זו.

או שאנחנו יכולים?

כלל מחיקת הסוגים נכון באופן כללי, אך יש מקרה אחד בו אנו יכולים להימנע ממגבלה זו: פונקציות מוטבעות. פרמטרים מסוגים של פונקציות מוטבעות יכולים להיות אישר, כדי שנוכל להתייחס לפרמטרים מסוג זה בזמן הריצה.

גוף הפונקציות המוטבעות מוטמע. כלומר, המהדר מחליף את הגוף ישירות למקומות בהם נקראת הפונקציה במקום הפעלת התפקוד הרגילה.

אם נכריז על הפונקציה הקודמת כ- בשורה וסמן את פרמטר הסוג כ- אישרואז נוכל לגשת למידע מסוג כללי בזמן ריצה:

כיף מוטבע Iterable.filterIsInstance () = מסנן {זה T}

האיחוד המובנה עובד כמו קסם:

>> val set = setOf ("1984", 2, 3, "World new brave", 11) >> println (set.filterIsInstance ()) [2, 3, 11]

בואו נכתוב דוגמא נוספת. כולנו מכירים את אותם SLF4j טיפוסיים כּוֹרֵת עֵצִים הגדרות:

מחלקה משתמש {יומן val פרטי = LoggerFactory.getLogger (משתמש :: class.java) // ...}

באמצעות פונקציות מוטבעות מאוחדות, אנו יכולים לכתוב אלגנטי יותר ומחריד תחביר פחות כּוֹרֵת עֵצִים הגדרות:

לוגר כיף מוטבע (): לוגר = LoggerFactory.getLogger (T :: class.java)

אז נוכל לכתוב:

מחלקה משתמש {יומן פרטי פרטי = לוגר () // ...}

זה נותן לנו אפשרות נקייה יותר ליישום רישום, בדרך קוטלין.

6.3. צלול עמוק לתוך אימות מוטבע

אז מה כל כך מיוחד בפונקציות מוטבעות כך שאיחוד סוג עובד רק איתן? כידוע, המהדר של קוטלין מעתיק את קוד הביס של פונקציות מוטבעות למקומות בהם נקראת הפונקציה.

מכיוון שבכל אתר שיחות, המהדר יודע את סוג הפרמטר המדויק, הוא יכול להחליף את פרמטר הסוג הגנרי בהפניות הסוג בפועל.

לדוגמא, כשאנחנו כותבים:

מחלקה משתמש {יומן פרטי פרטי = לוגר () // ...}

כאשר המהדר מכניס את ה- כּוֹרֵת עֵצִים() פונקציית שיחה, היא יודעת את פרמטר הסוג הגנרי בפועל -מִשׁתַמֵשׁ. אז במקום למחוק את פרטי הסוג, המהדר מנצל את הזדמנות האימות ומאמת מחדש את פרמטר הסוג בפועל.

7. מסקנה

במאמר זה בדקנו את הסוגים הגנריים של קוטלין. ראינו כיצד להשתמש ב- הַחוּצָה ו ב מילות מפתח כהלכה. השתמשנו בהקרנות מסוג והגדרנו שיטה כללית המשתמשת במגבלות גנריות.

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד בפרויקט GitHub - זהו פרויקט Maven, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.


$config[zx-auto] not found$config[zx-overlay] not found