אתחול עצלן בקוטלין

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

במאמר זה נבחן את אחת התכונות המעניינות ביותר בתחביר קוטלין - אתחול עצל.

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

2. תבנית אתחול עצלה בג'אווה

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

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

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

class class ClassWithHeavyInitialization {private ClassWithHeavyInitialization () {} class static private LazyHolder {final final final ClassWithHeavyInitialization INSTANCE = new ClassWithHeavyInitialization (); } ClassWithHeavyInitialization static ציבורי getInstance () {return LazyHolder.INSTANCE; }}

שימו לב איך, רק מתי נקרא ל- getInstance () שיטה ב ClassWithHeavyInitialization, הסטטי LazyHolder class יטען, והמופע החדש של ה- ClassWithHeavyInitialization יווצר. לאחר מכן, המופע יוקצה ל- סטָטִיסופילמשל התייחסות.

אנחנו יכולים לבדוק את זה getInstance () מחזיר את אותו מופע בכל פעם שהוא נקרא:

@Test ציבורי בטל giveHeavyClass_whenInitLazy_thenShouldReturnInstanceOnFirstCall () {// כאשר ClassWithHeavyInitialization classWithHeavyInitialization = ClassWithHeavyInitialization.getInstance (); ClassWithHeavyInitialization classWithHeavyInitialization2 = ClassWithHeavyInitialization.getInstance (); // ואז assertTrue (classWithHeavyInitialization == classWithHeavyInitialization2); }

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

3. אתחול עצלן בקוטלין

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

כדי ליצור אובייקט שיאותחל מאותה הגישה הראשונה אליו, אנו יכולים להשתמש ב- עָצֵל שיטה:

@Test fun givenLazyValue_whenGetIt_thenShouldInitializeItOnlyOnce () {// given value numberOfInitializations: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization by lazy {numberOfInitializations.incrementAndal numberOfInitializations.get (), 1)}

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

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

אנחנו יכולים להעביר את LazyThreadSafetyMode כטיעון ל עָצֵל פוּנקצִיָה. מצב פרסום ברירת המחדל הוא מסונכרןכלומר רק שרשור אחד יכול לאתחל את האובייקט הנתון.

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

בואו נסתכל על התרחיש הזה:

כיף @Test whenGetItUsingPublication_thenCouldInitializeItMoreThanOnce () {// נתון numberOfInitializations Val: AtomicInteger = AtomicInteger () val lazyValue: ClassWithHeavyInitialization ידי עצלן (LazyThreadSafetyMode.PUBLICATION) {numberOfInitializations.incrementAndGet () ClassWithHeavyInitialization ()} Val executorService = Executors.newFixedThreadPool (2) Val countDownLatch = CountDownLatch (1) // כאשר executorService.send {countDownLatch.await (); println (lazyValue)} executorService.submit {countDownLatch.await (); println (lazyValue)} countDownLatch.countDown () // ואז executorService.awaitTermination (1, TimeUnit.SECONDS) executorService.shutdown () assertEquals (numberOfInitializations.get (), 2)}

אנו יכולים לראות שהתחלת שני שרשורים בו זמנית גורמת לאתחול של ה- ClassWithHeavyInitialization שיקרה פעמיים.

יש גם מצב שלישי - אף אחד - אך אסור להשתמש בה בסביבה מרובת הברגה מכיוון שהתנהגותה אינה מוגדרת.

4. של קוטלין לטנית

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

קוטלין: יש לאתחל את הנכס או להיות מופשט

זה בעצם אומר שעלינו לאתחל את המשתנה או לסמן אותו כ תַקצִיר.

מצד שני, ישנם מקרים שבהם ניתן להקצות את המשתנה באופן דינמי באמצעות הזרקת תלות למשל.

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

lateinit var a: String @Test fun givenLateInitProperty_whenAccessItAfterInit_thenPass () {// when a = "it" println (a) // then not throw}

אם נשכח לאתחל את לטנית נכס, נקבל UninitializedPropertyAccessException:

@Test (צפוי = UninitializedPropertyAccessException :: class) כיף givenLateInitProperty_whenAccessItWithoutInit_thenThrow () {// כאשר println (a)}

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

lateinit var value: Int

ואם נעשה זאת, היינו מקבלים שגיאת אוסף:

קוטלין: לא ניתן להשתמש בשינוי 'lateinit' בתכונות של סוגים פרימיטיביים

5. מסקנה

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

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

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

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד ב- GitHub.


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