ใน Java 8 มีวิธีการใหม่String.chars()ที่ส่งกลับกระแสของints ( IntStream) ที่แสดงถึงรหัสตัวละคร ฉันเดาว่าหลายคนคงคาดหวังกระแสของcharที่นี่แทน อะไรคือแรงจูงใจในการออกแบบ API ด้วยวิธีนี้
ใน Java 8 มีวิธีการใหม่String.chars()ที่ส่งกลับกระแสของints ( 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ไม่มีอยู่สิ่งที่จะเป็นปัญหาในการเพิ่มหรือไม่