JavaRush /בלוג Java /Random-HE /בואו נפרק את המחלקה StringUtils
Roman Beekeeper
רָמָה

בואו נפרק את המחלקה StringUtils

פורסם בקבוצה
שלום לכולם, קוראים יקרים שלי. אני מנסה לכתוב על מה שבאמת מעניין אותי ומה שמדאיג אותי כרגע. לכן, היום תהיה קצת קריאה קלה שתהיה שימושית עבורך בתור התייחסות בעתיד: בואו נדבר על StringUtils . בואו נפרק את המחלקה StringUtils - 1זה קרה שבזמן מסוים עקפתי את ספריית Apache Commons Lang 3 . זוהי ספרייה עם שיעורי עזר לעבודה עם אובייקטים שונים. זהו אוסף של שיטות שימושיות לעבודה עם מחרוזות, אוספים וכדומה. בפרויקט נוכחי, שבו נאלצתי לעבוד ביתר פירוט עם מחרוזות בתרגום לוגיקה עסקית בת 25 שנה (מ-COBOL ל-Java), התברר שאין לי ידע עמוק מספיק במחלקה StringUtils . אז הייתי צריך ליצור הכל בעצמי. מה שאני מתכוון? העובדה שאתה לא צריך לכתוב משימות מסוימות הכרוכות במניפולציה של מחרוזת בעצמך, אלא להשתמש בפתרון מוכן. מה רע בלכתוב את זה בעצמך? לפחות בזה שזה יותר קוד שכבר נכתב מזמן. לא פחות דחוף הנושא של בדיקת הקוד שנכתב בנוסף. כאשר אנו משתמשים בספרייה שהוכיחה את עצמה כטובה, אנו מצפים שהיא כבר נבדקה ושלא נצטרך לכתוב חבורה של מקרי בדיקה כדי לבדוק אותה. במקרה, מערך השיטות לעבודה עם מחרוזת ב-Java אינו כה גדול. אין באמת הרבה שיטות שיהיו שימושיות לעבודה. מחלקה זו נוצרה גם כדי לספק בדיקות עבור NullPointerException. המתאר של המאמר שלנו יהיה כדלקמן:
  1. כיצד לחבר?
  2. דוגמאות מהעבודה שלי: איך, בלי לדעת על שיעור כל כך שימושי, יצרתי את קב האופניים שלי .
  3. בואו נסתכל על שיטות אחרות שמצאתי מעניינות.
  4. בואו נסכם.
כל המקרים יתווספו למאגר נפרד בארגון Javarush Community ב-GitHub. יהיו דוגמאות ומבחנים נפרדים עבורם.

0. איך להתחבר

מי שהולכים איתי יד ביד כבר מכירים פחות או יותר גם את Git וגם את Maven, אז בהמשך אני אסתמך על הידע הזה ולא אחזור על עצמי. למי שפספס את המאמרים הקודמים שלי או שרק התחיל לקרוא, הנה חומרים על Maven ו- Git . כמובן שללא מערכת בנייה (Maven, Gredl), אפשר לחבר הכל גם ידנית, אבל זה מטורף בימינו ובטח שלא צריך לעשות את זה ככה: עדיף מיד ללמוד איך לעשות הכל נכון. לכן, כדי לעבוד עם Maven, אנו מוסיפים תחילה את התלות המתאימה:
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
כאשר ${apache.common.version} היא הגרסה של ספרייה זו. לאחר מכן, כדי לייבא למחלקה כלשהי, הוסף ייבוא:
import org.apache.commons.lang3.StringUtils;
וזהו, הכל בתיק))

1. דוגמאות מפרויקט אמיתי

  • שיטת leftPad

הדוגמה הראשונה בדרך כלל נראית כל כך מטופשת עכשיו שזה טוב מאוד שהקולגות שלי ידעו על StringUtils.leftPad ואמרו לי. מה הייתה המשימה: הקוד נבנה בצורה כזו שהיה צורך להפוך את הנתונים אם הם לא הגיעו בצורה נכונה. היה צפוי ששדה המחרוזת יכלול רק מספרים, כלומר. אם האורך שלו הוא 3 והערך שלו הוא 1, הערך צריך להיות "001". כלומר, תחילה עליך להסיר את כל הרווחים, ולאחר מכן לכסות אותו באפסים. דוגמאות נוספות כדי להבהיר את מהות המשימה: מ-"12" -> "012" מ-"1" -> "001" וכן הלאה. מה עשיתי? תיאר זאת במחלקה LeftPadExample . כתבתי שיטה שתעשה את כל זה:
public static String ownLeftPad(String value) {
   String trimmedValue = value.trim();

   if(trimmedValue.length() == value.length()) {
       return value;
   }

   StringBuilder newValue = new StringBuilder(trimmedValue);

   IntStream.rangeClosed(1, value.length() - trimmedValue.length())
           .forEach(it -> newValue.insert(0, "0"));
   return newValue.toString();
}
כבסיס, לקחתי את הרעיון שאנחנו יכולים פשוט לקבל את ההבדל בין הערך המקורי לערך הגזום ולמלא אותו באפסים מלפנים. לשם כך השתמשתי ב-IntStream כדי לבצע את אותה פעולה n פעמים. וזה בהחלט צריך להיבדק. הנה מה שהייתי יכול לעשות אם הייתי יודע על שיטת StringUtils.leftPad מראש :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
כפי שאתה יכול לראות, יש הרבה פחות קוד, וגם ספרייה שאושרה על ידי כולם משמשת. לצורך כך יצרתי שני מבחנים במחלקה LeftPadExampleTest (בדרך כלל כשהם מתכננים לבדוק מחלקה, הם יוצרים מחלקה עם אותו שם + טסט באותה חבילה, רק ב-src/test/java). בדיקות אלו בודקות שיטה אחת כדי להבטיח שהיא הופכת את הערך בצורה נכונה, ולאחר מכן שיטה אחרת. כמובן, יהיה צורך לכתוב הרבה יותר מבחנים, אבל בדיקה היא לא הנושא העיקרי במקרה שלנו:
package com.github.javarushcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Unit-level testing for LeftPadExample")
class LeftPadExampleTest {

   @DisplayName("Should transform by using ownLeftPad method as expected")
   @Test
   public void shouldTransformOwnLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.ownLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

}
אני יכול להעיר כמה הערות לגבי המבחנים לעת עתה. הם נכתבים באמצעות JUnit 5:
  1. מבחן יטופל כאל מבחן אם יש לו את ההערה המתאימה - @Test.
  2. אם השם קשה לתאר את פעולת הבדיקה או שהתיאור ארוך ולא נוח לקריאה, ניתן להוסיף את ההערה @DisplayName ולהפוך אותו לתיאור רגיל שייראה בעת הפעלת בדיקות.
  3. בכתיבת מבחנים אני משתמש בגישת BDD, בה אני מחלק את המבחנים לחלקים לוגיים:
    1. //given - בלוק הגדרת נתונים לפני הבדיקה;
    2. //מתי הוא הבלוק שבו מופעל החלק בקוד שאנו בודקים;
    3. //then הוא בלוק שבו בודקים את התוצאות של הבלוק when.
אם תפעיל אותם, הם יאשרו שהכל עובד כמצופה.

  • שיטת stripStart

כאן הייתי צריך לפתור בעיה עם שורה שיכולה להכיל רווחים ופסיקים בהתחלה. לאחר השינוי, לא הייתה צריכה להיות להם משמעות חדשה. הצהרת הבעיה ברורה מתמיד. כמה דוגמאות יחזקו את ההבנה שלנו: ", , ספרים" -> "ספרים" ",,, ספרים" -> "ספרים" b , ספרים" -> "ב, ספרים" כמו במקרה של leftPad, הוספתי את מחלקה StrimStartExample , שבה יש שתי שיטות. אחד - עם פתרון משלו:
public static String ownStripStart(String value) {
   int index = 0;
   List commaSpace = asList(" ", ",");
   for (int i = 0; i < value.length(); i++) {
       if (commaSpace.contains(String.valueOf(value.charAt(i)))) {
           index++;
       } else {
           break;
       }
   }
   return value.substring(index);
}
כאן הרעיון היה למצוא את האינדקס שמתחיל ממנו אין יותר רווחים או פסיקים. אם הם לא היו שם בכלל בהתחלה, אז המדד יהיה אפס. והשני - עם פתרון דרך StringUtils :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
כאן אנחנו מעבירים את המידע של הארגומנט הראשון לגבי איזו מחרוזת אנחנו עובדים, ובשני אנחנו מעבירים מחרוזת שמורכבת מתווים שצריך לדלג עליהם. אנו יוצרים את המחלקה StripStartExampleTest באותו אופן :
package com.github.javarushcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Unit-level testing for StripStartExample")
class StripStartExampleTest {

   @DisplayName("Should transform by using stripStart method as expected")
   @Test
   public void shouldTransformOwnStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.ownStripStart(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }
}

  • שיטת isEmpty

שיטה זו, כמובן, הרבה יותר פשוטה, אבל זה לא הופך אותה לפחות שימושית. זה מרחיב את היכולות של שיטת String.isEmpty() , אשר מוסיפה גם בדיקה של null. בשביל מה? כדי להימנע מ-NullPointerException, כלומר, להימנע מקריאת שיטות על משתנה שהוא null . לכן, כדי לא לכתוב:
if(value != null && value.isEmpty()) {
   //doing something
}
אתה יכול פשוט לעשות את זה:
if(StringUtils.isEmpty(value)) {
   //doing something
}
היתרון בשיטה זו הוא שברור מיד היכן משתמשים באיזו שיטה.

2. ניתוח של שיטות אחרות של המחלקה StringUtils

עכשיו בואו נדבר על אותן שיטות שלדעתי גם ראויות לתשומת לב. אם מדברים באופן כללי על StringUtils , כדאי לומר שהוא מספק שיטות בטוחות null המקבילות לאלו שנמצאות במחלקה String (כפי שקורה בשיטת isEmpty ). בואו נעבור עליהם:

  • השווה שיטת

שיטה כזו קיימת ב- String ותשליך NullPointerException אם, כאשר משווים בין שתי מחרוזות, אחת מהן היא null. כדי למנוע בדיקות מכוערות בקוד שלנו, אנחנו יכולים להשתמש בשיטת StringUtils.compare(String str1, String str2) : היא מחזירה int כתוצאה מההשוואה. מה המשמעות של ערכים אלו? int = 0 אם הם זהים (או שניהם null). int < 0, אם str1 קטן מ-str2. int > 0, אם str1 גדול מ-str2. כמו כן, אם אתה מסתכל על התיעוד שלהם, ה-Javadoc של שיטה זו מציג את התרחישים הבאים:
StringUtils.compare(null, null)   = 0
StringUtils.compare(null , "a")   < 0
StringUtils.compare("a", null)    > 0
StringUtils.compare("abc", "abc") = 0
StringUtils.compare("a", "b")     < 0
StringUtils.compare("b", "a")     > 0
StringUtils.compare("a", "B")     > 0
StringUtils.compare("ab", "abc")  < 0

  • מכיל... שיטות

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

    דוגמאות:

    StringUtils.contains(null, *)     = false
    StringUtils.contains(*, null)     = false
    StringUtils.contains("", "")      = true
    StringUtils.contains("abc", "")   = true
    StringUtils.contains("abc", "a")  = true
    StringUtils.contains("abc", "z")  = false

    שוב, אבטחת NPE (Null Pointer Exception) קיימת.

  2. containsAny היא שיטה הבודקת אם יש אחד מהתווים הקיימים במחרוזת. גם דבר שימושי: לעתים קרובות אתה צריך לעשות זאת.

    דוגמאות מהתיעוד:

    StringUtils.containsAny(null, *)                  = false
    StringUtils.containsAny("", *)                    = false
    StringUtils.containsAny(*, null)                  = false
    StringUtils.containsAny(*, [])                    = false
    StringUtils.containsAny("zzabyycdxx", ['z', 'a']) = true
    StringUtils.containsAny("zzabyycdxx", ['b', 'y']) = true
    StringUtils.containsAny("zzabyycdxx", ['z', 'y']) = true
    StringUtils.containsAny("aba", ['z'])             = false

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

  4. כמה דוגמאות מהמסמכים:

    StringUtils.containsIgnoreCase(null, *) = false
    StringUtils.containsIgnoreCase(*, null) = false
    StringUtils.containsIgnoreCase("", "") = true
    StringUtils.containsIgnoreCase("abc", "") = true
    StringUtils.containsIgnoreCase("abc", "a") = true
    StringUtils.containsIgnoreCase("abc", "z") = false
    StringUtils.containsIgnoreCase("abc", "A") = true
    StringUtils.containsIgnoreCase("abc", "Z") = false

  5. containsNone - אם לשפוט לפי השם, אתה כבר יכול להבין מה נבדק. לא אמורים להיות קווים בפנים. דבר שימושי, בהחלט. חיפוש מהיר של כמה דמויות לא רצויות ;). בבוט הטלגרם שלנו נסנן גסויות ולא נתעלם מהשיטות המצחיקות הללו.

    ודוגמאות, איפה היינו בלעדיהם:

    StringUtils.containsNone(null, *)       = true
    StringUtils.containsNone(*, null)       = true
    StringUtils.containsNone("", *)         = true
    StringUtils.containsNone("ab", '')      = true
    StringUtils.containsNone("abab", 'xyz') = true
    StringUtils.containsNone("ab1", 'xyz')  = true
    StringUtils.containsNone("abz", 'xyz')  = false

  • שיטת defaultString

סדרה של שיטות שעוזרות להימנע מהוספת מידע נוסף אם המחרוזת היא null ואתה צריך להגדיר ערך ברירת מחדל. יש הרבה אפשרויות שיתאימו לכל טעם. העיקרי ביניהם הוא StringUtils.defaultString(final String str, final String defaultStr) - במקרה str הוא null, פשוט נעביר את הערך ל- defaultStr . דוגמאות מהתיעוד:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
זה מאוד נוח לשימוש כשאתה יוצר שיעור POJO עם נתונים.

  • שיטת מחיקת רווח לבן

זוהי שיטה מעניינת, אם כי אין הרבה אפשרויות ליישום שלה. יחד עם זאת, אם מתעורר מקרה כזה, השיטה בהחלט תהיה שימושית מאוד. זה מסיר את כל הרווחים מהמחרוזת. בכל מקום בו נמצא הפער הזה, לא יישאר לו זכר))) דוגמאות מהמסמכים:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • מסתיים עם שיטה

מדבר בעד עצמו. זוהי שיטה שימושית מאוד: היא בודקת אם המחרוזת מסתיימת במחרוזת המוצעת או לא. זה הכרחי לעתים קרובות. כמובן, אתה יכול לכתוב את הצ'ק בעצמך, אבל שימוש בשיטה מוכנה ברור יותר נוח וטוב יותר. דוגמאות:
StringUtils.endsWith(null, null)      = true
StringUtils.endsWith(null, "def")     = false
StringUtils.endsWith("abcdef", null)  = false
StringUtils.endsWith("abcdef", "def") = true
StringUtils.endsWith("ABCDEF", "def") = false
StringUtils.endsWith("ABCDEF", "cde") = false
StringUtils.endsWith("ABCDEF", "")    = true
כפי שאתה יכול לראות, הכל מסתיים בשורה ריקה))) אני חושב שהדוגמה הזו (StringUtils.endsWith("ABCDEF", "") = true) היא רק בונוס, כי זה אבסורדי) יש גם שיטה ש מתעלם מהמקרה.

  • שיטה שווה

דוגמה מצוינת לשיטת null safe המשווה בין שתי מחרוזות. מה שלא נכניס לשם, התשובה תהיה שם, והיא תהיה ללא שגיאות. דוגמאות:
StringUtils.equals(null, null)   = true
StringUtils.equals(null, "abc")  = false
StringUtils.equals("abc", null)  = false
StringUtils.equals("abc", "abc") = true
StringUtils.equals("abc", "ABC") = false
כמובן שיש גם equalsIgnoreCase - הכל נעשה בדיוק באותו אופן, רק שאנחנו מתעלמים מהמקרה. בוא נראה?
StringUtils.equalsIgnoreCase(null, null)   = true
StringUtils.equalsIgnoreCase(null, "abc")  = false
StringUtils.equalsIgnoreCase("abc", null)  = false
StringUtils.equalsIgnoreCase("abc", "abc") = true
StringUtils.equalsIgnoreCase("abc", "ABC") = true

  • שווה לכל שיטה

בוא נמשיך ונרחיב את שיטת השווים . נניח שבמקום כמה בדיקות שוויון, אנחנו רוצים לבצע אחת. לשם כך, נוכל להעביר מחרוזת שאיתה תושווה קבוצה של מחרוזות; אם אחת מהן שווה לזו המוצעת, היא תהיה TRUE. אנו מעבירים מחרוזת ואוסף של מחרוזות כדי להשוות ביניהם (המחרוזת הראשונה עם המיתרים מהאוסף). קָשֶׁה? להלן דוגמאות מהמסמכים שיעזרו לך להבין למה הכוונה:
StringUtils.equalsAny(null, (CharSequence[]) null) = false
StringUtils.equalsAny(null, null, null)    = true
StringUtils.equalsAny(null, "abc", "def")  = false
StringUtils.equalsAny("abc", null, "def")  = false
StringUtils.equalsAny("abc", "abc", "def") = true
StringUtils.equalsAny("abc", "ABC", "DEF") = false
יש גם equalsAnyIgnoreCase . ודוגמאות לכך:
StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null) = false
StringUtils.equalsAnyIgnoreCase(null, null, null)    = true
StringUtils.equalsAnyIgnoreCase(null, "abc", "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", null, "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", "abc", "def") = true
StringUtils.equalsAnyIgnoreCase("abc", "ABC", "DEF") = true

שורה תחתונה

כתוצאה מכך, אנו עוזבים עם הידע של מה זה StringUtils ואיזה שיטות שימושיות יש לו. ובכן, מתוך הבנה שיש דברים כל כך שימושיים ואין צורך לגדר כל פעם בקביים במקומות בהם ניתן יהיה לסגור את הנושא בעזרת פתרון מוכן. באופן כללי, ניתחנו רק חלק מהשיטות. אם תרצו, אני יכול להמשיך: יש עוד הרבה כאלה, והם באמת ראויים לתשומת לב. אם יש לך רעיונות איך אפשר להציג את זה, אנא כתוב - אני תמיד פתוח לרעיונות חדשים. התיעוד לשיטות כתוב היטב, מתווספות דוגמאות מבחן עם תוצאות, מה שעוזר להבין טוב יותר את פעולת השיטה. לכן, אנחנו לא נרתעים מקריאת התיעוד: זה יפיג את הספקות שלך לגבי הפונקציונליות של כלי השירות. כדי לצבור ניסיון חדש בקידוד, אני ממליץ לך לבדוק כיצד מיוצרים וכותבים שיעורי עזר. זה יהיה שימושי בעתיד, מכיוון שבדרך כלל לכל פרויקט יש שיעורי גרוטאות משלו, והניסיון בכתיבתם יהיה שימושי. באופן מסורתי, אני מציע לך להירשם לחשבון שלי ב- Github ) למי שלא יודע על הפרויקט שלי עם בוט טלגרם, הנה הקישור למאמר הראשון . תודה לכולם על הקריאה. הוספתי כמה קישורים שימושיים למטה.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION