JavaRush /Blog Java /Random-VI /Hãy chia nhỏ lớp StringUtils
Roman Beekeeper
Mức độ

Hãy chia nhỏ lớp StringUtils

Xuất bản trong nhóm
Xin chào tất cả mọi người, những độc giả thân yêu của tôi. Tôi cố gắng viết về những gì tôi thực sự quan tâm và những gì làm tôi lo lắng vào lúc này. Vì vậy, hôm nay sẽ có một số bài đọc nhẹ sẽ hữu ích cho bạn để tham khảo sau này: hãy nói về StringUtils . Hãy chia nhỏ lớp StringUtils - 1Tình cờ có lần tôi bỏ qua thư viện Apache Commons Lang 3 . Đây là thư viện có các lớp phụ trợ để làm việc với các đối tượng khác nhau. Đây là tập hợp các phương thức hữu ích để làm việc với chuỗi, bộ sưu tập, v.v. Trong một dự án hiện tại, nơi tôi phải làm việc chi tiết hơn với các chuỗi trong việc dịch logic nghiệp vụ 25 năm tuổi (từ COBOL sang Java), hóa ra là tôi không có kiến ​​thức đủ sâu về lớp StringUtils . Vì vậy tôi phải tự mình tạo ra mọi thứ. Ý tôi là gì? Thực tế là bạn không phải tự mình viết một số tác vụ nhất định liên quan đến thao tác chuỗi mà hãy sử dụng giải pháp có sẵn. Có gì sai khi tự viết nó? Ít nhất thì đây là đoạn mã đã được viết từ lâu. Không kém phần cấp bách là vấn đề kiểm tra mã được viết bổ sung. Khi chúng tôi sử dụng một thư viện đã được chứng minh là tốt, chúng tôi hy vọng rằng nó đã được thử nghiệm và chúng tôi không cần phải viết nhiều trường hợp thử nghiệm để kiểm tra nó. Tình cờ là tập hợp các phương thức để làm việc với một chuỗi trong Java không quá lớn. Thực sự không có nhiều phương pháp hữu ích cho công việc. Lớp này cũng được tạo ra để kiểm tra NullPointerException. Đề cương bài viết của chúng tôi sẽ như sau:
  1. Làm thế nào để kết nối?
  2. Ví dụ từ công việc của tôi: làm thế nào mà không biết về một lớp học hữu ích như vậy, tôi đã tạo ra chiếc nạng xe đạp của mình .
  3. Hãy xem xét các phương pháp khác mà tôi thấy thú vị.
  4. Hãy tóm tắt.
Tất cả các trường hợp sẽ được thêm vào một kho lưu trữ riêng trong tổ chức Cộng đồng Javarush trên GitHub. Sẽ có những ví dụ và bài kiểm tra riêng cho chúng.

0. Cách kết nối

Những người đi cùng tôi ít nhiều đều đã quen thuộc với cả Git và Maven, vì vậy tôi sẽ dựa vào kiến ​​​​thức này hơn nữa và không lặp lại nữa. Đối với những người đã bỏ lỡ các bài viết trước của tôi hoặc mới bắt đầu đọc, đây là tài liệu về MavenGit . Tất nhiên, nếu không có hệ thống xây dựng (Maven, Gredl), bạn cũng có thể kết nối mọi thứ theo cách thủ công, nhưng ngày nay điều này thật điên rồ và bạn chắc chắn không cần phải làm như vậy: tốt hơn là bạn nên học ngay cách làm mọi thứ một cách chính xác. Do đó, để làm việc với Maven, trước tiên chúng ta thêm phần phụ thuộc thích hợp:
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
Trong đó ${apache.common.version} là phiên bản của thư viện này. Tiếp theo, để nhập vào một số lớp, hãy thêm nhập:
import org.apache.commons.lang3.StringUtils;
Và thế là xong, tất cả đều có trong túi))

1. Ví dụ từ một dự án thực tế

  • phương pháp leftPad

Ví dụ đầu tiên nhìn chung có vẻ ngu ngốc đến nỗi thật tốt khi các đồng nghiệp của tôi biết về StringUtils.leftPad và nói với tôi. Nhiệm vụ là gì: mã được xây dựng theo cách cần thiết để chuyển đổi dữ liệu nếu nó không đến nơi chính xác. Người ta mong đợi rằng trường chuỗi chỉ nên bao gồm các số, tức là nếu độ dài của nó là 3 và giá trị của nó là 1 thì mục nhập phải là “001”. Nghĩa là, trước tiên bạn cần xóa tất cả các khoảng trắng, sau đó che nó bằng các số 0. Thêm ví dụ để làm rõ bản chất của nhiệm vụ: từ “12“ -> “012” từ “1“ -> “001”, v.v. Tôi đã làm gì? Đã mô tả điều này trong lớp LeftPadExample . Tôi đã viết một phương pháp sẽ thực hiện tất cả điều này:
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();
}
Về cơ bản, tôi lấy ý tưởng rằng chúng ta có thể chỉ cần lấy chênh lệch giữa giá trị ban đầu và giá trị đã cắt bớt và điền vào đó bằng số 0 ở phía trước. Để làm điều này, tôi đã sử dụng IntStream để thực hiện thao tác tương tự n lần. Và điều này chắc chắn cần phải được kiểm tra. Đây là những gì tôi có thể làm nếu tôi biết trước về phương thức StringUtils.leftPad :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
Như bạn có thể thấy, có ít mã hơn nhiều và một thư viện được mọi người xác nhận cũng được sử dụng. Với mục đích này, tôi đã tạo hai bài kiểm tra trong lớp LeftPadExampleTest (thông thường khi họ định kiểm tra một lớp, họ tạo một lớp có cùng tên + Kiểm tra trong cùng một gói, chỉ trong src/test/java). Các thử nghiệm này kiểm tra một phương thức để đảm bảo rằng nó biến đổi giá trị một cách chính xác, sau đó là một phương thức khác. Tất nhiên, sẽ cần phải viết nhiều bài kiểm tra hơn, nhưng kiểm tra không phải là chủ đề chính trong trường hợp của chúng tôi:
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);
   }

}
Bây giờ tôi có thể đưa ra một vài nhận xét về các bài kiểm tra. Chúng được viết bằng JUnit 5:
  1. Một bài kiểm tra sẽ được coi là một bài kiểm tra nếu nó có chú thích thích hợp - @Test.
  2. Nếu khó mô tả hoạt động của thử nghiệm bằng tên hoặc mô tả dài và khó đọc, bạn có thể thêm chú thích @DisplayName và đặt chú thích đó thành mô tả bình thường sẽ hiển thị khi chạy thử nghiệm.
  3. Khi viết bài kiểm tra, tôi sử dụng phương pháp BDD, trong đó tôi chia bài kiểm tra thành các phần logic:
    1. // đã cho - khối thiết lập dữ liệu trước khi kiểm tra;
    2. // khi nào khối mà phần mã mà chúng ta đang kiểm tra được khởi chạy;
    3. // then là khối trong đó kết quả của khối when được kiểm tra.
Nếu bạn chạy chúng, chúng sẽ xác nhận rằng mọi thứ đều hoạt động như mong đợi.

  • phương thức dải Bắt đầu

Ở đây tôi cần giải quyết vấn đề với một dòng có thể có dấu cách và dấu phẩy ở đầu. Sau khi chuyển đổi, lẽ ra chúng không còn có ý nghĩa mới nữa. Tuyên bố vấn đề rõ ràng hơn bao giờ hết. Một vài ví dụ sẽ củng cố sự hiểu biết của chúng ta: “, , books” -> “books” “,,, books” -> “books” b , books” -> “b , books” Như trong trường hợp với leftPad, tôi đã thêm Lớp StrimStartExample , trong đó có hai phương thức. Một - với giải pháp riêng của nó:
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);
}
Ở đây, ý tưởng là tìm chỉ mục bắt đầu từ đó không còn dấu cách hoặc dấu phẩy. Nếu chúng hoàn toàn không có ở đó ngay từ đầu thì chỉ số sẽ bằng 0. Và giải pháp thứ hai - với giải pháp thông qua StringUtils :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
Ở đây, chúng tôi chuyển thông tin đối số đầu tiên về chuỗi mà chúng tôi đang làm việc và trong chuỗi thứ hai, chúng tôi chuyển một chuỗi bao gồm các ký tự cần được bỏ qua. Chúng ta tạo lớp StripStartExampleTest theo cách tương tự :
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);
   }
}

  • phương thức isEmpty

Tất nhiên, phương pháp này đơn giản hơn nhiều, nhưng điều đó không làm nó kém hữu ích hơn chút nào. Nó mở rộng khả năng của phương thức String.isEmpty() , đồng thời bổ sung thêm tính năng kiểm tra giá trị rỗng. Để làm gì? Để tránh NullPointerException, nghĩa là tránh gọi các phương thức gọi trên một biến có giá trị null . Vì vậy, để không viết:
if(value != null && value.isEmpty()) {
   //doing something
}
Bạn chỉ có thể làm điều này:
if(StringUtils.isEmpty(value)) {
   //doing something
}
Ưu điểm của phương pháp này là có thể biết ngay phương pháp nào được sử dụng.

2. Phân tích các phương thức khác của lớp StringUtils

Bây giờ chúng ta hãy nói về những phương pháp mà theo tôi, cũng đáng được quan tâm. Nói chung về StringUtils , điều đáng nói là nó cung cấp các phương thức an toàn null tương tự như các phương thức được tìm thấy trong lớp String (như trường hợp của phương thức isEmpty ). Chúng ta hãy đi qua chúng:

  • phương pháp so sánh

Một phương thức như vậy tồn tại trong Chuỗi và sẽ ném ra một ngoại lệ NullPointerException nếu khi so sánh hai chuỗi, một trong số chúng là null. Để tránh những bước kiểm tra không tốt trong mã của chúng ta, chúng ta có thể sử dụng phương thức StringUtils.compare(String str1, String str2) : nó trả về một int làm kết quả so sánh. Những giá trị này có ý nghĩa gì? int = 0 nếu chúng giống nhau (hoặc cả hai đều rỗng). int < 0, nếu str1 nhỏ hơn str2. int > 0, nếu str1 lớn hơn str2. Ngoài ra, nếu bạn xem tài liệu của họ, Javadoc của phương pháp này trình bày các tình huống sau:
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

  • chứa... phương pháp

Ở đây các nhà phát triển tiện ích đã có một sự bùng nổ. Bất cứ phương pháp nào bạn muốn đều có. Tôi quyết định đặt chúng lại với nhau:
  1. chứa là một phương thức kiểm tra xem chuỗi dự kiến ​​có nằm trong chuỗi khác hay không. Điều này hữu ích như thế nào? Bạn có thể sử dụng phương pháp này nếu bạn cần đảm bảo rằng có một từ nhất định trong văn bản.

    Ví dụ:

    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

    Một lần nữa, bảo mật NPE (Null Pointer Exception) hiện diện.

  2. containsAny là một phương thức kiểm tra xem có bất kỳ ký tự nào có trong chuỗi hay không. Cũng là một điều hữu ích: bạn thường phải làm điều này.

    Ví dụ từ tài liệu:

    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 là một phần mở rộng hữu ích cho phương thức chứa . Thật vậy, để kiểm tra trường hợp như vậy mà không có phương pháp này, bạn sẽ phải thực hiện một số tùy chọn. Và như vậy chỉ có một phương pháp sẽ được sử dụng một cách hài hòa.

  4. Một vài ví dụ từ các tài liệu:

    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. chứaNone - xét theo tên, bạn có thể hiểu những gì đang được kiểm tra. Không nên có dòng bên trong. Một điều hữu ích, chắc chắn. Tìm kiếm nhanh một số ký tự không mong muốn;). Trong bot điện tín của chúng tôi , chúng tôi sẽ lọc những lời tục tĩu và sẽ không bỏ qua những phương pháp hài hước này.

    Và ví dụ, chúng ta sẽ ở đâu nếu không có chúng:

    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

  • phương thức defaultString

Một loạt các phương pháp giúp tránh thêm thông tin bổ sung nếu chuỗi rỗng và bạn cần đặt một số giá trị mặc định. Có nhiều lựa chọn phù hợp với mọi sở thích. Đứng đầu trong số đó là StringUtils.defaultString(final String str, Final String defaultStr) - trong trường hợp str là null, chúng ta sẽ chỉ chuyển giá trị defaultStr . Ví dụ từ tài liệu:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
Nó rất thuận tiện khi sử dụng khi bạn tạo lớp POJO bằng dữ liệu.

  • phương thức xóa khoảng trắng

Đây là một phương pháp thú vị, mặc dù không có nhiều lựa chọn cho ứng dụng của nó. Đồng thời, nếu trường hợp như vậy xảy ra, phương pháp này chắc chắn sẽ rất hữu ích. Nó loại bỏ tất cả các khoảng trắng khỏi chuỗi. Khoảng cách này ở đâu thì sẽ không có dấu vết của nó))) Ví dụ từ các tài liệu:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • kết thúcVới phương thức

Nói cho chính nó. Đây là một phương pháp rất hữu ích: nó kiểm tra xem chuỗi có kết thúc bằng chuỗi được gợi ý hay không. Điều này thường là cần thiết. Tất nhiên, bạn có thể tự viết séc, nhưng sử dụng phương pháp làm sẵn rõ ràng sẽ thuận tiện và tốt hơn. Ví dụ:
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
Như bạn có thể thấy, mọi thứ đều kết thúc bằng một dòng trống))) Tôi nghĩ rằng ví dụ này (StringUtils.endsWith("ABCDEF", "") = true) chỉ là một phần thưởng, vì điều này thật vô lý) Ngoài ra còn có một phương pháp bỏ qua trường hợp .

  • phương pháp bằng

Một ví dụ tuyệt vời về phương pháp an toàn null để so sánh hai chuỗi. Bất cứ điều gì chúng ta đặt vào đó, câu trả lời sẽ ở đó và sẽ không có lỗi. Ví dụ:
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
Tất nhiên, cũng có EqualsIgnoreCase - mọi thứ được thực hiện theo cách giống hệt nhau, chỉ có điều chúng ta bỏ qua trường hợp này. Hãy xem nào?
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

  • phương thức bằng bất kỳ

Hãy tiếp tục và mở rộng phương thức bằng . Giả sử thay vì thực hiện một số kiểm tra đẳng thức, chúng ta muốn thực hiện một kiểm tra. Đối với điều này, chúng ta có thể truyền một chuỗi để so sánh một tập hợp các chuỗi; nếu bất kỳ chuỗi nào trong số chúng bằng chuỗi được đề xuất thì nó sẽ là TRUE. Chúng tôi chuyển một chuỗi và một tập hợp các chuỗi để so sánh chúng với nhau (chuỗi đầu tiên có các chuỗi từ bộ sưu tập). Khó? Dưới đây là ví dụ từ các tài liệu để giúp bạn hiểu ý nghĩa của nó:
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
Ngoài ra còn có EqualsAnyIgnoreCase . Và ví dụ cho nó:
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

Điểm mấu chốt

Kết quả là chúng ta sẽ có kiến ​​thức về StringUtils là gì và nó có những phương thức hữu ích nào. Chà, với việc nhận ra rằng có những thứ hữu ích như vậy và không cần phải chống nạng mỗi lần rào chắn ở những nơi có thể giải quyết vấn đề với sự trợ giúp của một giải pháp làm sẵn. Nói chung, chúng tôi chỉ phân tích một phần của phương pháp. Nếu bạn muốn, tôi có thể tiếp tục: còn rất nhiều người trong số họ nữa và họ thực sự đáng được quan tâm. Nếu bạn có bất kỳ ý tưởng nào về cách trình bày vấn đề này, vui lòng viết thư - Tôi luôn cởi mở với những ý tưởng mới. Tài liệu về các phương pháp được viết rất hay, các ví dụ thử nghiệm kèm theo kết quả được bổ sung, giúp hiểu rõ hơn về hoạt động của phương pháp. Do đó, chúng tôi không ngại đọc tài liệu: nó sẽ xóa tan nghi ngờ của bạn về chức năng của tiện ích. Để có được trải nghiệm viết mã mới, tôi khuyên bạn nên xem cách các lớp tiện ích được tạo và viết. Điều này sẽ hữu ích trong tương lai, vì thông thường mỗi dự án đều có các lớp phế liệu riêng và kinh nghiệm viết chúng sẽ rất hữu ích. Theo truyền thống, tôi khuyên bạn nên đăng ký tài khoản của tôi trên Github ) Đối với những người chưa biết về dự án của tôi với bot telegram, đây là liên kết đến bài viết đầu tiên . Cảm ơn mọi người đã đọc. Tôi đã thêm một số liên kết hữu ích bên dưới.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION