Java Pattern.asPredicate: เปลี่ยน regex เป็นตัวกรองที่ประกอบกับ Stream ได้
เมื่อ 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 ที่ไม่เชื่อถือ