Java Pattern.asMatchPredicate: ใช้ regex แบบตรงทั้งข้อความใน Stream
เวลา filter Stream ด้วย regex คำว่า “match” มีสองความหมายครับ: ทั้งข้อความต้องตรง หรือแค่ค้นเจอบางช่วง Pattern.asMatchPredicate() ใช้ Matcher.matches() จึงตรงทั้ง input ส่วน asPredicate() ใช้ find() และยอมให้มีข้อความก่อนหรือหลัง
WHY: ชื่อคล้าย แต่ขอบเขต validation ต่างกัน
ถ้ารหัสงานต้องเป็น JOB- ตามด้วยตัวเลข การค้นเจอบางช่วงจะยอมรับ note JOB-42 done ทั้งที่ input ไม่ใช่รหัสล้วน เหมือน รปภ. ตรวจว่ามีบัตรอยู่ในกระเป๋า แทนที่จะตรวจว่าบัตรที่ยื่นมาทั้งใบถูกต้อง เลือก predicate ผิดตัวทำให้ validation หลวมโดยไม่มี compile error
HOW: กรองเฉพาะค่าที่ตรงทั้งข้อความ
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
Pattern jobId = Pattern.compile("JOB-[0-9]+");
Predicate<String> fullMatch = jobId.asMatchPredicate();
List<String> values = List.of(
"JOB-7",
"note JOB-42 done",
"JOB-x",
"JOB-108");
List<String> accepted = values.stream().filter(fullMatch).toList();
assert accepted.equals(List.of("JOB-7", "JOB-108")) : accepted;
assert jobId.asPredicate().test("note JOB-42 done");
assert !fullMatch.test("note JOB-42 done");
}
}
การ compile Pattern ครั้งเดียวแล้ว reuse ลดพิธีและเลี่ยง compile regex ซ้ำในทุก element แต่ regex ยังอาจกิน CPU มากเมื่อ pattern มี backtracking รุนแรง โดยเฉพาะ input ที่ผู้โจมตีควบคุม จำกัดความยาว input และออกแบบ pattern ให้ง่ายก่อนนำไปใช้ที่ขอบ API
Decision table
| เจตนา | API |
|---|---|
| validation ทั้งค่า | asMatchPredicate() |
| ค้นหาบางช่วงในข้อความ | asPredicate() |
| ต้องใช้กลุ่ม capture | matcher() แล้วอ่าน group |
| input ปริมาณมาก/ไม่เชื่อถือ | จำกัดขนาดและ benchmark pattern |
อย่าเติม ^...$ แล้วสลับ API ไปมาโดยไม่จำเป็น เพราะ anchor กับ line mode มีรายละเอียดของตัวเอง การเลือกเมธอดที่บอก semantics ตรงทำให้ review ง่ายกว่า
Recap
- ใช้
asMatchPredicateสำหรับ full-string validation - ใช้
asPredicateเมื่อเจตนาคือค้นเจอบางส่วน - compile Pattern ครั้งเดียวแล้ว reuse
- จำกัด input และตรวจ regex ที่เสี่ยง backtracking