แนวคิดในการเขียนโปรแกรมมีหลายรูปแบบ OOP ก็เป็นรูปแบบหนึ่งของการเขียนโปรแกรมที่มองทุกอย่างเป็นออบเจตก์ การเขียนโปรแกรมแบบฟังก์

ชั่นก็เป็นอีกรูปแบบหนึ่งโดยมองทุกอย่างเป็นฟังก์ชั่น ภาษาจาวาก็เป็นอีกหนึ่งภาษาที่มีการพัฒนาเพื่อรองรับการเขียนโปรแกรมแบบฟังก์ชั่นถึงแม้จะไม่สามารถทำได้ทั้งหมดตามข้อกำหนดของการเขียนโปรแกรมแบบฟังก์ชั่น โดยตั้งแต่ภาษาจาวาเวอร์ชั่น 8  มีการเพิ่มความสามารถของ นิพจน์แบบแลมด้า (Lambda Expression) อินเตอร์เฟสแบบฟังก์ชั่น (Functional Interface) และ การอ้างอิงเมธอด (Method Reference) เพื่อให้เราสามารถเขียนโปรแกรมในรูปแบบการเขียนโปรแกรมแบบฟังก์ชั่น แต่เนื่องจากภาษาจาวาเป็น OOP ดังนั้นการทำงานของโปรแกรมเบื้องหลังจึงยังคงเป็นการทำงานกับออบเจกต์เช่นเดิม ความสามารถที่เพิ่มขึ้นมานี้ช่วยให้เราเขียนโปรแกรมได้ง่ายและกระชับขึ้น 

นิพจน์แบบแลมด้า (Lambda Expression)

     นิพจน์แบบแลมด้า (lambda expression) เป็นการเขียนโปรแกรมในรูปแบบฟังก์ชั่น ทำให้เราสามารถใช้งานเมธอดได้โดยที่ไม่ต้องสร้างออบเจกต์ขึ้นมาก่อน นิพจน์แบบแลมด้าใช้ได้กับคลาสแบบอินเตอร์เฟสที่มีแอบแสตรกเมธอด (abstract method) เพียงเมธอดเดียว แต่คลาสแบบอินเตอร์เฟสดังกล่าวอาจจะมีหลายเมธอดดีฟอลต์ (default method) และหลายเมธอดสแตติก (static method) ก็ได้ แอบแสตรกเมธอดคือเมธอดที่มีแต่ชื่อแต่ไม่มีส่วนของโปรแกรมว่าจะต้องทำงานอย่างไร

     เรามักจะใชันิพจน์แบบแลมด้าแทนการสร้างออบเจกต์ด้วยการใช้งานคลาสแบบไม่ระบุชื่อคลาส (anonymous class) เราจึงมองได้ว่านิพจน์แบบแลมด้าเป็นตัวแทนของออบเจกต์ของคลาสแบบอินเตอร์เฟส คลาสแบบอินเตอร์เฟสที่ใช้นิพจน์แบบแลมด้าได้จะถูกอธิบาย (annotate) ด้วย @FunctionalInterface ใน Java doc

     จากตัวอย่างด้านล่าง คลาส Runnable ซึ่งเป็นคลาสแบบอินเตอร์เฟสที่มีแอบแสตรกเมธอดเดียวคือเมธอด run() และถูกอธิบายว่าเป็น @FunctionalInterface การเรียกใช้งานโดยไม่ใช้นิพจน์แบบแลมด้าจะเป็นการสร้างออบเจกต์ Runnable ด้วยการประกาศคลาสแบบไม่ระบุชื่อคลาส (anonymous class) แล้วจึงกำหนดว่าเมธอด run() ต้องทำอะไรบ้างซึ่งในที่นี้คือพิมพ์ข้อความ หมายความว่าเมธอดที่เราใช้งานขึ้นอยู่กับออบเจกต์ที่สร้างขึ้นมา

     หากเทียบกับการใช้งานในแบบนิพจน์แบบแลมด้าตามตัวอย่างด้านล่าง สังเกตุว่าไม่ต้องมีการประกาศคลาส และเราสามารถเขียนโปรแกรมได้สั้นขึ้นมาก (อย่างไรก็ตามถึงแม้ว่าเราจะไม่ได้มีการสร้างออบเจกต์ขึ้นมา แต่ภาษาจาวาเป็น OOP ดังนั้นสิ่งที่ได้จากการใช้นิพจน์แบบแลมด้ายังคงเป็นออบเจกต์อยู่นั่นเอง)

     โครงสร้างของนิพจน์แบบแลมด้าประกอบด้วย รายการตัวแปร (variable list) เครื่องหมายการกำหนดค่าหรือเรียกว่า arrow token คือ -> และชุดคำสั่งที่เราต้องการทำงาน ตัวอย่างด้านบนเป็นการใช้นิพจน์แบบแลมด้าแบบที่ไม่มีรายการตัวแปรจึงแสดงส่วนของรายการตัวแปรโดยใช้ () ตามด้วย ->  และชุดคำสั่งคือเมธอด println()

     ตัวอย่างด้านล่างเป็นการใช้ชุดคำสั่งมากกว่า 1 บรรทัด

      ตัวอย่างด้านล่างเป็นการสร้างอาเรย์ลิสต์และเรียงลำดับข้อมูลในอาเรย์ลิสต์ โดยสร้างคลาส MyObject ซึ่งมีฟิลด์ field1 เก็บข้อมูลชนิด String ดังโปรแกรมในบรรทัดที่ 32-46 จากนั้นสร้างออบเจกต์ MyObject ขึ้นมา 3 ออบเจกต์ในบรรทัดที่ 9-11 ในบรรทัดที่ 13 สร้างอาเรย์ลิสต์ชื่อ myObjectList เพื่อเก็บออบเจกต์ MyObject และเพิ่มออบเจกต์ MyObject ในอาเรย์ลิสต์ myObjectList ในบรรทัดที่ 15-17 จากนั้นเรียงลำดับข้อมูลในอาเรย์ลิสต์ด้วยเมธอด Collection.sort() โดยสร้างออบเจกต์ Comparator ขึ้นมาเพื่อเปรียบเทียบข้อมูลโดยสร้างจากการประกาศคลาสในแบบไม่ระบุชื่อในบรรทัดที่  19-24 และพิมพ์ค่าในบรรทัดที่ 26-28

     จากตัวอย่างด้านบน หากเราใช้นิพจน์แบบแลมด้าการเรียงลำดับข้อมูลในบรรทัดที่ 19-24 สามารถเขียนได้ดังตัวอย่างด้านล่าง (คลาสแบบอินเตอร์เฟส Comparator ก็เป็น @FunctionalInterface )

     จากตัวอย่างด้านบนจะเห็นว่าเมื่อเราใช้รูปแบบนิพจน์แบบแลมด้า จะไม่เห็นการระบุชื่อ Runnable หรือ Comparator ทั้งนี้เพราะภาษาจาวารู้ได้เองว่าจากเมธอดที่ใช้งานจะใช้ต้องใช้คลาสแบบอินเตอร์เฟส Runnable หรือ Comparator นั่นเอง

    จากตัวอย่างด้านบน เมื่อเปรียบเทียบคำสั่งที่ใช้กับโครงสร้างของนิพจน์แบบแลมด้ารายชื่อตัวแปรคือ (MyObject o1, MyObject o2) และชุดคำสั่งคือ o1.getField1().compareTo(o2.getField1())

     เราสามารถสรุปรูปแบบการใช้งานนิพจน์แบบแลมด้าในกรณีต่างๆได้ดังนี้

1. ในกรณีที่ไม่มีรายการตัวแปรเราต้องกำหนด () ในตำแหน่งของรายการตัวแปร เช่น  () -> Statement

2. ในกรณีที่มีตัวแปรเดียวไม่ต้องมี () ที่ตัวแปรดังกล่าว เช่น SingleListVariable -> Statement 

3. หากมีหลายตัวแปรต้องมี () ครอบตัวแปรทั้งหมด และคั่นระหว่างตัวแปรด้วยเครื่องหมาย , เช่น (type variable1, type variable2) -> Statement 

4. ในกรณีที่ชนิดของตัวแปรในรายการตัวแปรเป็นชนิดเดียวกันกับที่ใช้ในเมธอด เราสามารถละเลยชนิดของตัวแปรได้ เช่น  (variable1, variable2) -> Statement  

5. ในกรณีที่มีชุดคำสั่งเดียวไม่ต้องปิดบรรทัดคำสั่งด้วย ; เช่น () -> Statement 

6. ในกรณีที่มีหลายชุดคำสั่ง ต้องครอบด้วย { } และแต่ละบรรทัดคำสั่งต้องปิดด้วย ; เช่น  () -> {Statement1; Statement2; Statement3;}

7. ในกรณีที่มีการใช้คำสั่ง return จะต้องครอบบรรทัดคำสั่งด้วย { } เสมอถึงแม้จะมีชุดคำสั่งเดียวก็ตาม

     ตัวอย่างด้านล่างเราจะทดลองสร้างคลาสที่เป็น functional interface โดยในบรรทัดที่ 20-22 เป็นการสร้างคลาสแบบอินเตอร์เฟสชื่อ ConcatString ขึ้นมาโดยมีเพียงหนึ่งแอบแสตรกเมธอดซึ่งมีพารามิเตอร์เป็นข้อความ 2 ข้อความ จากนั้นในบรรทัดที่ 15-17 เราสร้างเมธอด doSomethings() ขึ้นมาโดยมีพารามิเตอร์เป็นออบเจกต์ ConcatString และข้อความ เพื่อเรียกใช้เมธอด concatString() ในการดำเนินการกับข้อความ สังเกตุว่าเราไม่ได้มีการระบุในรายละเอียดว่าจะดำเนินการอย่างไรกับข้อความ ซึ่งเราจะระบุในการเรียกใช้งานในแบบคลาสไม่ระบุชื่อ (anonymous class) ในบรรทัดที่ 6-11

      เนื่องจากคลาสแบบอินเตอร์เฟสที่เราสร้างขึ้นมามีเพียงแอบแสตรกเมธอดเดียว จึงสามารถใช้นิพจน์แบบแลมด้าได้ ตามตัวอย่างด้านล่าง

     เราสามารถกำหนดนิพจน์แบบแลมด้าให้กับตัวแปรก่อนนำไปใช้งานได้ดังตัวอย่างด้านล่าง ในบรรทัดที่ 7 เป็นการกำหนดนิพจน์แบบแลมด้าให้กับตัวแปร cc หลังจากนั้นจึงเอาไปเรียกใช้ในเมธอด doSomethings อีกทีหนึ่ง ซึ่งลักษณะการใช้งานแบบนี้ทำให้เราเอานิพจน์แบบแลมด้าไปใช้ได้ในหลายส่วนของโปรแกรมโดยไม่ต้องเขียนนิพจน์แบบแลมด้าดังกล่าวซ้ำๆในหลายๆที่

     จากตัวอย่างด้านบนนิพจน์แบบแลมด้ามีชุดคำสั่งเดียว ดังนั้นเราจึงระบุแต่ชุดคำสั่งโดยไม่ต้องมีคำสั่ง return() แต่ถ้าเรามีชุดคำสั่งมากกว่าหนึ่งหรือต้องใช้คำสั่ง return() เราต้องครอบชุดคำสั่งด้วย { } ดังตัวอย่างด้านล่าง

          โปรแกรม IntelliJ IDEA จะช่วยแนะนำเราในการใช้นิพจน์แบบแลมด้า เช่นในบรรทัดที่ 6 จากตัวอย่างด้านล่าง สังเกตุว่า new ConcatString() เป็นสีเทาและเมื่อเราเอาเมาส์ไปชี้ IntelliJ IDEA จะแนะนำว่าสามารถเปลี่ยนเป็นนิพจน์แบบแลมด้าได้ซึ่งหากเราเลือกที่ Replace with lambda โปรแกรม IntelliJ IDEA จะดำเนินการเปลี่ยนเป็นนิพจน์แบบแลมด้าให้เรา

นิพจน์แบบแลมด้าก็เหมือนบล็อก

     ถึงแม้ว่าเราจะเห็นว่านิพจน์แบบแลมด้าเป็นเหมือนชุดคำสั่งแบบหนึ่ง แต่จริงๆแล้วนิพจน์แบบแลมด้าเป็นเหมือนบล็อกๆหนึ่ง เราสามารถทดสอบแนวคิดนี้ได้โดยการใช้งานตัวแปรที่อยู่นอกนิพจน์แบบแลมด้ากับนิพจน์แบบแลมด้า

     จากตัวอย่างการใช้งานนิพจน์แบบแลมด้าที่ผ่านมา เรากำหนดตัวแปร i = 0 ในบรรทัดที่ 6 และสั่งพิมพ์ตัวแปร i  ในนิพจน์แบบแลมด้าในบรรทัดที่ 8 ซึ่งจะสามารถพิมพ์ค่า i ออกมาได้ 

     แต่หากเราเปลี่ยนค่าตัวแปร i ในบรรทัดที่ 7 ตามตัวอย่างด้านล่าง จะพบว่า IntelliJ IDEA แจ้งข้อผิดพลาดขึ้นมาว่า local variables referenced from a lambda expression must be final or effectively final  นั่นคือตัวแปรที่ไม่ได้อยู่ในนิพจน์แบบแลมด้าที่ถูกนำมาใช้ในนิพจน์แบบแลมด้าจะต้องเป็นตัวแปรแบบ final แต่ที่เราไม่พบการแจ้งข้อผิดพลาดในตัวอย่างก่อนหน้าเพราะถึงแม้ว่าตัวแปรจะไม่ถูกกำหนดเป็น final แต่ถ้าตัวแปรไม่ถูกแก้ไขเราจะเรียกว่า effectively final 

     ในทำนองเดียวกัน เมื่อเราอ้างถึงตัวแปรในนิพจน์แบบแลมด้าจากภายนอก จะเห็นว่าเกิดข้อผิดพลาดว่าไม่รู้จักตัวแปรดังกล่าว

อินเตอร์เฟสแบบฟังก์ชั่น (Functional Interface)

    อินเตอร์เฟสแบบฟังก์ชั่นคือคลาสแบบอินเตอร์เฟสที่ถูกอธิบาย (annotated) ด้วย @FuinctionalInterface และมีแอบแสตรกเมธอด (abstract method) เพียงเมธอดเดียว แต่อาจจะมีหลายเมธอดดีฟอลต์ (default method) และหลาย

เมธอดสแตติก (static method) ก็ได้ อินเตอร์เฟสแบบฟังก์ชั่นสามารถใช้งานได้ด้วยนิพจน์แบบแลมด้า 

     จากตัวอย่างด้านบนจะเห็นว่า Runnable เป็นอินเตอร์เฟสแบบฟังก์ชั่น ดังนั้นเราสามารถเรียกใช้ในแบบนิพจน์แบบแลมด้าได้ เช่น จากตัวอย่างด้านล่างเราเรียกใช้ในแบบคลาสไม่มีชื่อ (anonymous class)

     เนื่องจาก Runnable เป็น functional interface จึงสามารถใช้งานในรูปแบบนิพจน์แบบแลมด้าได้ดังนี้

     ภาษาจาวามีหลายคลาสแบบอินเทอร์เฟซที่ถูกแปลงเป็นคลาสแบบอินเตอร์เฟสที่ถูกอธิบาย (annotated) ด้วย @FuinctionalInterface ซึ่งเราสามารถใช้งานด้วยนิพจน์แบบแลมด้าได้ เช่น

Runnable -> มีเฉพาะแอบแสตรกเมธอด run()

Comparable-> มีเฉพาะแอบแสตรกเมธอด CompareTo()

ActionListener -> มีเฉพาะแอบแสตรกเมธอด actionPerformed()

Callable -> มีเฉพาะแอบแสตรกเมธอด call ()

 แพคเกจ java.util.function

     ภาษาจาวามีแพคเกจ java.util.function ซึ่งเป็นแพคเกจของคลาสแบบอินเตอร์เฟสที่ถูกอธิบาย (annotated) ด้วย @FunctionalInterface ที่ถูกสร้างขึ้นมาเพื่อเป็นโครงสร้างให้เราใช้งานนิพจน์แบบแลมด้าได้ง่ายๆโดยที่ไม่ต้องเขียนคลาสแบบอินเตอร์เฟสขึ้นมาเอง คลาสหลักๆในแพคเกจ java.util.function ประกอบด้วย คลาสแบบอินเตอร์เฟส Consumer คลาสแบบอินเตอร์เฟส Predicate คลาสแบบอินเตอร์เฟส Function คลาสแบบอินเตอร์เฟส Supplier และคลาสแบบอินเตอร์เฟส UnaryOperator ส่วนคลาสอื่นๆเป็นส่วนขยายของคลาสดังกล่าวเพื่อรองรับการใช้งานกับชนิดข้อมูลแบบเฉพาะเจาะจง เช่น DoubleConsumer หรือรองรับตัวแปรได้สองตัว เช่น BiPredicate เป็นต้น

คลาสแบบอินเตอร์เฟส Consumer

     คลาสแบบอินเตอร์เฟส Consumer ถูกออกแบบมาให้รองรับนิพจน์แบบ

แลมด้าที่รับค่าเพียงค่าเดียวเพื่อนำไปดำเนินการตามที่กำหนดและไม่มีการส่งค่ากลับ เราเรียกใช้เมธอด accept() เพื่อทำงานตามคำสั่งในนิพจน์แบบแลมด้า

     ในการใช้งานเราสร้างตัวแปรชนิด Consumer ขึ้นมาเพื่อเก็บนิพจน์แบบแลมด้า โดนเราสามารถกำหนดชนิดของออบเจกต์ของตัวแปรได้ ซึ่งหากไม่กำหนดตัวแปรจะถูกมมองเป็นออบเจกต์ทั่วไป
    คลาสแบบอินเตอร์เฟส Consumer มีส่วนขยายอีกหลายคลาสประกอบด้วย BiConsumer ซึ่งรองรับค่าได้ 2 ค่า และ DoubleConsumer IntConsumer LongConsumer เพื่อรองรับข้อมูลชนิด primitive data type 

     ตัวอย่างด้านล่างเป็นการพิมพ์ข้อมูลตามที่กำหนด โดยบรรทัดที่ 8  เป็นการสร้างตัวแปรชนิด Consumer เพื่อเก็บนิพจน์แบบแลมด้าและในบรรทัดที่ 12 เป็นการเรียกใช้ด้วยเมธอด accept()

     Java API ก็ใช้คลาสแบบอินเตอร์เฟส Consumer ด้วย ดังตัวอย่างด้านล่างในบรรทัดที่ 14 เราใช้เมธอด forEach() ของอาเรย์ลิสต์ในการวนรอบเพื่อพิมพ์ข้อมูลจากลิสต์ ซึ่งเมธอด forEach() เองก็ใช้คลาสแบบอินเตอร์เฟส Consumer เพื่อให้เราสามารถใช้นิพจน์แบบแลมด้าได้

คลาสแบบอินเตอร์เฟส Predicate

     คลาสแบบอินเตอร์เฟส Predicate  ถูกออกแบบมาให้รองรับนิพจน์แบบแลมด้าที่รับค่าเพียงค่าเดียวเพื่อดำเนินการตามที่กำหนดและคืนค่ากลับเป็น true หรือ false เราเรียกใช้เมธอด test() เพื่อทำงานตามคำสั่งในนิพจน์แบบแลมด้า

     คลาสแบบอินเตอร์เฟส Predicate ก็มีส่วนขยายเช่นเดียวกัน เช่น BiPredicate ซึ่งรองรับค่าได้ 2 ค่า และ DoublePredicate IntPredicate LongPredicate เพื่อรองรับข้อมูลชนิด primitive data type 

     ตัวอย่างด้านล่างเป็นการทดสอบเพื่อพิมพ์เฉพาะค่าที่น้อยกว่า 100 โดยบรรทัดที่ 8 เป็นการสร้างตัวแปรชนิด Predicate เพื่อเก็บนิพจน์แบบแลมด้าและในบรรทัดที่ 10 เป็นการเรียกใช้ด้วยเมธอด test()

     เราสามารถใช้เมธอด and() isEqual() negate() not() or() เพื่อเชื่อมเงื่อนไขเข้าด้วยกัน ดังตัวอย่างด้านล่างในบรรทัดที่ 11 เป็นการใช้เมธอด and() เพื่อตรวจสอบค่าว่ามากกว่า 10 แต่น้อยกว่า 100 หรือไม่ 

คลาสแบบอินเตอร์เฟส Function

     คลาสแบบอินเตอร์เฟส Function ถูกออกแบบมาให้รองรับนิพจน์แบบแลมด้าที่รับค่าเพียงค่าเดียวเพื่อดำเนินการตามที่กำหนดและคืนค่าที่ได้จากการดำเนินการ โดยชนิดข้อมูลที่นำเข้าและส่งออกสามารถแตกต่างกันได้ เราเรียกใช้เมธอด apply() เพื่อทำงานตามคำสั่งในนิพจน์แบบแลมด้า
    คลาสแบบอินเตอร์เฟส Function จะมีส่วนขยายอยู่หลายคลาสเพื่อรองรับเพื่อรองรับค่าที่เป็นชนิด primitive data type และ BiFunction ซึ่งรองรับค่าได้ 2 ค่า

     ตัวอย่างด้านล่างเป็นการพิมพ์ชื่อของรายการแรกในลิสต์ โดยในบรรทัดที่ 15 เป็นการสร้างตัวแปรชนิด Function เพื่อเก็บนิพจน์แบบแลมด้าและในบรรทัดที่ 17 เป็นการเรียกใช้ด้วยเมธอด accept()

    เราสามารถนำฟังก์ชั่นมาต่อกันโดยใช้เมธอด andThen() โดยที่ผลลัพธ์จากฟังก์ชั่นแรกจะเป็นข้อมูลนำเข้าของฟังก์ชั่นที่สอง ตัวอย่างด้านล่างเป็นการพิมพ์เฉพาะชื่อในรายการแรกของลิสต์ในแบบตัวอักษรใหญ่โดยฟังก์ชั่นแรกเป็นการแปลงจากออบเจกต์ employee เป็น String อักษรใหญ่ ซึ่งเป็นข้อมูลที่จะนำเข้าในฟังก์ชั่นที่สองและคืนค่าเป็น String โดยในบรรทัดที่ 15 และ 17 เป็นการสร้างตัวแปรชนิด Function เพื่อเก็บนิพจน์แบบแลมด้าและในบรรทัดที่ 18 เป็นการสร้างตัวแปรชนิด Function เพื่อเก็บการเชื่อมต่อของทั้งสองฟังก์ชั่นและในบรรทัดที่ 19 เป็นการเรียกใช้ด้วยเมธอด apply()

คลาสแบบอินเตอร์เฟส Supplier

     คลาสแบบอินเตอร์เฟส Supplier ถูกออกแบบมาให้รองรับนิพจน์แบบแลมด้าที่ไม่รับค่าแต่สามารถส่งค่าที่ได้จากการดำเนินการกลับไปให้ โดยมากใช้ในการสร้างค่าต่างๆ เช่น สุ่มตัวเลข เป็นต้น เราเรียกใช้เมธอด get() เพื่อทำงานตามคำสั่งในนิพจน์แบบแลมด้า
    คลาสแบบอินเตอร์เฟส Supplier ก็มีส่วนขยายเช่นเดียวกัน เช่น DoubleSupplier IntSupplier LongSupplier เพื่อรองรับข้อมูลชนิด primitive data type 

     จากตัวอย่างด้านล่างเป็นการสุ่มค่าที่เป็นจำนวนเต็มจาก 0-1000 โดยในบรรทัดที่ 10 ป็นการสร้างตัวแปรชนิด Supplier เพื่อเก็บนิพจน์แบบแลมด้าและในบรรทัดที่ 11 เป็นการเรียกใช้ด้วยเมธอด get()

คลาสแบบอินเตอร์เฟส UnaryOperator

          คลาสแบบอินเตอร์เฟส UnaryOperator ถูกออกแบบมาให้รองรับนิพจน์แบบแลมด้าที่รับค่าเพียงค่าเดียวเพื่อดำเนินการตามที่กำหนดและคืนค่าที่ได้จากการดำเนินการ โดยชนิดข้อมูลที่นำเข้าและส่งออกต้องเป็นชนิดข้อมูลเดียวกัน  เราเรียกใช้เมธอด apply() เพื่อทำงานตามคำสั่งในนิพจน์แบบแลมด้า

Streams และ Method Reference

     Streams ในที่นี้ไม่ใช่ I/O Streams แต่เป็นการทำงานกับชุดข้อมูลโดย ใช้คลาสแบบอินเตอร์เฟส Streams ซี่งอยู่ในแพคเกจ java.util.stream  โดยออบ

เจกต์ Streams จะหุ้มห่อ (wrap) ชุดข้อมูลเพื่อให้เราดำเนินการกับชุดข้อมูลได้สะดวกและรวดเร็ว ออบเจกต์ Streams ไม่ใช่โครงสร้างข้อมูล ไม่มีการเก็บข้อมูล และไม่มีผลกระทบกับแหล่งข้อมูลที่ห่อหุ้มด้วยออบเจกต์ Streams 

     ลักษณะการทำงานของ Streams เป็นการดำเนินงานกับชุดของข้อมูลในรูปแบบฟังก์ชั่น (functional-style operations ) เหมือนการนำหลายๆฟังก์ชั่นมาร้อยเรียงต่อกันจนกระทั่งได้ผลลัพธ์ที่ต้องการ โดยเริ่มจากการสร้างออบเจกต์ Streams ขึ้นมาด้วยชุดข้อมูลที่ต้องการดำเนินการจากนั้นเรียกใช้เมธอดต่างๆของออบเจกต์ Streams เพื่อดำเนินการกับชุดข้อมูลตามที่ต้องการโดยผลลัพธ์ที่ได้จากแต่ละเมธอดก็เป็นออบเจกต์ Streams เช่นเดียวกันซึ่งจะถูกส่งต่อไปทำงานในเมธอดในลำดับถัดๆไป แต่จะมีบางเมธอดที่ไม่ได้ให้ผลลัพธ์กลับออกมาเป็นออบเจกต์ Streams เราเรียกเมธอดแบบนี้ว่า terminal operations ซึ่งการเรียกใช้เมธอดที่เป็น terminal operations หมายถึงจบการทำงานแล้วนั่นเอง ส่วนเมธอดที่ให้ผลลัพธ์เป็นออบเจกต์ Streams เราเรียกว่า intermediate operations

     ในคลาสแบบอินเตอร์เฟส Collection จะมีเมธอด stream() เพื่อให้เราสร้างออบเจกต์ Streams ขึ้นมา เช่น ถ้าเรามีลิสต์ของชื่อและต้องการพิมพ์เฉพาะชื่อที่ขึ้นต้นด้วยอักษร T และพิมพ์เป็นตัวอักษรใหญ่ทั้งหมด เราสามารถดำเนินการด้วยออบเจกต์ Streams ได้ดังตัวอย่างด้านล่าง

     ในบรรทัดที่ 9-13 เราสร้างอาเรย์ลิสต์ชื่อ name และเพิ่มชื่อตัวอย่างลงในลิสต์ บรรทัดที่ 14 เป็นการสร้างออบเจกต์ Streams โดยเรียกเมธอด stream() จากนั้นตามด้วยเมธอดที่เป็นลำดับของงานที่เราต้องการคือเมธอด filter() เพื่อกรองเฉพาะชื่อที่ขึ้นต้นด้วยอักษร T จากนั้นใช้เมธอด map() เพื่อแปลงเป็นตัวอักษรใหญ่ และใช้เมธอด forEach เพื่อวนรอบพิมพ์ข้อมูล 

     การเรียกใช้เมธอด stream() จะเป็นการบอกว่าเราจะสร้างออบเจกต์ Streams ด้วยชุดของข้อมูลตามที่กำหนด สิ่งที่ตามมาคือลำดับของเมธอดดำเนินงานซึ่งจะคั่นด้วย . นั่นเอง เมื่อเราพิมพ์ .  IntelliJ IDEA จะแสดงเมธอดที่ใช้ได้มาให้เราเลือก หากลองสังเกตุดูจะเห็นว่าแต่ละเมธอดมีพื้นฐานเป็นคลาสแบบอินเตอร์เฟสในแพคเกจ java.util.function นั่นเอง เมธอดที่เราเรียกใช้นี้อาจจะมีชื่อเหมือนกับเมธอดที่เราเคยใช้ในหัวข้ออื่น ดังนั้นให้นึกเสมอว่าเมธอดเหล่านี้เป็นเมธอดในคลาสแบบอินเตอร์เฟส Streams

     สำหรับเงื่อนไขที่เป็นพารามิเตอร์ของเมธอด ในบางกรณีเราสามารถเขียนในรูปแบบ method reference ซึ่งเป็นการอ้างอิงถึงคลาสและเมธอดที่จะใช้เท่านั้นก็ได้ เช่น String::toSupperCase ซึ่งมีความหมายเท่ากับ s -> s.toUpperCase โดยที่ s เป็นชนิดข้อมูลแบบ String หรือ System.out::printIn ซึ่งมีความหมายเท่ากับ s -> System.out.printIn(s) แต่ถูกลดรูปลงเพื่อให้กระชับขึ้น

     นอกจากการสร้างออบเจกต์ Streams ด้วยเมธอด steam() จากชุดข้อมูลแล้ว เราสามารถสร้างออบเจกต์ Streams และกำหนดชุดข้อมูลให้กับออบเจกต์ Streams โดยตรงได้ด้วยเมธอด of() ดังตัวอย่างด้านล่าง

Stream<String> numberString = Stream.of(“1”, “2”, “3”)

     ในกรณีที่เราอาจจะต้องการพิมพ์ข้อมูลออกมาดูก่อนที่จะดำเนินการต่อไปซึ่งเราไม่สามารถใช้เมธอด forEach() เพราะเป็น terminal operations เราสามารถใช้เมธอด peek() ได้ดังตัวอย่างด้านล่าง

peek(System.out::printIn)

     ในกรณีที่เราต้องการพิมพ์สมาชิกของลิสต์มากกว่า 1 ลิสต์โดยที่ชนิดข้อมูลของลิสต์และชนิดข้อมูลของสมาชิกของลิสต์เหมือนกัน เราสามารถทำได้โดยใช้เมธอด flatMap() จากตัวอย่างด้านล่างเป็นการสร้างคลาสของพนักงานและแผนกขึ้นมาโดยในเมธอด main() บรรทัดที่ 9 – 18 เราสร้างออบเจกต์พนักงานและออบเจกต์แผนกขึ้นมาจากนั้นบรรจุพนักงานลงในแผนก

      และเราต้องการพิมพ์รายชื่อพนักงานจากทุกแผนกโดยที่ไม่ต้องการเขียนโปรแกรมวนรอบทีละแผนก เราสร้างลิสต์ของแผนกและบรรจุออบเจกต์แผนกลงในลิสต์ในบรรทัดที่ 19 – 21 จากนั้นสร้างออบเจกต์สตรีมในบรรทัดที่ 22 และเรียกใช้เมธอด flatMap() ในบรรทัดที่ 23 ซึ่งจริงๆแล้วเราต้องการดำเนินการกับลิสต์ของออบเจกต์ Employee แต่ลิสต์นั้นไม่ใช่แหล่งข้อมูลโดยตรง เราจึงใช้เมธอด flatMap() กับแต่ละออบเจกต์ Department ในสตรีมเพื่อเข้าถึงออบเจกต์ Employee โดยการใช้เมธอด getEmployees() ของออบเจกต์ และแปลงเป็นสตรีมอีกทีหนึ่งโดยใช้เมธอด stream() จากนั้นจึงส่งผลลัพธ์ที่เป็นสตรีมส่งต่อให้เมธอด forEach() ในบรรทัดที่ 24 พิมพ์ชื่อพนักงานออกมา

          รูปด้านล่างแสดงข้อแตกต่างของเมธอด map() และ flatMap()

    เราสามารถบันทึกผลลัพธ์จากการดำเนินการของสตรีมเป็นลิสต์ได้โดยใช้เมธอด collect() จากตัวอย่างด้านล่างเราสร้างลิสต์ของออบเจกต์ Employee ในบรรทัดที่ 23  เพื่อมารับผลลัพธ์ที่ได้จากการใช้เมธอด collect() ในบรรทัดที่ 25 และสั่งพิมพ์ข้อมูลจากลิสต์ในบรรทัดที่ 26 ซึ่งจะได้ผลลัพธ์ เช่นเดียวกับตัวอย่างด้านบน

     การดำเนินการของสตรีมเป็นแบบที่เรียกว่า lazily evaluation คือจะไม่ทำงานถ้าไม่มีเมธอดที่เป็น terminal operations ปิดท้าย