Filter ง่ายๆ สไตล์ Kotlin
เร็วกว่ารอกาแฟดริป
การ filter ใน Java 6 ไม่ใช่เรื่องยากครับ เรามี list อยู่อันนึง อยากจะกรองด้วยเงื่อนไขบางอย่าง ก็แค่สร้าง list ใหม่ วน for-loop เช็ค if ถ้าเข้าเงื่อนไขก็เติมเข้า list ใหม่
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// numbers = [1,2,3,4,5,6]List<Integer> even = new ArrayList<>();
for (Integer i : numbers) {
if (i % 2 == 0) {
even.add(i);
}
}
// even = [2,4,6]
โค้ดแค่ไม่กี่บรรทัดเอง ไม่ใช่เรื่องใหญ่อะไรนักหนา… จนกระทั่งคุณต้อง filter ทุกอย่างบนโลกนี้ ไม่ว่าคุณจะ filter อะไร ที่ไหน กี่ครั้ง คุณต้องสร้าง list อันใหม่ วน for-loop เช็คเงื่อนไงตลอด ทั้งๆที่สิ่งเดียวที่เปลี่ยนคือเงื่อนไขเท่านั้น!
ชีวิตผมเปลี่ยนเมื่อผมเขียน Kotlin ครับ การ filter ใน Kotlin ง่ายแสนง่าย ไม่ต้องมาวนฟงวนฟอเอง ใส่เงื่อนไขเดียวจบ!
การ filter ใน Koltin
val numbers = listOf(1,2,3,4,5,6)
// numbers = [1,2,3,4,5,6]val even = numbers.filter { it % 2 == 0 }
// even = [2,4,6]val odd = numbers.filter { it % 2 != 0 }
// odd = [1,3,5]val strings = listOf("one","two","three","four")
// strings = ["one","two","three","four"]val longerThanThree = strings.filter { it.length > 3 }
// longerThanThree = ["three","four"]
การ filter ใน Kotlin ง่ายมากครับ เพียงแค่คุณมี List คุณเรียก method filter คุณใส่เงื่อนไข เสร็จ! คุณได้ list ที่ filter แล้ว หนึ่งบรรทัดเท่านั้น แถมอ่านเข้าใจด้วย ไม่ว่าคุณจะ filter กี่ที่ ใส่แค่เงื่อนไขเท่านั้นครับ
filter ทำงานยังไง
เรามาดู source code ของ filter จาก kotlin-stdlib กันดีกว่า
ตรงนี้ต้องอาศัยความเข้าใจในเรื่อง Extension Function และ Functional Programming นิดนึงครับ (มีลิ้งค์อยู่ด้านล่าง)
filter
เป็น higher order extension function ที่ประกาศบน Iterable
(ซึ่ง List extends Collection แล้ว Collection extends Iterable มาอีกที)
filter
รับ parameter 1 ตัวชื่อ predicate ประเภทฟังก์ชั่นรับ T
คืน Boolean
filter
เรียก filterTo
พร้อมส่ง ArrayList
ใหม่กับ predicate เดิมไปให้
filterTo
เป็น extension function บน Iterable
เหมือนกัน
filterTo
วน for
ใน this
ซึ่ง this
ในที่นี้คือ Iterable
ตัวที่เราเรียก filter
นั่นแหละ
ในแต่ละรอบของลูปทำการเช็คเงื่อนไขตาม predicate ที่เราส่งเข้ามา ถ้าตรงเงื่อนไขก็เติม element นั้นเข้าไปใน destination (ที่เมื่อกี้ filter เพิ่งส่ง ArrayList เข้ามา) จากนั้น return destination ออกไป
สรุปสั้นๆคือ filter
ทำงานซ้ำซากให้เราหมดเลย ทั้ง new ArrayList ทั้งวน for ทั้ง เช็คเงื่อนไข ทั้งเติม element เข้าไปใน list ใหม่ เราเพียงส่งเงื่อนไขให้มันในรูปแบบตัวแปรฟังก์ชั่นเท่านั้นเอง
ทำ filter เองด้วย Java 6 ได้มั้ย
ได้ครับ แต่ Java 6 ไม่ support extension function ดังนั้นเราทำได้ 2 วิธี คือ
1 subclass ArrayList มาเติม filter
2 สร้างเป็น static utility method
เนื่องจาก Java 6 ไม่ support higer order function เช่นกัน เราจึงต้องจำลอง predicate: (T) -> Boolean
กันก่อน
Predicate
Predicate
เป็นแค่ interface ไว้ให้เราห่อเงื่อนไขการ filter ของเราเฉยๆ ซึ่งมันก็คือๆกันกับตัวแปรประเภทฟังก์ชั่นน่ะแหละ ห่อโค้ดเอาไว้ใช้
Subclass ArrayList
ทำออกมาคร่าวๆก็คงประมาณนี้ครับ ต่อไปนี้เราอยากใช้ List ที่ filter ได้ก็ต้องใช้ FilterableArrayList
แทน
List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6);
// ints = [1,2,3,4,5,6]FilterableArrayList<Integer> numbers =
new FilterableArrayList(ints);
// numbers = [1,2,3,4,5,6]List<Integer> even = numbers.filter(
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 == 0;
}
}
}
// even = [2,4,6]List<Integer> odd = numbers.filter(
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 != 0;
}
}
}
// odd = [1,3,5]
คราวนี้จะ filter อะไร สิ่งเดียวที่เปลี่ยนคือเงื่อนไขเท่านั้น อาจจะอุบาทว์หน่อยตรงที่เราต้องสร้าง anonymous class ของ Predicate (ซึ่งแก้ได้ด้วยการใช้ retrolambda)
ถึงแม้จะสะดวก สามารถเรียก method filter บนตัวแปรได้เลย แต่ก็ไม่ยืดหยุ่น เพราะใช้ได้เฉพาะ FilterableArrayList
เท่านั้น
Static Utility Method
สำหรับวิธีนี้ใช้ได้กับทุก List
เลย แต่ก็จะลำบากหน่อยเพราะต้องเรียก static method จาก util class ไม่ได้เรียกจากตัวแปรตรงๆ
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// numbers = [1,2,3,4,5,6]List<Integer> even = ListUtil.filter(numbers,
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 == 0;
}
}
}
// even = [2,4,6]List<Integer> odd = ListUtil.filter(numbers,
new Predicate<Integer>() {
@Override
Boolean check(Integer element) {
return element % 2 != 0;
}
}
}
// odd = [1,3,5]
สรุป
filter
ทำงานหนัก ซ้ำๆ ที่น่าเบื่อให้เรา ทั้งวน for ทั้ง if ทั้ง add โดยเราแค่ใส่เงื่อนไขเท่านั้น ซึ่งความง่ายนี้เกิดขึ้นได้เพราะ extension function และ higher order function ที่ Kotlin มี
หากพยายาม implement เองด้วย Java 6 ก็พอได้ แต่จะติดปัญหาที่ syntax anonymous class น่าเกลียด และต้องเรียกใช้จาก utility class
เห็นอย่างนี้แล้วมาร่วม #ทีมKotlin กันเถอะครับ