JavaRush /مدونة جافا /Random-AR /دعونا نحلل فئة StringUtils
Roman Beekeeper
مستوى

دعونا نحلل فئة StringUtils

نشرت في المجموعة
مرحبا بالجميع، عزيزي القراء. أحاول أن أكتب عما يهمني حقًا وما يقلقني في الوقت الحالي. لذلك، اليوم سيكون هناك بعض القراءة الخفيفة التي ستكون مفيدة لك كمرجع في المستقبل: دعونا نتحدث عن StringUtils . دعونا نحلل فئة StringUtils - 1لقد حدث أنني تجاوزت مكتبة Apache Commons Lang 3 ذات مرة . هذه مكتبة بها فئات مساعدة للعمل مع كائنات مختلفة. هذه مجموعة من الأساليب المفيدة للعمل مع السلاسل والمجموعات وما إلى ذلك. في المشروع الحالي، حيث اضطررت إلى العمل بمزيد من التفصيل مع السلاسل في ترجمة منطق الأعمال البالغ من العمر 25 عامًا (من COBOL إلى Java)، اتضح أنه لم يكن لدي معرفة عميقة كافية بفئة StringUtils . لذلك كان علي أن أصنع كل شيء بنفسي. الذي أقصده؟ الحقيقة هي أنك لست مضطرًا إلى كتابة مهام معينة تتضمن التلاعب بالسلاسل بنفسك، ولكن استخدم حلاً جاهزًا. ما العيب في كتابتها بنفسك؟ على الأقل هذا هو المزيد من التعليمات البرمجية التي تمت كتابتها بالفعل منذ وقت طويل. ما لا يقل إلحاحًا هو مسألة اختبار الكود المكتوب بالإضافة إلى ذلك. عندما نستخدم مكتبة أثبتت أنها جيدة، نتوقع أنها قد تم اختبارها بالفعل وأننا لا نحتاج إلى كتابة مجموعة من حالات الاختبار لاختبارها. يحدث أن مجموعة طرق العمل مع سلسلة في Java ليست كبيرة جدًا. لا يوجد حقًا العديد من الطرق التي قد تكون مفيدة للعمل. تم إنشاء هذه الفئة أيضًا لتوفير عمليات التحقق من NullPointerException. الخطوط العريضة لمقالتنا ستكون على النحو التالي:
  1. كيفية الاتصال؟
  2. أمثلة من عملي: كيف، دون أن أعرف عن مثل هذا الفصل المفيد، قمت بإنشاء عكاز دراجتي .
  3. دعونا نلقي نظرة على الطرق الأخرى التي وجدتها مثيرة للاهتمام.
  4. دعونا نلخص.
ستتم إضافة جميع الحالات إلى مستودع منفصل في مؤسسة مجتمع Javarush على 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. // معين - كتلة إعداد البيانات قبل الاختبار؛
    2. // متى يتم تشغيل الكتلة التي يتم فيها تشغيل جزء الكود الذي نختبره؛
    3. // ثم عبارة عن كتلة يتم فيها فحص نتائج الكتلة.
إذا قمت بتشغيلها، فسوف تؤكد أن كل شيء يعمل كما هو متوقع.

  • طريقة قطاع البداية

كنت هنا بحاجة إلى حل مشكلة تتعلق بخط يمكن أن يحتوي على مسافات وفواصل في البداية. بعد التحول، لا ينبغي أن يكون لها معنى جديد. بيان المشكلة هو أكثر وضوحا من أي وقت مضى. ستعزز بعض الأمثلة فهمنا: "، ، كتب" -> "كتب" "،،، كتب" -> "كتب" ب ، كتب" -> "ب ، كتب" كما في حالة 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);
   }
}

  • طريقة فارغة

هذه الطريقة بالطبع أبسط بكثير، لكن هذا لا يجعلها أقل فائدة. فهو يوسع إمكانيات التابع String.isEmpty() ‎، والذي يضيف أيضًا التحقق من القيمة null. لماذا؟ لتجنب NullPointerException، أي تجنب استدعاء الأساليب على متغير null . لذلك، لكي لا أكتب:
if(value != null && value.isEmpty()) {
   //doing something
}
يمكنك ببساطة القيام بذلك:
if(StringUtils.isEmpty(value)) {
   //doing something
}
ميزة هذه الطريقة هي أنه من الواضح على الفور أين يتم استخدام الطريقة.

2. تحليل الطرق الأخرى لفئة StringUtils

الآن دعونا نتحدث عن تلك الأساليب التي، في رأيي، تستحق الاهتمام أيضًا. عند التحدث بشكل عام عن StringUtils ، تجدر الإشارة إلى أنها توفر طرقًا آمنة فارغة مماثلة لتلك الموجودة في فئة String (كما هو الحال مع الأسلوب isEmpty ). دعنا نذهب من خلالهم:

  • طريقة المقارنة

توجد مثل هذه الطريقة في السلسلة وستطرح NullPointerException إذا كانت إحداهما فارغة عند مقارنة سلسلتين. لتجنب عمليات التحقق القبيحة في الكود الخاص بنا، يمكننا استخدام طريقة StringUtils.compare(String str1, String str2) : فهي تُرجع int كنتيجة للمقارنة. ماذا تعني هذه القيم؟ int = 0 إذا كانا متماثلين (أو كلاهما فارغان). 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. يحتوي على طريقة تتحقق مما إذا كانت السلسلة المتوقعة موجودة داخل سلسلة أخرى. كيف يكون هذا مفيدا؟ يمكنك استخدام هذه الطريقة إذا كنت تريد التأكد من وجود كلمة معينة في النص.

    أمثلة:

    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 (استثناء المؤشر الفارغ) موجود.

  2. يحتوي على أي طريقة تتحقق من وجود أي من الأحرف الموجودة في السلسلة. شيء مفيد أيضًا: غالبًا ما يتعين عليك القيام بذلك.

    أمثلة من الوثائق:

    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. يحتوي على IgnoreCase وهو امتداد مفيد للطريقة التي تحتوي على . في الواقع، للتحقق من مثل هذه الحالة دون هذه الطريقة، سيتعين عليك المرور عبر عدة خيارات. ولذا سيتم استخدام طريقة واحدة فقط بشكل متناغم.

  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. يحتوي على لا شيء - انطلاقا من الاسم، يمكنك بالفعل فهم ما يتم التحقق منه. لا ينبغي أن يكون هناك خطوط في الداخل. شيء مفيد بالتأكيد. بحث سريع عن بعض الشخصيات غير المرغوب فيها ;). في روبوت التليجرام الخاص بنا ، سنقوم بتصفية البذاءات ولن نتجاهل هذه الأساليب المضحكة.

    وأمثلة أين كنا بدونها:

    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

  • طريقة السلسلة الافتراضية

سلسلة من الطرق التي تساعد على تجنب إضافة معلومات إضافية إذا كانت السلسلة فارغة وتحتاج إلى تعيين بعض القيمة الافتراضية. هناك العديد من الخيارات التي تناسب كل الأذواق. أهمها StringUtils.defaultString(final String str, Final String defaultStr) - في حالة كانت str فارغة، فسنقوم ببساطة بتمرير القيمة إلى 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) هو مجرد مكافأة، لأن هذا سخيف) هناك أيضًا طريقة يتجاهل الحالة.

  • طريقة يساوي

مثال رائع على الطريقة الآمنة الفارغة التي تقارن سلسلتين. مهما وضعنا هناك، فإن الجواب سيكون هناك، وسيكون دون أخطاء. أمثلة:
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
بالطبع، هناك أيضًا EquisIgnoreCase - كل شيء يتم بنفس الطريقة تمامًا، فقط نتجاهل الحالة. دعنا نرى؟
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

  • يساوي أي طريقة

دعونا نمضي قدمًا ونوسع طريقة التساوي . لنفترض أنه بدلاً من إجراء العديد من عمليات التحقق من المساواة، نريد إجراء واحدة. لهذا، يمكننا تمرير سلسلة سيتم مقارنة مجموعة من السلاسل بها؛ إذا كان أي منها يساوي السلسلة المقترحة، فسيكون صحيحًا. نقوم بتمرير سلسلة ومجموعة من السلاسل لمقارنتها مع بعضها البعض (السلسلة الأولى مع السلاسل من المجموعة). صعب؟ فيما يلي أمثلة من المستندات لمساعدتك على فهم المقصود:
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 وما هي الأساليب المفيدة التي لديها. حسنًا، مع إدراك أن هناك مثل هذه الأشياء المفيدة وليست هناك حاجة للسياج بالعكازات في كل مرة في الأماكن التي يمكن فيها إغلاق المشكلة بمساعدة حل جاهز. بشكل عام، قمنا بتحليل جزء فقط من الأساليب. إذا كنت ترغب في ذلك، يمكنني الاستمرار: هناك الكثير منهم، وهم يستحقون الاهتمام حقا. إذا كانت لديك أي أفكار حول كيفية تقديم ذلك، يرجى الكتابة - أنا منفتح دائمًا على الأفكار الجديدة. تمت كتابة الوثائق الخاصة بالطرق بشكل جيد للغاية، وتمت إضافة أمثلة الاختبار مع النتائج، مما يساعد على فهم تشغيل الطريقة بشكل أفضل. لذلك، لا نخجل من قراءة الوثائق: فهي ستبدد شكوكك حول وظيفة الأداة المساعدة. لاكتساب خبرة جديدة في البرمجة، أنصحك بإلقاء نظرة على كيفية إنشاء وكتابة فئات المرافق. سيكون هذا مفيدًا في المستقبل، نظرًا لأن كل مشروع عادةً ما يكون له فئات خردة خاصة به، وستكون تجربة كتابتها مفيدة. تقليديا، أقترح عليك الاشتراك في حسابي على جيثب ) لمن لا يعرف عن مشروعي مع بوت تيليجرام، هنا رابط المقالة الأولى . شكرا للجميع على القراءة. لقد أضفت بعض الروابط المفيدة أدناه.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION