เวอร์ชันใหม่ล่าสุดของแพลตฟอร์ม Java จาวา 8 เปิดตัวมานานกว่าหนึ่งปีแล้ว หลาย บริษัท และ นักพัฒนา ยังคงใช้งานได้กับเวอร์ชันก่อนหน้าซึ่งเป็นที่เข้าใจได้เนื่องจากมีปัญหามากมายในการย้ายจากเวอร์ชันแพลตฟอร์มหนึ่งไปยังอีกเวอร์ชันหนึ่ง ถึงกระนั้นนักพัฒนาจำนวนมากก็ยังคงเริ่มต้น ใหม่ แอปพลิเคชันที่มี Java เวอร์ชันเก่า มีเหตุผลที่ดีน้อยมากที่จะทำเช่นนี้เนื่องจาก Java 8 ได้นำการปรับปรุงที่สำคัญบางอย่างมาสู่ภาษา
มีมากมาย คุณสมบัติใหม่ ใน Java 8 ฉันจะแสดงสิ่งที่มีประโยชน์และน่าสนใจที่สุดให้คุณดู:
CompletableFuture
ถึง แลมด้า เป็นบล็อกรหัสที่สามารถอ้างอิงและส่งผ่านไปยังส่วนของรหัสอื่นสำหรับการดำเนินการในอนาคตอย่างน้อยหนึ่งครั้ง ตัวอย่างเช่นฟังก์ชันที่ไม่ระบุชื่อในภาษาอื่นคือแลมบ์ดา เช่นเดียวกับฟังก์ชัน lambdas สามารถส่งผ่านอาร์กิวเมนต์ในขณะที่ดำเนินการแก้ไขผลลัพธ์ได้ แนะนำ Java 8 นิพจน์แลมบ์ดา ซึ่งนำเสนอไวยากรณ์ง่ายๆในการสร้างและใช้ lambdas
มาดูตัวอย่างวิธีการปรับปรุงโค้ดของเรา ที่นี่เรามีตัวเปรียบเทียบง่ายๆซึ่งเปรียบเทียบสอง Integer
ค่าตามโมดูโล 2:
class BinaryComparator implements Comparator{ @Override public int compare(Integer i1, Integer i2) { return i1 % 2 - i2 % 2; } }
ในอนาคตอาจมีการเรียกอินสแตนซ์ของคลาสนี้ในโค้ดซึ่งจำเป็นต้องใช้ตัวเปรียบเทียบนี้เช่นนี้:
... List list = ...; Comparator comparator = new BinaryComparator(); Collections.sort(list, comparator); ...
ไวยากรณ์แลมบ์ดาใหม่ช่วยให้เราทำสิ่งนี้ได้ง่ายขึ้น นี่คือนิพจน์แลมบ์ดาที่เรียบง่ายซึ่งทำสิ่งเดียวกับ compare
วิธีการจาก BinaryComparator
:
(Integer i1, Integer i2) -> i1 % 2 - i2 % 2;
โครงสร้างมีความคล้ายคลึงกันมากกับฟังก์ชัน ในวงเล็บเราตั้งค่ารายการอาร์กิวเมนต์ ไวยากรณ์ ->
แสดงว่านี่คือแลมด้า และในส่วนทางขวามือของนิพจน์นี้เราตั้งค่าพฤติกรรมของแลมด้า
ตอนนี้เราสามารถปรับปรุงตัวอย่างก่อนหน้าของเรา:
... List list = ...; Collections.sort(list, (Integer i1, Integer i2) -> i1 % 2 - i2 % 2); ...
เราอาจกำหนดตัวแปรด้วยวัตถุนี้ มาดูกันว่าหน้าตาเป็นอย่างไร:
ข้อใดต่อไปนี้ถือเป็นปัญหาด้านความปลอดภัยทั่วไป (เลือกทุกข้อที่ใช่)
Comparator comparator = (Integer i1, Integer i2) -> i1 % 2 - i2 % 2;
ตอนนี้เราสามารถใช้ฟังก์ชันนี้ซ้ำได้ดังนี้:
... List list1 = ...; List list2 = ...; Collections.sort(list1, comparator); Collections.sort(list2, comparator); ...
สังเกตว่าในตัวอย่างเหล่านี้แลมด้าจะถูกส่งต่อไปยัง sort()
วิธีเดียวกับอินสแตนซ์ของ BinaryComparator
ถูกส่งผ่านในตัวอย่างก่อนหน้านี้ JVM รู้ได้อย่างไรว่าจะตีความแลมด้าอย่างถูกต้อง?
เพื่อให้ฟังก์ชันใช้ lambdas เป็นอาร์กิวเมนต์ Java 8 จึงแนะนำแนวคิดใหม่: อินเตอร์เฟซที่ใช้งานได้ . อินเทอร์เฟซที่ใช้งานได้คืออินเทอร์เฟซที่มีวิธีนามธรรมเพียงวิธีเดียว ในความเป็นจริง Java 8 ถือว่านิพจน์แลมบ์ดาเป็นการใช้งานอินเทอร์เฟซการทำงานแบบพิเศษ ซึ่งหมายความว่าในการรับแลมบ์ดาเป็นอาร์กิวเมนต์เมธอดประเภทที่ประกาศของอาร์กิวเมนต์นั้นจะต้องเป็นอินเทอร์เฟซที่ใช้งานได้เท่านั้น
เมื่อเราประกาศอินเทอร์เฟซที่ใช้งานได้เราอาจเพิ่ม @FunctionalInterface
สัญกรณ์เพื่อแสดงให้นักพัฒนาเห็นว่ามันคืออะไร:
@FunctionalInterface private interface DTOSender { void send(String accountId, DTO dto); } void sendDTO(BisnessModel object, DTOSender dtoSender) { //some logic for sending... ... dtoSender.send(id, dto); ... }
ตอนนี้เราสามารถเรียก method sendDTO
โดยส่งผ่าน lambdas ที่แตกต่างกันเพื่อให้ได้พฤติกรรมที่แตกต่างกันดังนี้:
sendDTO(object, ((accountId, dto) -> sendToAndroid(accountId, dto))); sendDTO(object, ((accountId, dto) -> sendToIos(accountId, dto)));
อาร์กิวเมนต์แลมบ์ดาช่วยให้เราสามารถปรับเปลี่ยนพฤติกรรมของฟังก์ชันหรือวิธีการได้ ดังที่เราเห็นในตัวอย่างสุดท้ายบางครั้งแลมด้าทำหน้าที่เรียกวิธีอื่นเท่านั้น (sendToAndroid
หรือ sendToIos
) สำหรับกรณีพิเศษนี้ Java 8 แนะนำชวเลขที่สะดวก: การอ้างอิงวิธีการ . ไวยากรณ์แบบย่อนี้แสดงถึงแลมบ์ดาที่เรียกเมธอดและมีรูปแบบ objectName::methodName
สิ่งนี้ช่วยให้เราทำให้ตัวอย่างก่อนหน้านี้กระชับและอ่านง่ายยิ่งขึ้น:
sendDTO(object, this::sendToAndroid); sendDTO(object, this::sendToIos);
ในกรณีนี้วิธีการ sendToAndroid
และ sendToIos
ถูกนำไปใช้ใน this
ชั้นเรียน. เรายังอาจอ้างอิงวิธีการของวัตถุหรือคลาสอื่น
Java 8 นำความสามารถใหม่มาใช้กับ Collections
ในรูปแบบของ Stream API ใหม่ล่าสุด ฟังก์ชันใหม่นี้มีให้โดย java.util.stream
และมุ่งเป้าไปที่การเปิดใช้งาน การทำงาน แนวทางการเขียนโปรแกรมด้วยคอลเล็กชัน ดังที่เราจะเห็นสิ่งนี้เป็นไปได้มากเนื่องจากไวยากรณ์แลมบ์ดาใหม่ที่เราเพิ่งพูดถึง
Stream API นำเสนอการกรองการนับและการแมปคอลเลคชันที่ง่ายดายรวมถึงวิธีต่างๆในการดึงข้อมูลชิ้นส่วนและชุดย่อยออกจากข้อมูลเหล่านี้ ด้วยไวยากรณ์รูปแบบการทำงานทำให้ Stream API อนุญาตให้ใช้โค้ดที่สั้นและสวยงามมากขึ้นสำหรับการทำงานกับคอลเลกชัน
เริ่มต้นด้วยตัวอย่างสั้น ๆ เราจะใช้โมเดลข้อมูลนี้ในตัวอย่างทั้งหมด:
class Author { String name; int countOfBooks; } class Book { String name; int year; Author author; }
ลองนึกภาพว่าเราต้องพิมพ์ผู้เขียนทั้งหมดใน a books
คอลเลกชันที่เขียนหนังสือหลังปี 2005 เราจะทำอย่างไรใน Java 7?
for (Book book : books) { if (book.author != null && book.year > 2005){ System.out.println(book.author.name); } }
แล้วเราจะทำอย่างไรใน Java 8?
books.stream() .filter(book -> book.year > 2005) // filter out books published in or before 2005 .map(Book::getAuthor) // get the list of authors for the remaining books .filter(Objects::nonNull) // remove null authors from the list .map(Author::getName) // get the list of names for the remaining authors .forEach(System.out::println); // print the value of each remaining element
เป็นเพียงสำนวนเดียวเท่านั้น! เรียกเมธอด stream()
เมื่อใด ๆ Collection
ส่งคืน a Stream
วัตถุที่ห่อหุ้มองค์ประกอบทั้งหมดของคอลเลกชันนั้น สิ่งนี้สามารถจัดการได้ด้วยตัวปรับแต่งต่างๆจาก Stream API เช่น filter()
และ map()
. ตัวปรับแต่งแต่ละตัวจะส่งคืน Stream
ใหม่ วัตถุที่มีผลการปรับเปลี่ยนซึ่งสามารถจัดการเพิ่มเติมได้ .forEach()
วิธีการช่วยให้เราดำเนินการบางอย่างสำหรับแต่ละอินสแตนซ์ของสตรีมที่เป็นผลลัพธ์
ตัวอย่างนี้ยังแสดงให้เห็นถึงความสัมพันธ์ที่ใกล้ชิดระหว่างการเขียนโปรแกรมเชิงฟังก์ชันและนิพจน์แลมบ์ดา สังเกตว่าอาร์กิวเมนต์ที่ส่งไปยังแต่ละเมธอดในสตรีมอาจเป็นแลมด้าที่กำหนดเองหรือการอ้างอิงเมธอด ในทางเทคนิคตัวปรับแต่งแต่ละตัวสามารถรับอินเทอร์เฟซที่ใช้งานได้ตามที่อธิบายไว้ในส่วนก่อนหน้า
Stream API ช่วยให้นักพัฒนาดูคอลเลกชัน Java จากมุมมองใหม่ ลองนึกภาพตอนนี้ว่าเราต้องได้รับ Map
ของภาษาที่ใช้ได้ในแต่ละประเทศ สิ่งนี้จะนำไปใช้ใน Java 7 ได้อย่างไร?
Map countryToSetOfLanguages = new HashMap(); for (Locale locale : Locale.getAvailableLocales()){ String country = locale.getDisplayCountry(); if (!countryToSetOfLanguages.containsKey(country)){ countryToSetOfLanguages.put(country, new HashSet()); } countryToSetOfLanguages.get(country).add(locale.getDisplayLanguage()); }
ใน Java 8 สิ่งต่าง ๆ ค่อนข้างดีกว่า:
import java.util.stream.*; import static java.util.stream.Collectors.*; ... Map countryToSetOfLanguages = Stream.of(Locale.getAvailableLocales()) .collect(groupingBy(Locale::getDisplayCountry, mapping(Locale::getDisplayLanguage, toSet())));
วิธีการ collect()
ช่วยให้เรารวบรวมผลลัพธ์ของสตรีมในรูปแบบต่างๆ ที่นี่เราจะเห็นว่าอันดับแรกจัดกลุ่มตามประเทศจากนั้นจึงจับคู่แต่ละกลุ่มตามภาษา (groupingBy()
และ toSet()
เป็นวิธีการคงที่จากคลาส Collectors
)
วิธีเขียน unit test java
Stream API มีความสามารถอื่น ๆ อีกมากมาย สามารถดูเอกสารฉบับสมบูรณ์ได้ ที่นี่ . ฉันขอแนะนำให้อ่านเพิ่มเติมเพื่อทำความเข้าใจอย่างลึกซึ้งยิ่งขึ้นเกี่ยวกับเครื่องมืออันทรงพลังทั้งหมดที่แพ็คเกจนี้มีให้
CompletableFuture
ใน Java 7’s java.util.concurrent
แพคเกจมีอินเทอร์เฟซ Future
ซึ่งช่วยให้เราได้รับสถานะหรือผลลัพธ์ของงานอะซิงโครนัสบางอย่างในอนาคต ในการใช้ฟังก์ชันนี้เราต้อง:
ExecutorService
ซึ่งจัดการการดำเนินการของงานอะซิงโครนัสและสามารถสร้าง Future
วัตถุเพื่อติดตามความคืบหน้าRunnable
งาน.ExecutorService
ซึ่งจะให้ Future
ให้สิทธิ์เข้าถึงสถานะหรือผลลัพธ์ในการใช้ผลลัพธ์ของงานแบบอะซิงโครนัสจำเป็นต้องติดตามความคืบหน้าจากภายนอกโดยใช้วิธีการของ Future
อินเทอร์เฟซและเมื่อพร้อมแล้วให้ดึงผลลัพธ์อย่างชัดเจนและดำเนินการเพิ่มเติมกับพวกเขา สิ่งนี้ค่อนข้างซับซ้อนในการนำไปใช้โดยไม่มีข้อผิดพลาดโดยเฉพาะอย่างยิ่งในแอปพลิเคชันที่มีงานพร้อมกันจำนวนมาก
อย่างไรก็ตามใน Java 8 นั้น Future
แนวคิดถูกนำไปใช้เพิ่มเติมด้วย CompletableFuture
อินเทอร์เฟซซึ่งอนุญาตให้สร้างและดำเนินการโซ่ของงานอะซิงโครนัส เป็นกลไกที่มีประสิทธิภาพในการสร้างแอปพลิเคชันแบบอะซิงโครนัสใน Java 8 เนื่องจากช่วยให้เราประมวลผลผลลัพธ์ของแต่ละงานโดยอัตโนมัติเมื่อเสร็จสิ้น
ลองดูตัวอย่าง:
import java.util.concurrent.CompletableFuture; ... CompletableFuture voidCompletableFuture = CompletableFuture.supplyAsync(() -> blockingReadPage()) .thenApply(this::getLinks) .thenAccept(System.out::println);
วิธีการ CompletableFuture.supplyAsync
สร้างงานอะซิงโครนัสใหม่ในค่าเริ่มต้น Executor
(โดยทั่วไป ForkJoinPool
). เมื่องานเสร็จสิ้นผลลัพธ์จะถูกจัดให้เป็นอาร์กิวเมนต์โดยอัตโนมัติสำหรับฟังก์ชัน this::getLinks
ซึ่งรันในงานอะซิงโครนัสใหม่ สุดท้ายผลลัพธ์ของขั้นตอนที่สองนี้จะถูกพิมพ์ไปที่ System.out
โดยอัตโนมัติ thenApply()
และ thenAccept()
เป็นเพียงสองในหลาย ๆ วิธีการที่มีประโยชน์ พร้อมที่จะช่วยคุณสร้างงานพร้อมกันโดยไม่ต้องใช้ Executors
ด้วยตนเอง
CompletableFuture
ทำให้ง่ายต่อการจัดการลำดับของการดำเนินการแบบอะซิงโครนัสที่ซับซ้อน สมมติว่าเราจำเป็นต้องสร้างการดำเนินการทางคณิตศาสตร์หลายขั้นตอนโดยมีงานสามอย่าง ภารกิจที่ 1 และ ภารกิจที่ 2 ใช้อัลกอริทึมที่แตกต่างกันเพื่อค้นหาผลลัพธ์สำหรับขั้นตอนแรกและเรารู้ว่ามีเพียงหนึ่งในนั้นเท่านั้นที่จะทำงานได้ในขณะที่อีกขั้นจะล้มเหลว อย่างไรก็ตามการทำงานใดขึ้นอยู่กับข้อมูลอินพุตซึ่งเราไม่ทราบล่วงหน้า ผลลัพธ์จากงานเหล่านี้จะต้องรวมกับผลลัพธ์ของ งาน 3 . ดังนั้นเราต้องหาผลลัพธ์ของอย่างใดอย่างหนึ่ง งาน 1 หรือ ภารกิจที่ 2 , และ ผลของ งาน 3 . เพื่อให้บรรลุเป้าหมายนี้เราสามารถเขียนสิ่งนี้:
ไฟล์ส่วนหัว c ++ ตัวอย่าง
import static java.util.concurrent.CompletableFuture.*; ... Supplier task1 = (...) -> { ... // some complex calculation return 1; // example result }; Supplier task2 = (...) -> { ... // some complex calculation throw new RuntimeException(); // example exception }; Supplier task3 = (...) -> { ... // some complex calculation return 3; // example result }; supplyAsync(task1) // run task1 .applyToEither( // use whichever result is ready first, result of task1 or supplyAsync(task2), // result of task2 (Integer i) -> i) // return result as-is .thenCombine( // combine result supplyAsync(task3), // with result of task3 Integer::sum) // using summation .thenAccept(System.out::println); // print final result after execution
หากเราตรวจสอบว่า Java 8 จัดการกับสิ่งนี้อย่างไรเราจะเห็นว่าทั้งสามงานจะทำงานพร้อมกันแบบอะซิงโครนัส อย่างไรก็ตาม ภารกิจที่ 2 หากล้มเหลวโดยมีข้อยกเว้นผลลัพธ์สุดท้ายจะถูกคำนวณและพิมพ์สำเร็จ
CompletableFuture
ทำให้การสร้างงานแบบอะซิงโครนัสที่มีหลายขั้นตอนง่ายขึ้นมากและทำให้เรามีอินเทอร์เฟซที่ง่ายสำหรับการกำหนดสิ่งที่ควรดำเนินการเมื่อเสร็จสิ้นแต่ละขั้นตอน
ตามที่ระบุไว้โดย Java’s การรับเข้าของตัวเอง :
ก่อนรีลีส Java SE 8 กลไกวันที่และเวลาของ Java ได้รับการจัดเตรียมโดย
java.util.Date
,java.util.Calendar
และjava.util.TimeZone
คลาสเช่นเดียวกับคลาสย่อยเช่นjava.util.GregorianCalendar
ชั้นเรียนเหล่านี้มีข้อบกพร่องหลายประการ ได้แก่
- คลาสปฏิทินไม่ใช่ประเภทที่ปลอดภัย
- เนื่องจากคลาสนั้นไม่แน่นอนจึงไม่สามารถใช้ในแอพพลิเคชั่นมัลติเธรดได้
- ข้อบกพร่องในรหัสแอปพลิเคชันเป็นเรื่องปกติเนื่องจากจำนวนเดือนที่ผิดปกติและการขาดความปลอดภัยประเภท '
ในที่สุด Java 8 ก็แก้ปัญหาที่มีมายาวนานเหล่านี้ด้วย java.time
แพ็คเกจซึ่งมีคลาสสำหรับการทำงานกับวันที่และเวลา ทั้งหมดนี้ไม่เปลี่ยนรูปและมี API คล้ายกับเฟรมเวิร์กยอดนิยม Joda-Time ซึ่งนักพัฒนา Java เกือบทั้งหมดใช้ในแอปพลิเคชันของตนแทนที่จะเป็น Date
, Calendar
และ TimeZone
นี่คือคลาสที่มีประโยชน์บางส่วนในแพ็คเกจนี้:
Clock
- นาฬิกาบอกเวลาปัจจุบันรวมถึงปัจจุบันวันที่และเวลาพร้อมเขตเวลาDuration
และ Period
- ระยะเวลา Duration
ใช้ค่าตามเวลาเช่น“ 76.8 วินาทีและ Period
ตามวันที่เช่น“ 4 ปี 6 เดือนและ 12 วัน”Instant
- จุดในเวลาทันทีในหลายรูปแบบLocalDate
, LocalDateTime
, LocalTime
, Year
, YearMonth
- วันที่เวลาปีเดือนหรือบางอย่างรวมกันโดยไม่มีเขตเวลาในระบบปฏิทิน ISO-8601OffsetDateTime
, OffsetTime
- วันที่ - เวลาที่มีค่าชดเชยจาก UTC / Greenwich ในระบบปฏิทิน ISO-8601 เช่น“ 2015-08-29T14: 15: 30 + 01: 00 น.”ZonedDateTime
- วันที่ - เวลาพร้อมเขตเวลาที่เกี่ยวข้องในระบบปฏิทิน ISO-8601 เช่น“ 1986-08-29T10: 15: 30 + 01: 00 ยุโรป / ปารีส”
บางครั้งเราต้องหาวันที่สัมพันธ์กันเช่น“ วันอังคารแรกของเดือน” สำหรับกรณีเหล่านี้ java.time
จัดให้มีชั้นเรียนพิเศษ TemporalAdjuster
. TemporalAdjuster
คลาสประกอบด้วยชุดตัวปรับมาตรฐานซึ่งมีให้เป็นวิธีการแบบคงที่ สิ่งเหล่านี้ช่วยให้เราสามารถ:
นี่คือตัวอย่างสั้น ๆ ในการรับวันอังคารแรกของเดือน:
LocalDate getFirstTuesday(int year, int month) { return LocalDate.of(year, month, 1) .with(TemporalAdjusters.nextOrSame(DayOfWeek.TUESDAY)); }
ยังคงใช้ Java 7 อยู่หรือไม่? รับกับโปรแกรม! # Java8 ทวีต อย่างที่เราเห็น Java 8 เป็นแพลตฟอร์ม Java รุ่นแรก มีการเปลี่ยนแปลงภาษาจำนวนมากโดยเฉพาะอย่างยิ่งเมื่อมีการเปิดตัว lambdas ซึ่งแสดงถึงการย้ายเพื่อนำความสามารถในการเขียนโปรแกรมที่ใช้งานได้มากขึ้นมาสู่ Java Stream API เป็นตัวอย่างที่ดีว่า lambdas สามารถเปลี่ยนวิธีการทำงานกับเครื่องมือ Java มาตรฐานที่เราคุ้นเคยอยู่แล้วได้อย่างไร
นอกจากนี้ Java 8 ยังนำเสนอคุณสมบัติใหม่ ๆ สำหรับการทำงานกับไฟล์ การเขียนโปรแกรมแบบอะซิงโครนัส และการยกเครื่องเครื่องมือวันที่และเวลาที่จำเป็นมาก
การเปลี่ยนแปลงเหล่านี้ถือเป็นการก้าวไปข้างหน้าครั้งใหญ่สำหรับภาษา Java การพัฒนา Java น่าสนใจและมีประสิทธิภาพมากขึ้น