מבוא ל- AutoValue

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

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

על מנת ליצור אובייקט מסוג ערך כל שעליך לעשות הוא לעשות להעלות הערה על שיעור מופשט עם @ AutoValue ביאור ולהרכיב את הכיתה שלך. מה שנוצר הוא אובייקט ערך עם שיטות אקססור, בנאי פרמטרי, שנעקף כראוי toString (), שווה ל- (Object) ו hashCode () שיטות.

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

@AutoValue מופשט בכיתה אדם {אדם סטטי ליצור (שם מחרוזת, גיל int) {להחזיר AutoValue_Person חדש (שם, גיל); } שם מחרוזת מופשט (); גיל מופשט מופשט (); } 

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

2. הגדרת Maven

כדי להשתמש ב- AutoValue בפרויקטים של Maven, עליך לכלול את התלות הבאה ב- pom.xml:

 com.google.auto.value ערך אוטומטי 1.2 

ניתן למצוא את הגרסה האחרונה באמצעות קישור זה.

3. אובייקטים מסוג ערך

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

3.1. מהם סוגי הערכים?

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

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

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

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

בנוסף, מחלקת הקלדת ערך חייבת להיות סופית, כך שהם לא ניתנים להרחבה, לפחות שמישהו יעקוף את השיטות. JavaBeans, DTOs ו- POJOs לא צריכים להיות סופיים.

3.2. יצירת סוג ערך

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

היינו עושים כיתה אחרונה ומסמנים את כל התחומים שלה כסופיים. ואז נשתמש ב- IDE כדי ליצור את הבנאי, את hashCode () השיטה, שווה (אובייקט) השיטה, גטרס כשיטות חובה ו toString () שיטה, והיה לנו שיעור כזה:

כיתת גמר ציבורית Foo {גמר פרטי טקסט מחרוזת; מספר אינטליגנטי פרטי פרטי; Foo פומבי (טקסט מחרוזת, מספר int) {this.text = טקסט; this.number = מספר; } // getters סטנדרטי @ Override public int hashCode () {return Objects.hash (טקסט, מספר); } @ עקירה ציבורית מחרוזת toString () {להחזיר "Foo [text =" + text + ", number =" + number + "]"; } @ עקוף בוליאני ציבורי שווה (אובייקט אובייקט) {אם (זה == אובייקט) יחזיר אמת; אם (obj == null) להחזיר שקר; אם (getClass ()! = obj.getClass ()) להחזיר שקר; Foo other = (Foo) obj; אם (מספר! = אחר.מספר) להחזיר שקר; אם (טקסט == null) {אם (other.text! = null) להחזיר שקר; } אחרת אם (! text.equals (other.text)) {return false; } להחזיר נכון; }}

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

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

לכן, אפילו שינוי שדה של אותו אובייקט ישנה את hashCode ערך.

3.3. כיצד עובדים סוגי ערכים

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

בכל פעם שאנחנו רוצים להשוות בין שני אובייקטים שהוקלדו לערך, עלינו, אם כן, להשתמש ב- שווה (אובייקט) שיטת ה- לְהִתְנַגֵד מעמד.

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

יתר על כן, כדי שנשתמש באובייקטים הערכיים שלנו באוספים מבוססי-חשיש כמו HashSets ו- מפת גיבובללא שבירה, עלינו ליישם כראוי את hashCode () שיטה.

3.4. מדוע אנו זקוקים לסוגי ערך

הצורך בסוגי ערך עולה לעתים קרובות למדי. אלה מקרים שבהם אנו רוצים לבטל את התנהגות ברירת המחדל של המקור לְהִתְנַגֵד מעמד.

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

בהנחה שנרצה ליצור אובייקט כסף באופן הבא:

מחלקה ציבורית MutableMoney {סכום פרטי פרטי; מטבע מחרוזת פרטי; MutableMoney ציבורי (סכום ארוך, מטבע מחרוזת) {this.amount = סכום; this.currency = מטבע; } // גטרים וקובעים סטנדרטיים}

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

@Test הציבור בטל givenTwoSameValueMoneyObjects_whenEqualityTestFails_thenCorrect () {MutableMoney m1 = חדש MutableMoney (10000, "דולר"); MutableMoney m2 = MutableMoney חדש (10000, "דולר"); assertFalse (m1.equals (m2)); }

שימו לב לסמנטיקה של המבחן.

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

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

עכשיו בואו ניצור אובייקט ערך שווה ערך והפעם נאפשר ל- IDE לייצר את רוב הקוד:

שיעור גמר ציבורי ImmutableMoney {סכום פרטי פרטי סופי; מטבע מחרוזת סופי פרטי; ImmutableMoney הציבור (סכום ארוך, מטבע מחרוזת) {this.amount = סכום; this.currency = מטבע; } @Override public int hashCode () {final int prime = 31; תוצאה int = 1; תוצאה = ראשית * תוצאה + (int) (סכום ^ (כמות >>> 32)); תוצאה = ראשית * תוצאה + ((מטבע == null)? 0: currency.hashCode ()); תוצאת החזרה; } @ עקוף בוליאני ציבורי שווה (אובייקט אובייקט) {אם (זה == אובייקט) יחזיר אמת; אם (obj == null) להחזיר שקר; אם (getClass ()! = obj.getClass ()) להחזיר שקר; ImmutableMoney אחר = (ImmutableMoney) obj; אם (סכום! = אחר.סכום) להחזיר שקר; if (currency == null) {if (other.currency! = null) להחזיר false; } אחרת אם (! currency.equals (other.currency)) מחזירים שקר; לחזור אמיתי; }}

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

@Test הציבור בטל givenTwoSameValueMoneyValueObjects_whenEqualityTestPasses_thenCorrect () {ImmutableMoney m1 = חדש ImmutableMoney (10000, "דולר"); ImmutableMoney m2 = חדש ImmutableMoney (10000, "דולר"); assertTrue (m1.equals (m2)); }

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

4. מדוע הערכה אוטומטית?

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

4.1. סוגיות עם קידוד ידני

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

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

עקיפת ה- hashCode () ו equalTo (אובייקט) שיטות דורשות כ 9 שורות ו 18 שורות בהתאמה ועוקפת את toString () השיטה מוסיפה עוד חמש שורות.

פירוש הדבר שבסיס קוד מעוצב היטב עבור מחלקת השדה בשני שלנו ייקח בערך 50 שורות קוד.

4.2 תעודות זהות להצלה?

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

קדימה מהירה, כמה חודשים בהמשך הדרך, מניחה שעלינו לבקר שוב בקוד שלנו ולבצע תיקונים בקוד שלנו כֶּסֶף שיעורים ואולי להמיר את מַטְבֵּעַ שדה מה חוּט הקלד לסוג ערך אחר שנקרא מַטְבֵּעַ.

4.3 תעודות זהות לא ממש מועילות

IDE כמו Eclipse לא יכול פשוט לערוך עבורנו את שיטות הגישה שלנו וגם את toString (), hashCode () אוֹ שווה (אובייקט) שיטות.

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

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

5. דוגמה אוטומטית לערך

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

אנו נסתכל על אותו הדבר כֶּסֶף לדוגמא, אך הפעם עם AutoValue. אנחנו נקרא לשיעור הזה AutoValueMoney למען העקביות:

@AutoValue מחלקה מופשטת ציבורית AutoValueMoney {תקציר ציבורי מחרוזת getCurrency (); תקציר ציבורי ארוך getAmount (); AutoValueMoney ליצור סטטי ציבורי (מטבע מחרוזת, סכום ארוך) {להחזיר AutoValue_AutoValueMoney חדש (מטבע, סכום); }}

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

מחלקה סופית ציבורית AutoValue_AutoValueMoney מרחיב AutoValueMoney {מטבע מחרוזת סופי פרטי; סכום סופי פרטי פרטי; AutoValue_AutoValueMoney (מטבע מחרוזת, סכום ארוך) {if (currency == null) לזרוק NullPointerException חדש (מטבע); this.currency = מטבע; this.amount = סכום; } // getters סטנדרטיים @ Override int hashCode ציבורי () {int h = 1; h * = 1000003; h ^ = currency.hashCode (); h * = 1000003; h ^ = כמות; להחזיר ח; } @ עקוף בוליאני ציבורי שווה (אובייקט o) {אם (o == זה) {להחזיר נכון; } אם (o מופע של AutoValueMoney) {AutoValueMoney that = (AutoValueMoney) o; החזר (this.currency.equals (that.getCurrency ())) && (this.amount == that.getAmount ()); } להחזיר שקר; }}

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

ג'אבאק תמיד יחדש קוד מעודכן עבורנו.

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

הנה מבחן המאמת כי השדות שלנו מוגדרים כהלכה:

@Test ציבורי בטל givenValueTypeWithAutoValue_whenFieldsCorrectlySet_thenCorrect () {AutoValueMoney m = AutoValueMoney.create ("USD", 10000); assertEquals (m.getAmount (), 10000); assertEquals (m.getCurrency (), "USD"); }

מבחן לאימות שניים AutoValueMoney אובייקטים עם אותו מטבע ובאותה סכום בודקים שווים:

@Test ציבורי בטל given2EqualValueTypesWithAutoValue_whenEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("USD", 5000); AutoValueMoney m2 = AutoValueMoney.create ("דולר", 5000); assertTrue (m1.equals (m2)); }

כאשר אנו משנים את סוג המטבע של אובייקט כסף אחד ל- GBP, הבדיקה: 5000 ליש"ט == 5000 דולר כבר לא נכון:

@Test הציבור בטל given2DifferentValueTypesWithAutoValue_whenNotEqual_thenCorrect () {AutoValueMoney m1 = AutoValueMoney.create ("GBP", 5000); AutoValueMoney m2 = AutoValueMoney.create ("דולר", 5000); assertFalse (m1.equals (m2)); }

6. ערך אוטומטי עם בונים

הדוגמה הראשונית שבדקנו מכסה את השימוש הבסיסי ב- AutoValue בשיטת מפעל סטטית כ- API שלנו ליצירה ציבורית.

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

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

כדי לפתור בעיה זו עלינו להשתמש ב- בּוֹנֶה תבנית. לְמַרְבֶּה הַמַזָל. זה יכול להיווצר על ידי AutoValue.

מחלקת AutoValue שלנו לא באמת משתנה הרבה, אלא ששיטת המפעל הסטטי מוחלפת על ידי בונה:

@ AutoValue תקציר מופשט ציבורי AutoValueMoneyWithBuilder {תקציר ציבורי מחרוזת getCurrency (); תקציר ציבורי ארוך getAmount (); Builder Builder סטטי () {להחזיר AutoValue_AutoValueMoneyWithBuilder.Builder חדש (); } @ AutoValue.Builder מחלקה סטטית מופשטת Builder {abstract Builder setCurrency (מטבע מחרוזת); set Builder מופשט setAmount (כמות ארוכה); תבנית AutoValueMoneyWithBuilder מופשטת (); }}

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

הכיתה הסופית סטטי Builder מרחיב את AutoValueMoneyWithBuilder.Builder {מטבע מחרוזת פרטי; סכום פרטי פרטי; Builder () {} Builder (AutoValueMoneyWithBuilder source) {this.currency = source.getCurrency (); this.amount = source.getAmount (); } @Override AutoValueMoneyWithBuilder.Builder הציבורי setCurrency (מטבע מחרוזת) {this.currency = currency; להחזיר את זה; } @Override AutoValueMoneyWithBuilder.Builder setAmount (סכום ארוך) {this.amount = סכום; להחזיר את זה; } @Override AutoValueMoneyWithBuilder build build () {String חסר = ""; אם (מטבע == null) {חסר + = "מטבע"; } אם (סכום == 0) {חסר + = "סכום"; } אם (! missing.isEmpty ()) {זרוק IllegalStateException חדש ("חסרים מאפיינים נדרשים:" + חסר); } להחזיר AutoValue_AutoValueMoneyWithBuilder (מטבע זה, סכום זה); }}

שימו לב גם כיצד תוצאות הבדיקה לא משתנות.

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

@Test הציבור בטל givenValueTypeWithBuilder_whenFieldsCorrectlySet_thenCorrect () {AutoValueMoneyWithBuilder m = AutoValueMoneyWithBuilder.builder (). setAmount (5000) .setCurrency ("דולר"). build (); assertEquals (m.getAmount (), 5000); assertEquals (m.getCurrency (), "USD"); }

כדי לבדוק ששוויון תלוי במצב פנימי:

@Test ציבורי בטל given2EqualValueTypesWithBuilder_whenEqual_thenCorrect () {AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder () .setAmount (5000). SetCurrency ("USD"). Build (); AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder () .setAmount (5000). SetCurrency ("USD"). Build (); assertTrue (m1.equals (m2)); }

וכאשר ערכי השדה שונים:

@Test הציבור בטל given2DifferentValueTypesBuilder_whenNotEqual_thenCorrect () {AutoValueMoneyWithBuilder m1 = AutoValueMoneyWithBuilder.builder () .setAmount (5000). SetCurrency ("USD"). Build (); AutoValueMoneyWithBuilder m2 = AutoValueMoneyWithBuilder.builder () .setAmount (5000). SetCurrency ("GBP"). Build (); assertFalse (m1.equals (m2)); }

7. מסקנה

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

אלטרנטיבה ל- AutoValue של גוגל היא פרויקט לומבוק - תוכלו להציץ במאמר ההיכרות בנושא השימוש בלומבוק כאן.

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


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