ใน Java 8 มีวิธีการใหม่String.chars()
ที่ส่งกลับกระแสของint
s ( IntStream
) ที่แสดงถึงรหัสตัวละคร ฉันเดาว่าหลายคนคงคาดหวังกระแสของchar
ที่นี่แทน อะไรคือแรงจูงใจในการออกแบบ API ด้วยวิธีนี้
ใน Java 8 มีวิธีการใหม่String.chars()
ที่ส่งกลับกระแสของint
s ( IntStream
) ที่แสดงถึงรหัสตัวละคร ฉันเดาว่าหลายคนคงคาดหวังกระแสของchar
ที่นี่แทน อะไรคือแรงจูงใจในการออกแบบ API ด้วยวิธีนี้
คำตอบ:
ดังที่คนอื่น ๆ ได้กล่าวไปแล้วการตัดสินใจออกแบบด้านหลังนี้ก็เพื่อป้องกันการระเบิดของวิธีการและชั้นเรียน
ถึงกระนั้นโดยส่วนตัวฉันคิดว่านี่เป็นการตัดสินใจที่แย่มากและควรมีเพราะพวกเขาไม่ต้องการที่จะทำCharStream
ซึ่งมีเหตุผลและวิธีการที่แตกต่างกันแทนchars()
ฉันจะคิดว่า:
Stream<Character> chars()
ที่ให้กระแสของตัวละครกล่องซึ่งจะมีโทษประสิทธิภาพแสงบางอย่างIntStream unboxedChars()
ซึ่งจะใช้สำหรับรหัสประสิทธิภาพอย่างไรก็ตามแทนที่จะมุ่งเน้นไปที่สาเหตุที่ทำแบบนี้ในปัจจุบันฉันคิดว่าคำตอบนี้ควรมุ่งเน้นไปที่การแสดงวิธีการทำกับ API ที่เราได้รับกับ Java 8
ใน Java 7 ฉันจะทำแบบนี้:
for (int i = 0; i < hello.length(); i++) {
System.out.println(hello.charAt(i));
}
และฉันคิดว่าวิธีการที่สมเหตุสมผลใน Java 8 มีดังต่อไปนี้:
hello.chars()
.mapToObj(i -> (char)i)
.forEach(System.out::println);
ที่นี่ฉันได้รับIntStream
และแมปไปยังวัตถุผ่านแลมบ์ดาi -> (char)i
ซึ่งจะทำกล่องให้โดยอัตโนมัติStream<Character>
และจากนั้นเราสามารถทำสิ่งที่เราต้องการและยังคงใช้วิธีการอ้างอิงเป็นข้อดี
ระวังแม้ว่าคุณจะต้องทำmapToObj
ถ้าคุณลืมและใช้map
แล้วจะไม่มีอะไรบ่น แต่คุณจะยังคงจบลงด้วยการIntStream
และคุณอาจจะสงสัยว่าทำไมมันพิมพ์ค่าจำนวนเต็มแทนสตริงที่เป็นตัวแทนของตัวละคร
ทางเลือกที่น่าเกลียดอื่น ๆ สำหรับ Java 8:
เมื่ออยู่ในIntStream
และต้องการพิมพ์ในที่สุดคุณจะไม่สามารถใช้วิธีการอ้างอิงอีกต่อไปสำหรับการพิมพ์:
hello.chars()
.forEach(i -> System.out.println((char)i));
ยิ่งกว่านั้นการใช้วิธีการอ้างอิงถึงวิธีการของคุณเองไม่ทำงานอีกต่อไป! พิจารณาสิ่งต่อไปนี้:
private void print(char c) {
System.out.println(c);
}
แล้ว
hello.chars()
.forEach(this::print);
สิ่งนี้จะทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากอาจมีการแปลงแบบเสีย
สรุป:
API ที่ได้รับการออกแบบด้วยวิธีนี้เพราะไม่ต้องการที่จะเพิ่มCharStream
ผมเองคิดว่าวิธีการที่จะกลับมาStream<Character>
และการแก้ปัญหาในขณะนี้คือการใช้mapToObj(i -> (char)i)
บนIntStream
เพื่อให้สามารถทำงานได้อย่างถูกต้องกับพวกเขา
codePoints()
แทนchars()
และคุณจะพบมากของฟังก์ชั่นห้องสมุดแล้วยอมรับint
สำหรับจุดรหัสนอกจากนี้ยังchar
เช่นวิธีการทั้งหมดของjava.lang.Character
เช่นเดียวกับStringBuilder.appendCodePoint
ฯลฯ jdk1.5
การสนับสนุนนี้มีอยู่ตั้งแต่
String
char[]
ฉันพนันได้เลยว่าchar
การประมวลผลรหัสส่วนใหญ่ผิดพลาดคู่แทน
void print(int ch) { System.out.println((char)ch); }
และจากนั้นคุณสามารถใช้การอ้างอิงวิธีการ
Stream<Character>
ถูกปฏิเสธ
คำตอบจาก skiwiปกคลุมหลายจุดสำคัญแล้ว ฉันจะเติมพื้นหลังอีกเล็กน้อย
การออกแบบของ API ใด ๆ คือชุดของการแลกเปลี่ยน ใน Java ปัญหาหนึ่งที่ยากคือการตัดสินใจเกี่ยวกับการออกแบบที่ทำมานานแล้ว
ดั่งเดิมมาแล้วใน Java ตั้งแต่ 1.0 พวกเขาทำให้ Java เป็นภาษาเชิงวัตถุที่ "ไม่บริสุทธิ์" เนื่องจาก primitives ไม่ใช่วัตถุ ฉันเชื่อว่าการเพิ่มเข้ามาของพื้นฐานคือการตัดสินใจอย่างจริงจังในการปรับปรุงประสิทธิภาพโดยเสียค่าใช้จ่ายของความบริสุทธิ์เชิงวัตถุ
นี่คือการแลกเปลี่ยนที่เรายังคงอยู่กับวันนี้เกือบ 20 ปีต่อมา ฟีเจอร์ autoboxing ที่เพิ่มเข้ามาใน Java 5 ส่วนใหญ่ไม่จำเป็นต้องถ่วงซอร์สโค้ดด้วยการชกมวยและการเรียกเมธอด unboxing แต่โอเวอร์เฮดยังคงอยู่ที่นั่น ในหลายกรณีไม่สามารถสังเกตได้ อย่างไรก็ตามหากคุณต้องทำการชกมวยหรือ unboxing ภายในลูปด้านในคุณจะเห็นว่ามันสามารถกำหนดซีพียูและค่าใช้จ่ายในการรวบรวมขยะจำนวนมากได้
เมื่อออกแบบ Streams API เป็นที่ชัดเจนว่าเราต้องให้การสนับสนุนแบบดั้งเดิม ค่าใช้จ่ายมวย / unboxing จะฆ่าผลประโยชน์ใด ๆ จากการขนาน เราไม่ต้องการสนับสนุนการใช้งานดั้งเดิมทั้งหมดเนื่องจากจะเพิ่มความยุ่งเหยิงจำนวนมากให้กับ API (คุณเห็นการใช้งานจริงShortStream
หรือไม่?) "ทั้งหมด" หรือ "ไม่มี" เป็นสถานที่ที่สะดวกสบายสำหรับการออกแบบ แต่ก็ไม่เป็นที่ยอมรับ ดังนั้นเราต้องหาค่าที่สมเหตุสมผลของ "บางอย่าง" เราจบลงด้วยการที่มีความเชี่ยวชาญดั้งเดิมสำหรับint
, และlong
double
(โดยส่วนตัวแล้วฉันจะออกไปint
แต่นั่นเป็นเพียงฉัน)
สำหรับCharSequence.chars()
เราถือว่าการกลับมาStream<Character>
(ต้นแบบต้นอาจนำมาใช้นี้) แต่มันถูกปฏิเสธเพราะค่าใช้จ่ายในการชกมวย เมื่อพิจารณาว่าสายอักขระมีchar
ค่าเป็นพื้นฐานมันก็ดูเหมือนจะเป็นความผิดพลาดที่จะกำหนดมวยโดยไม่มีเงื่อนไขเมื่อผู้โทรอาจจะทำการประมวลผลเล็กน้อยเกี่ยวกับค่าและ unbox กลับเข้าไปในสตริง
นอกจากนี้เรายังพิจารณาถึงCharStream
ความเชี่ยวชาญดั้งเดิม แต่ดูเหมือนว่าการใช้งานจะค่อนข้างแคบเมื่อเทียบกับจำนวนมากที่จะเพิ่มใน API ดูเหมือนว่าจะไม่คุ้มที่จะเพิ่ม
บทลงโทษนี้จะเรียกผู้เรียกว่าพวกเขาต้องรู้ว่าค่าที่IntStream
บรรจุchar
เป็นตัวแทนints
และการคัดเลือกนั้นจะต้องทำในสถานที่ที่เหมาะสม สิ่งนี้ทำให้เกิดความสับสนเป็นสองเท่าเนื่องจากมีการเรียก API มากเกินไปPrintStream.print(char)
และมีความPrintStream.print(int)
แตกต่างอย่างชัดเจนในพฤติกรรมของพวกเขา อาจมีความสับสนเกิดขึ้นเนื่องจากการcodePoints()
โทรกลับมาIntStream
แต่ค่าที่มีอยู่นั้นค่อนข้างแตกต่างกัน
ดังนั้นสิ่งนี้จึงทำให้คุณเลือกใช้งานได้ในหลายทางเลือก:
เราไม่สามารถให้บริการเฉพาะทางแบบดั้งเดิมส่งผลให้ API ที่เรียบง่ายสง่างามและสอดคล้องกัน แต่กำหนดให้มีประสิทธิภาพสูงและค่าใช้จ่าย GC;
เราสามารถจัดเตรียมความเชี่ยวชาญเฉพาะทางแบบครบวงจรในราคาที่ทำให้เกิดความยุ่งเหยิงของ API และการกำหนดภาระการบำรุงรักษาให้กับนักพัฒนา JDK หรือ
เราสามารถจัดหาชุดย่อยของความเชี่ยวชาญดั้งเดิมให้ขนาด API ที่มีประสิทธิภาพสูงปานกลางซึ่งกำหนดภาระค่อนข้างน้อยสำหรับผู้โทรในกรณีการใช้งานที่ค่อนข้าง จำกัด (การประมวลผลถ่าน)
เราเลือกอันสุดท้าย
chars()
หนึ่งที่ผลตอบแทนStream<Character>
(มีโทษประสิทธิภาพเล็ก) และความเป็นอยู่อื่น ๆ ที่IntStream
ได้รับนี้ถือว่ายัง? มีโอกาสมากที่ผู้คนจะจบลงด้วยการแมปกับมันStream<Character>
หากพวกเขาคิดว่าความเชื่อมั่นนั้นคุ้มค่ากับโทษปรับประสิทธิภาพ
chars()
วิธีการที่ส่งคืนค่าถ่านในตัวIntStream
มันจะไม่เพิ่มการเรียก API อีกครั้งที่ได้รับค่าเดียวกัน แต่อยู่ในรูปแบบกล่อง ผู้เรียกใช้สามารถใส่ค่าได้โดยไม่มีปัญหา แน่นอนว่ามันจะสะดวกกว่าที่ไม่ต้องทำในกรณีนี้ (อาจเป็นของหายาก) แต่มีค่าใช้จ่ายในการเพิ่มความยุ่งเหยิงให้กับ API
chars()
กลับมาIntStream
ไม่ใช่ปัญหาใหญ่โดยเฉพาะอย่างยิ่งเนื่องจากความจริงที่ว่าวิธีนี้มันไม่ค่อยได้ใช้เลย แต่มันจะดีที่จะมีวิธีในตัวการแปลงกลับไปIntStream
String
มันสามารถทำได้ด้วย.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
แต่มันยาวจริงๆ
collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
มันไม่ได้เลวร้ายเกินไป: ฉันเดาว่ามันไม่ได้สั้นกว่านี้จริง ๆ แต่การใช้จุดรหัสจะหลีกเลี่ยงการ(char)
ปลดเปลื้องและอนุญาตให้ใช้การอ้างอิงวิธีการ รวมทั้งจัดการกับตัวแทนเสมือนอย่างถูกต้อง
IntStream
ไม่ได้มีวิธีการที่ใช้collect()
Collector
พวกเขามีเพียงสามcollect()
วิธีหาเรื่องตามที่ระบุไว้ในความคิดเห็นก่อนหน้า
CharStream
ไม่มีอยู่สิ่งที่จะเป็นปัญหาในการเพิ่มหรือไม่