นากต้นฉบับคัดบัตรผ่านตะแกรงลายเส้นลงสองตะกร้าบนฉากสว่าง

Java Pattern.asPredicate: เปลี่ยน regex เป็นตัวกรองที่ประกอบกับ Stream ได้

พี่โชว์Engineering

เมื่อ Stream ต้องกรองข้อความด้วย regex เราไม่ควรเรียก String.matches() ทุกสมาชิก เพราะมันคอมไพล์ pattern ซ้ำและ matches() ต้องตรงทั้งข้อความ Pattern.asPredicate() เปลี่ยน pattern ที่คอมไพล์ครั้งเดียวเป็น Predicate<String> ซึ่งใช้พฤติกรรมแบบ find()

import java.util.List;
import java.util.regex.Pattern;

public class Main {
  static List<String> errorLines(List<String> lines) {
    var containsErrorCode = Pattern.compile("ERR-[0-9]{3}").asPredicate();
    return lines.stream().filter(containsErrorCode).toList();
  }

  public static void main(String[] args) {
    var result = errorLines(List.of(
        "ok", "request failed ERR-503 retrying", "ERR-401", "ERR-x"));
    assert result.equals(List.of("request failed ERR-503 retrying", "ERR-401"));
    System.out.println(result);
  }
}

คำว่า find สำคัญ: pattern เจอได้ตรงส่วนใดส่วนหนึ่งของข้อความ หากต้องตรงทั้งก้อน ใช้ asMatchPredicate() หรือใส่ anchor โดยตั้งใจ เหมือนตะแกรงค้นตราประทับบนกล่อง ไม่ได้บังคับให้ทั้งกล่องมีแต่ตราประทับนั้น

Regex จากผู้ใช้มีความเสี่ยงทั้ง syntax error และงานหนักจาก catastrophic backtracking ควรจำกัดรูปแบบ/ความยาวและจัดการ PatternSyntaxException; อย่าคิดว่าการคอมไพล์ครั้งเดียวแก้ปัญหาเวลารันทั้งหมด

Recap

  • คอมไพล์ Pattern ครั้งเดียวเมื่อใช้ซ้ำ
  • เลือก asPredicate สำหรับ find และ asMatchPredicate สำหรับทั้งข้อความ
  • เขียน test ที่มี match กลางบรรทัดเพื่อยืนยัน semantics
  • ไม่รับ regex ไม่จำกัดจาก boundary ที่ไม่เชื่อถือ