מדריך ל- Java Regular Expressions API

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

במאמר זה נדון ב- Java Regex API וכיצד ניתן להשתמש בביטויים רגולריים בשפת התכנות של Java.

בעולם הביטויים הרגולריים, ניתן לבחור בין טעמים רבים ושונים, כגון grep, Perl, Python, PHP, awk ועוד.

משמעות הדבר היא כי ביטוי רגולרי שעובד בשפת תכנות אחת עשוי שלא לעבוד בשפה אחרת. תחביר הביטוי הרגולרי ב- Java דומה ביותר לזה שנמצא בפרל.

2. התקנה

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

יתר על כן, ה מיתר בכיתה יש גם תמיכה מובנית ב- regex בה אנו משתמשים בדרך כלל בקוד שלנו.

3. חבילת Java Regex

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

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

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

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

4. דוגמה פשוטה

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

הצורה הבסיסית ביותר של התאמת תבניות הנתמכת על ידי java.util.regex ה- API הוא ה- התאמה של א חוּט מילולית. לדוגמא, אם הביטוי הרגולרי הוא foo והקלט חוּט הוא foo, ההתאמה תצליח כי ה- מיתרים זהים:

@Test הציבור בטל givenText_whenSimpleRegexMatches_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("foo"); התאמת התאמה = דפוס. התאמה ("foo"); assertTrue (matcher.find ()); }

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

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

לאחר מכן אנו קוראים לשיטה למצוא באובייקט Matcher.

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

@Test הציבור בטל givenText_whenSimpleRegexMatchesTwice_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("foo"); Matcher matcher = pattern.matcher ("foofoo"); התאמות int = 0; בעוד (matcher.find ()) {matches ++; } assertEquals (התאמות, 2); }

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

run intest סטטי ציבורי ציבורי (מחרוזת regex, טקסט מחרוזת) {תבנית תבנית = תבנית. קומפילציה (regex); התאמת התאמה = דפוס. התאמה (טקסט); התאמות int = 0; בעוד (matcher.find ()) {matches ++; } התאמות חוזרות; }

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

5. דמויות מטא

תווים מטאיים משפיעים על אופן ההתאמה של תבנית, באופן שמוסיף לוגיקה לתבנית החיפוש. ממשק ה- API של Java תומך בכמה מטא-תווים, כשהפשוטה ביותר היא הנקודה “.” שמתאים לכל דמות:

@Test הציבור בטל givenText_whenMatchesWithDotMetach_thenCorrect () {int matches = runTest (".", "Foo"); assertTrue (התאמות> 0); }

בהתחשב בדוגמה הקודמת שבה regex foo התאים לטקסט foo בנוסף ל foofoo שתי פעמים. אם היינו משתמשים במטא-מטבע הנקודה ב- regex, לא היינו מקבלים שני התאמות במקרה השני:

@Test הציבור בטל givenRepeatedText_whenMatchesOnceWithDotMetach_thenCorrect () {int matches = runTest ("foo.", "Foofoo"); assertEquals (התאמות, 1); }

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

ה- API תומך בכמה תווי מטא אחרים שנבדוק בהמשך במאמר זה.

6. שיעורי דמויות

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

6.1. אוֹ מעמד

נבנה כ [א ב ג]. כל אחד מהאלמנטים בערכה מותאמים:

@ מבחן בטל פומבי נתוןORSet_whenMatchesAny_thenCorrect () {int matches = runTest ("[abc]", "b"); assertEquals (התאמות, 1); }

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

@ מבחן הריק ציבורי נתוןORSet_whenMatchesAnyAndAll_thenCorrect () {int matches = runTest ("[abc]", "cab"); assertEquals (התאמות, 3); }

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

@ מבחן בטל פומבי נתוןORSet_whenMatchesAllCombinations_thenCorrect () {int matches = runTest ("[bcr] at", "עכברוש חתול עטלף"); assertEquals (התאמות, 3); }

6.2. ולא מעמד

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

@ מבחן הריק ציבורי שניתן NORSet_whenMatchesNon_thenCorrect () {int matches = runTest ("[^ abc]", "g"); assertTrue (התאמות> 0); }

מקרה נוסף:

@ מבחן הריק ציבורי שניתן NORSet_whenMatchesAllExceptElements_thenCorrect () {int matches = runTest ("[^ bcr] at", "sat mat eat"); assertTrue (התאמות> 0); }

6.3. מחלקת טווח

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

התאמת אותיות רישיות:

@Test ציבורי בטל givenUpperCaseRange_whenMatchesUpperCase_ thenCorrect () {int matches = runTest ("[A-Z]", "שתי אלפביתות גדולות 34 בסך הכל"); assertEquals (התאמות, 2); }

התאמת אותיות קטנות:

@Test ציבורי בטל givenLowerCaseRange_whenMatchesLowerCase_ thenCorrect () {int matches = runTest ("[a-z]", "שתי אלפביתות גדולות 34 בסך הכל"); assertEquals (התאמות, 26); }

התאמה לאותיות קטנות ואותיות קטנות:

@ מבחן ציבורי בטל givenBothLowerAndUpperCaseRange_ whenMatchesAllLetters_thenCorrect () {int matches = runTest ("[a-zA-Z]", "שתי אלפביתות גדולות 34 בסך הכל"); assertEquals (התאמות, 28); }

התאמה לטווח נתון של מספרים:

@ מבחן הריק ציבורי givenNumberRange_whenMatchesAccurately_ thenCorrect () {int matches = runTest ("[1-5]", "שתי אלפביתות גדולות 34 בסך הכל"); assertEquals (התאמות, 2); }

התאמה לטווח אחר של מספרים:

@Test הציבור בטל givenNumberRange_whenMatchesAccurately_ thenCorrect2 () {int matches = runTest ("[30-35]", "שתי אלפביתות גדולות 34 בסך הכל"); assertEquals (התאמות, 1); }

6.4. כיתת האיחוד

כיתת אופי של האיחוד היא תוצאה של שילוב של שתי כיתות אופי או יותר:

@Test הציבור בטל givenTwoSets_whenMatchesUnion_thenCorrect () {int matches = runTest ("[1-3 [7-9]]", "123456789"); assertEquals (התאמות, 6); }

המבחן הנ"ל יתאים רק ל- 6 מתוך 9 המספרים השלמים מכיוון שמערכת האיחוד מדלגת על 4, 5 ו -6.

6.5. מחלקת צומת

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

@Test ציבורי בטל givenTwoSets_whenMatchesIntersection_thenCorrect () {int matches = runTest ("[1-6 && [3-9]]", "123456789"); assertEquals (התאמות, 4); }

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

6.6. מחלקת חיסור

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

@Test ציבורי בטל givenSetWithSubtraction_whenMatchesAccurately_thenCorrect () {int matches = runTest ("[0-9 && [^ 2468]]", "123456789"); assertEquals (התאמות, 5); }

רק 1,3,5,7,9 יותאם.

7. שיעורי תווים מוגדרים מראש

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

כפי שנראה, רוב התווים יתחילו עם קו נטוי אחורי, שיש לו משמעות מיוחדת בג'אווה. עבור אלה שיורכבו על ידי תבנית בכיתה - יש להימלט מהמתלה האחורית המובילה כלומר \ d הופך להיות \ ד.

ספרות תואמות, שוות ערך ל- [0-9]:

@Test הציבור בטל givenDigits_whenMatches_thenCorrect () {int מתאים = runTest ("\ d", "123"); assertEquals (התאמות, 3); }

התאמה שאינה ספרות, המקבילה ל- [^0-9]:

@Test ציבורי בטל givenNonDigits_whenMatches_thenCorrect () {int mathces = runTest ("\ D", "a6c"); assertEquals (התאמות, 2); }

התאמת שטח לבן:

@Test ציבורי בטל givenWhiteSpace_whenMatches_thenCorrect () {int תואם = runTest ("\ s", "a c"); assertEquals (התאמות, 1); }

שטח לא לבן תואם:

@Test ציבורי בטל givenNonWhiteSpace_whenMatches_thenCorrect () {int matches = runTest ("\ S", "a c"); assertEquals (התאמות, 2); }

התאמת תו מילה, שווה ערך ל- [a-zA-Z_0-9]:

@ מבט ציבורי בטל givenWordCharacter_whenMatches_thenCorrect () {int מתאים = runTest ("\ w", "היי!"); assertEquals (התאמות, 2); }

התאמה לדמות שאינה מילה:

@Test ציבורי בטל givenNonWordCharacter_whenMatches_thenCorrect () {int matches = runTest ("\ W", "hi!"); assertEquals (התאמות, 1); }

8. מכימות

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

כדי להתאים טקסט אפס או פעם אחת, אנו משתמשים ב- ? מכמת:

@Test ציבורי בטל givenZeroOrOneQuantifier_whenMatches_thenCorrect () {int matches = runTest ("\ a?", "Hi"); assertEquals (גפרורים, 3); }

לחלופין, אנו יכולים להשתמש בתחביר הסד, הנתמך גם על ידי ממשק ה- API של Java regex:

@Test ציבורי בטל givenZeroOrOneQuantifier_whenMatches_thenCorrect2 () {int matches = runTest ("\ a {0,1}", "hi"); assertEquals (התאמות, 3); }

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

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

כדי להתאים טקסט לאפס או לזמנים בלתי מוגבלים, אנחנו * אנחנו מכמת, זה בדיוק דומה?:

@Test ציבורי בטל givenZeroOrManyQuantifier_whenMatches_thenCorrect () {int matches = runTest ("\ a *", "hi"); assertEquals (גפרורים, 3); }

חלופה נתמכת:

@Test ציבורי בטל שניתן ZeroOrManyQuantifier_whenMatches_thenCorrect2 () {int matches = runTest ("\ a {0,}", "hi"); assertEquals (התאמות, 3); }

הכימות עם ההפרש הוא +, יש לו סף התאמה של 1. אם נדרש חוּט לא מתרחש בכלל, לא תהיה התאמה, אפילו לא אפס חוּט:

@Test ציבורי בטל givenOneOrManyQuantifier_whenMatches_thenCorrect () {int matches = runTest ("\ a +", "hi"); assertFalse (התאמות); }

חלופה נתמכת:

@Test ציבורי בטל givenOneOrManyQuantifier_whenMatches_thenCorrect2 () {int matches = runTest ("\ a {1,}", "hi"); assertFalse (התאמות); }

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

@Test ציבור בטל givenBraceQuantifier_whenMatches_thenCorrect () {int matches = runTest ("a {3}", "aaaaaa"); assertEquals (התאמות, 2); }

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

@Test ציבורי בטל givenBraceQuantifier_whenFailsToMatch_thenCorrect () {int matches = runTest ("a {3}", "aa"); assertFalse (התאמות> 0); }

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

@Test ציבור בטל givenBraceQuantifierWithRange_whenMatches_thenCorrect () {int matches = runTest ("a {2,3}", "aaaa"); assertEquals (התאמות, 1); }

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

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

@Test הציבור בטל givenBraceQuantifierWithRange_whenMatchesLazily_thenCorrect () {int matches = runTest ("a {2,3}?", "Aaaa"); assertEquals (התאמות, 2); }

9. לכידת קבוצות

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

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

בחלק זה נראה כמה דוגמאות לשימוש בקבוצות לכידה ב- API של Java regex.

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

@ מבחן חלל ציבורי givenCapturingGroup_whenMatches_thenCorrect () {int maches = runTest ("(\ d \ d)", "12"); assertEquals (התאמות, 1); }

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

@ מבחן חלל ציבורי givenCapturingGroup_whenMatches_thenCorrect2 () {int matches = runTest ("(\ d \ d)", "1212"); assertEquals (התאמות, 2); }

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

@ מבחן חלל ציבורי givenCapturingGroup_whenMatchesWithBackReference_ thenCorrect () {int matches = runTest ("(\ d \ d) \ 1", "1212"); assertEquals (התאמות, 1); }

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

@ מבחן חלל ציבורי givenCapturingGroup_whenMatches_thenCorrect3 () {int matches = runTest ("(\ d \ d) (\ d \ d)", "1212"); assertEquals (התאמות, 1); }

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

@ מבחן חלל ציבורי givenCapturingGroup_whenMatchesWithBackReference_ thenCorrect2 () {int matches = runTest ("(\ d \ d) \ 1 \ 1 \ 1", "12121212"); assertEquals (התאמות, 1); }

אך אם תשנה אפילו את הספרה האחרונה, ההתאמה תיכשל:

@Test ציבורי בטל givenCapturingGroupAndWrongInput_ whenMatchFailsWithBackReference_thenCorrect () {int matches = runTest ("(\ d \ d) \ 1", "1213"); assertFalse (התאמות> 0); }

חשוב לא לשכוח את ההטיחות האחוריות של הבריחה, זה חיוני בתחביר ג'אווה.

10. משחקי גבולות

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

כדי להתאים רק כאשר ה- regex הנדרש נכון בתחילת הטקסט, אנו משתמשים בקריאה ^.

בדיקה זו תיכשל מאז הטקסט כֶּלֶב ניתן למצוא בהתחלה:

@Test ציבורי בטל givenText_whenMatchesAtBeginning_thenCorrect () {int matches = runTest ("^ dog", "dogs are friendly"); assertTrue (התאמות> 0); }

המבחן הבא ייכשל:

@Test הציבור בטל givenTextAndWrongInput_whenMatchFailsAtBeginning_ thenCorrect () {int matches = runTest ("^ dog", "האם כלבים הם ידידותיים?"); assertFalse (התאמות> 0); }

כדי להתאים רק כאשר ה- regex הנדרש נכון בסוף הטקסט, אנו משתמשים בתו הדולר $. התאמה תימצא במקרה הבא:

@Test ציבורי בטל givenText_whenMatchesAtEnd_thenCorrect () {int matches = runTest ("כלב $", "החבר הכי טוב של האדם הוא כלב"); assertTrue (התאמות> 0); }

ושום התאמה לא תמצא כאן:

@Test הציבור בטל givenTextAndWrongInput_whMatchFailsAtEnd_thenCorrect () {int matches = runTest ("כלב $", "הוא החבר הכי טוב של כלב?"); assertFalse (התאמות> 0); }

אם אנו רוצים התאמה רק כאשר הטקסט הנדרש נמצא בגבול מילים, אנו משתמשים \ ב regex בתחילת ובסוף regex:

שטח הוא גבול מילים:

@Test ציבור בטל givenText_whenMatchesAtWordBoundary_thenCorrect () {int matches = runTest ("\ bdog \ b", "a dog is friendly"); assertTrue (התאמות> 0); }

המחרוזת הריקה בתחילת שורה היא גם גבול מילים:

@Test public void givenText_whenMatchesAtWordBoundary_thenCorrect2 () {int matches = runTest ("\ bdog \ b", "הכלב הוא החבר הכי טוב של האדם"); assertTrue (התאמות> 0); }

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

@Test הציבור בטל givenWrongText_whenMatchFailsAtWordBoundary_thenCorrect () {int matches = runTest ("\ bdog \ b", "snoop dogg is a rapper"); assertFalse (התאמות> 0); }

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

@Test public void givenText_whenMatchesAtWordAndNonBoundary_thenCorrect () {int matches = runTest ("\ bdog \ B", "snoop dogg is a rapper"); assertTrue (התאמות> 0); }

11. שיטות כיתת תבניות

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

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

ציבורי סטטי ציבורי int runTest (מחרוזת regex, מחרוזת טקסט, דגלים int) {תבנית = תבנית. compile (regex, דגלים); התאמה = דפוס. התאמה (טקסט); התאמות int = 0; בעוד (matcher.find ()) {matches ++; } התאמות חוזרות; }

בחלק זה נבחן את הדגלים הנתמכים השונים ואופן השימוש בהם.

תבנית. CANON_EQ

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

שקול את דמות יוניקוד המודגשת é. נקודת הקוד המורכבת שלה היא u00E9. עם זאת, ל- Unicode יש גם נקודת קוד נפרדת עבור תווי הרכיב שלה ה, u0065 והמבטא החריף, u0301. במקרה זה, דמות מורכבת u00E9 לא ניתן להבדיל משני רצפי הדמויות u0065 u0301.

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

@Test הציבור בטל שניתןRegexWithoutCanonEq_whenMatchFailsOnEquivalentUnicode_thenCorrect () {int matches = runTest ("\ u00E9", "\ u0065 \ u0301"); assertFalse (התאמות> 0); }

אבל אם נוסיף את הדגל, אז המבחן יעבור:

@Test הציבור בטל שניתןRegexWithCanonEq_whenMatchesOnEquivalentUnicode_thenCorrect () {int matches = runTest ("\ u00E9", "\ u0065 \ u0301", Pattern.CANON_EQ); assertTrue (התאמות> 0); }

תבנית. CASE_INSENSITIVE

דגל זה מאפשר התאמה ללא קשר למקרה. כברירת מחדל ההתאמה לוקחת בחשבון מקרה:

@Test הציבור בטל שניתןRegexWithDefaultMatcher_whenMatchFailsOnDifferentCases_thenCorrect () {int matches = runTest ("כלב", "זה כלב"); assertFalse (התאמות> 0); }

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

@Test public void givenRegexWithCaseInsensitiveMatcher _whenMatchesOnDifferentCases_thenCorrect () {int matches = runTest ("dog", "This is a Dog", Pattern.CASE_INSENSITIVE); assertTrue (התאמות> 0); }

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

@Test ציבור בטל שניתןRegexWithEmbeddedCaseInsensitiveMatcher _whenMatchesOnDifferentCases_thenCorrect () {int matches = runTest ("(? I) dog", "This is a Dog"); assertTrue (התאמות> 0); }

דפוס. הערות

ממשק ה- API של Java מאפשר לכלול הערות באמצעות # ב- regex. זה יכול לעזור בתיעוד רגקס מורכב שאולי לא יהיה ברור מאליו למתכנת אחר.

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

@Test הציבור בטל שניתןRegexWithComments_whenMatchFailsWithoutFlag_thenCorrect () {int matches = runTest ("כלב $ # בדוק עבור מילת כלב בסוף הטקסט", "זה כלב"); assertFalse (התאמות> 0); }

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

@Test הציבור בטל givenRegexWithComments_whenMatchesWithFlag_thenCorrect () {int matches = runTest ("כלב $ # לבדוק סוף הטקסט", "זה כלב", תבנית. COMMENTS); assertTrue (התאמות> 0); }

יש לכך גם ביטוי דגל מוטבע חלופי:

@Test ציבורי בטל שניתןRegexWithComments_whenMatchesWithEmbeddedFlag_thenCorrect () {int matches = runTest ("(? X) dog $ #check end of text", "זהו כלב"); assertTrue (התאמות> 0); }

תבנית. DOTALL

כברירת מחדל, כאשר אנו משתמשים בנקודה "." ביטוי ב- regex, אנו מתאימים לכל תו בקלט חוּט עד שנתקל בדמות שורה חדשה.

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

ראשית, נראה את התנהגות ברירת המחדל:

@Test הציבור בטל שניתןRegexWithLineTerminator_whenMatchFails_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("(. *)"); Matcher matcher = pattern.matcher ("זה טקסט" + System.getProperty ("line.separator") + "המשיך בשורה אחרת"); matcher.find (); assertEquals ("זה טקסט", matcher.group (1)); }

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

עכשיו פנימה dotall במצב, כל הטקסט כולל מסוף השורה יתאים:

@Test ציבורי בטל שניתןRegexWithLineTerminator_whMatchesWithDotall_thenCorrect () {תבנית תבנית = תבנית.קומפייל ("(. *)", תבנית.DOTALL); Matcher matcher = pattern.matcher ("זה טקסט" + System.getProperty ("line.separator") + "המשיך בשורה אחרת"); matcher.find (); assertEquals ("זה טקסט" + System.getProperty ("line.separator") + "המשיך בשורה אחרת", matcher.group (1)); }

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

@Test public void givenRegexWithLineTerminator_whenMatchesWithEmbeddedDotall _thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("(? S) (. *)"); Matcher matcher = pattern.matcher ("זה טקסט" + System.getProperty ("line.separator") + "המשיך בשורה אחרת"); matcher.find (); assertEquals ("זה טקסט" + System.getProperty ("line.separator") + "המשיך בשורה אחרת", matcher.group (1)); }

תבנית. ליטרלי

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

@Test הציבור בטל שניתןRegex_whenMatchesWithoutLiteralFlag_thenCorrect () {int matches = runTest ("(. *)", "Text"); assertTrue (התאמות> 0); }

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

@Test ציבור בטל שניתןRegex_whenMatchFailsWithLiteralFlag_thenCorrect () {int matches = runTest ("(. *)", "Text", Pattern.LITERAL); assertFalse (התאמות> 0); }

כעת אם נוסיף את המחרוזת הנדרשת, הבדיקה תעבור:

@Test הציבור בטל שניתןRegex_whenMatchesWithLiteralFlag_thenCorrect () {int matches = runTest ("(. *)", "Text (. *)", Pattern.LITERAL); assertTrue (התאמות> 0); }

אין תו דגל מוטבע המאפשר ניתוח ניתוח מילולי.

תבנית. רב

כברירת מחדל ^ ו $ מטא-תווים תואמים לחלוטין בהתחלה ובסוף בהתאמה של כל הקלט חוּט. ההתאמה מתעלמת מכל מסיימי קו:

@Test public void givenRegex_whenMatchFailsWithoutMultilineFlag_thenCorrect () {int matches = runTest ("dog $", "This is a dog" + System.getProperty ("line.separator") + "זה שועל"); assertFalse (התאמות> 0); }

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

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

@Test public void givenRegex_whenMatchesWithMultilineFlag_thenCorrect () {int matches = runTest ("dog $", "This is a dog" + System.getProperty ("line.separator") + "זה שועל", תבנית.MULTILINE); assertTrue (התאמות> 0); }

הנה גרסת הדגל המוטמעת:

@Test public void givenRegex_whenMatchesWithEmbeddedMultilineFlag_ thenCorrect () {int matches = runTest ("(? M) dog $", "זהו כלב" + System.getProperty ("line.separator") + "זה שועל"); assertTrue (התאמות> 0); }

12. שיטות כיתת התאמה

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

12.1. שיטות אינדקס

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

@ מבחן חלל ציבורי givenMatch_whenGetsIndices_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("כלב"); Matcher matcher = pattern.matcher ("הכלב הזה הוא שלי"); matcher.find (); assertEquals (5, matcher.start ()); assertEquals (8, matcher.end ()); }

12.2. שיטות לימוד

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

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

שתי השיטות מתחילות בתחילת הקלט חוּט :

@ מבחן ציבורי בטל כאשרStudyMethodsWork_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("כלב"); התאמת התאמה = דפוס. התאמה ("כלבים ידידותיים"); assertTrue (matcher.lookingAt ()); assertFalse (matcher.matches ()); }

שיטת ההתאמות תחזור נכון במקרה כזה:

@Test ציבורי בטל כאשרMatchesStudyMethodWorks_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("כלב"); התאמת התאמה = דפוס. התאמה ("כלב"); assertTrue (matcher.matches ()); }

12.3. שיטות החלפה

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

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

@ מבחן פומבי בטל כאשר ReplaceFirstWorks_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("כלב"); התאמת התאמה = דפוס. התאמה ("כלבים הם חיות בית, כלבים ידידותיים"); מחרוזת newStr = matcher.replaceFirst ("חתול"); assertEquals ("חתולים הם חיות בית, כלבים ידידותיים", newStr); }

החלף את כל ההתרחשויות:

@ מבחן ציבורי בטל כאשר החלפה AllWorks_thenCorrect () {תבנית תבנית = תבנית.קומפילציה ("כלב"); התאמת התאמה = דפוס. התאמה ("כלבים הם חיות בית, כלבים ידידותיים"); מחרוזת newStr = matcher.replaceAll ("חתול"); assertEquals ("חתולים הם חיות בית, חתולים ידידותיים", newStr); }

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

13. מסקנה

במאמר זה למדנו כיצד להשתמש בביטויים רגולריים ב- Java וכן בדקנו את התכונות החשובות ביותר של java.util.regex חֲבִילָה.

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