Java 8 แนะนำคุณสมบัติภาษาใหม่ที่สำคัญ เช่น นิพจน์แลมบ์ดา
การเปลี่ยนแปลงเหล่านี้ในภาษามาพร้อมกับการเปลี่ยนแปลงที่สำคัญในโค้ดไบต์ที่คอมไพล์แล้วซึ่งจะป้องกันไม่ให้ทำงานบนเครื่องเสมือน Java 7 โดยไม่ต้องใช้ retrotranslator หรือไม่?
Java 8 แนะนำคุณสมบัติภาษาใหม่ที่สำคัญ เช่น นิพจน์แลมบ์ดา
การเปลี่ยนแปลงเหล่านี้ในภาษามาพร้อมกับการเปลี่ยนแปลงที่สำคัญในโค้ดไบต์ที่คอมไพล์แล้วซึ่งจะป้องกันไม่ให้ทำงานบนเครื่องเสมือน Java 7 โดยไม่ต้องใช้ retrotranslator หรือไม่?
ไม่ การใช้คุณสมบัติ 1.8 ในซอร์สโค้ดของคุณจำเป็นต้องกำหนดเป้าหมาย 1.8 VM ฉันเพิ่งลองใช้ Java 8 รีลีสใหม่และลองคอมไพล์ด้วย -target 1.7 -source 1.8
และคอมไพเลอร์ปฏิเสธ:
$ javac Test -source 1.8 -target 1.7
javac: source release 1.8 requires target release 1.8
วิธีการเริ่มต้นจำเป็นต้องมีการเปลี่ยนแปลงใน bytecode และ JVM ซึ่งเป็นไปไม่ได้ที่จะทำบน Java 7 ตัวตรวจสอบ bytecode ของ Java 7 และต่ำกว่าจะปฏิเสธอินเทอร์เฟซที่มีเนื้อความของวิธีการ (ยกเว้นวิธีการเริ่มต้นแบบคงที่) การพยายามจำลองวิธีการเริ่มต้นด้วยวิธีการคงที่บนฝั่งผู้เรียกจะไม่ให้ผลลัพธ์เดียวกัน เนื่องจากวิธีการเริ่มต้นสามารถแทนที่ในคลาสย่อยได้ Retrolambda มีการรองรับที่จำกัดสำหรับวิธีการเริ่มต้นของแบ็คพอร์ต แต่ไม่สามารถแบ็คพอร์ตได้อย่างสมบูรณ์ เนื่องจากต้องใช้คุณลักษณะ JVM ใหม่อย่างแท้จริง
Lambdas สามารถทำงานบน Java 7 ได้ตามปกติ หากมีคลาส API ที่จำเป็นอยู่ที่นั่น คำสั่ง invokedynamic มีอยู่ใน Java 7 แต่เป็นไปได้ที่จะนำ lambdas ไปใช้เพื่อสร้างคลาส lambda ณ เวลาคอมไพล์ (JDK 8 รุ่นแรกๆ บิวด์ทำแบบนั้น) ซึ่งในกรณีนี้มันจะทำงานบน Java เวอร์ชันใดก็ได้ (Oracle ตัดสินใจใช้ invivodynamic สำหรับ lambdas เพื่อการพิสูจน์อักษรในอนาคต บางทีวันหนึ่ง JVM จะมีฟังก์ชันระดับเฟิร์สคลาส ดังนั้นจึงสามารถเปลี่ยน intakedynamic เพื่อใช้ฟังก์ชันเหล่านี้แทนการสร้างคลาสสำหรับ lambda ทุกตัว ซึ่งจะช่วยปรับปรุงประสิทธิภาพได้) สิ่งที่ Retrolambda ทำคือ ว่ามันประมวลผลคำสั่งการเรียกใช้แบบไดนามิกทั้งหมดและแทนที่ด้วยคลาสที่ไม่ระบุชื่อ เช่นเดียวกับสิ่งที่ Java 8 ทำตอนรันไทม์เมื่อมีการเรียก lamba inurgedynamic ในครั้งแรก
คำอธิบายประกอบซ้ำเป็นเพียงน้ำตาลเชิงวากยสัมพันธ์ เป็นรหัสไบต์ที่เข้ากันได้กับเวอร์ชันก่อนหน้า ใน Java 7 คุณจะต้องใช้วิธีการช่วยเหลือตัวเอง (เช่น getAnnotationsByType) ซึ่งซ่อนรายละเอียดการใช้งานของคำอธิบายประกอบคอนเทนเนอร์ซึ่งมีคำอธิบายประกอบที่ซ้ำกัน
AFAIK คำอธิบายประกอบประเภทจะมีอยู่เฉพาะในเวลาคอมไพล์เท่านั้น ดังนั้นจึงไม่ควรกำหนดให้มีการเปลี่ยนแปลงโค้ดไบต์ ดังนั้นเพียงแค่เปลี่ยนหมายเลขเวอร์ชัน bytecode ของคลาสที่คอมไพล์ Java 8 ก็เพียงพอแล้วที่จะทำให้คลาสเหล่านั้นทำงานบน Java 7 ได้
ชื่อพารามิเตอร์เมธอด มีอยู่ใน bytecode ด้วย Java 7 ดังนั้นจึงเข้ากันได้ด้วย คุณสามารถเข้าถึงได้โดยการอ่านโค้ดไบต์ของเมธอด และดูที่ชื่อตัวแปรในเครื่องในข้อมูลการดีบักของเมธอด ตัวอย่างเช่น Spring Framework ทำอย่างนั้นเพื่อใช้ @PathVariable ดังนั้นจึงอาจมีวิธีไลบรารีที่คุณสามารถเรียกใช้ได้ เนื่องจากวิธีการอินเทอร์เฟซแบบนามธรรมไม่มีเนื้อหาของวิธีการ ข้อมูลการดีบักนั้นจึงไม่มีอยู่สำหรับวิธีอินเทอร์เฟซใน Java 7 และ AFAIK บน Java 8 เช่นกัน
คุณสมบัติใหม่อื่นๆ ส่วนใหญ่เป็น API ใหม่ การปรับปรุง HotSpot และเครื่องมือ . API ใหม่บางส่วนมีให้บริการเป็นไลบรารีของบุคคลที่สาม (เช่น ThreeTen-Backport และ streamsupport)
สรุป สรุป วิธีการเริ่มต้นจำเป็นต้องมีคุณสมบัติ JVM ใหม่ แต่คุณสมบัติภาษาอื่นไม่ต้องการ หากต้องการใช้ คุณจะต้องคอมไพล์โค้ดใน Java 8 จากนั้นแปลงโค้ดไบต์ด้วย Retrolambda เป็น Java 5 /6/7 รูปแบบ อย่างน้อยที่สุด เวอร์ชันของโค้ดไบต์จะต้องมีการเปลี่ยนแปลง และ javac ไม่อนุญาต -source 1.8 -target 1.7
ดังนั้นจึงจำเป็นต้องมีตัวแปลย้อนหลัง
เท่าที่ฉันรู้ไม่มีการเปลี่ยนแปลงใด ๆ ใน JDK 8 ที่จำเป็นต้องเพิ่มรหัสไบต์ใหม่ เครื่องมือแลมบ์ดาบางส่วนกำลังดำเนินการโดยใช้ invokeDynamic
(ซึ่งมีอยู่แล้วใน JDK 7) ดังนั้น จากมุมมองของชุดคำสั่ง JVM ไม่มีอะไรที่จะทำให้ codebase เข้ากันไม่ได้ แม้ว่าจะมี API จำนวนมากที่เกี่ยวข้องและการปรับปรุงคอมไพเลอร์ที่อาจทำให้โค้ดจาก JDK 8 ยากต่อการคอมไพล์/รันภายใต้ JDK ก่อนหน้า (แต่ฉันไม่ได้ลองสิ่งนี้)
บางทีเอกสารอ้างอิงต่อไปนี้อาจช่วยเพิ่มความเข้าใจเกี่ยวกับวิธีการควบคุมการเปลี่ยนแปลงที่เกี่ยวข้องกับแลมบ์ดาได้
สิ่งเหล่านี้จะอธิบายรายละเอียดว่าสิ่งต่าง ๆ อยู่ภายใต้ประทุนอย่างไร บางทีคุณอาจพบคำตอบสำหรับคำถามของคุณที่นั่น
class C extends A with B
ถูกนำไปใช้กับอินเทอร์เฟซปกติ A
และ B
และคลาสที่แสดงร่วมกัน A$class
และ B$class
คลาส C
เพียงส่งต่อวิธีการไปยังคลาสสหายแบบคงที่ ไม่มีการบังคับใช้ประเภทตนเองเลย lambdas จะถูกแปลงตามเวลาคอมไพล์เป็นคลาสภายในแบบนามธรรม ดังนั้นนิพจน์ new D with A with B
ก็เช่นกัน การจับคู่รูปแบบเป็นกลุ่มของโครงสร้าง if-else การคืนสินค้านอกพื้นที่? กลไกลองจับจากแลมบ์ดา มีอะไรเหลือบ้างไหม? (ที่น่าสนใจ scalac ของฉันบอกว่า 1.6 เป็นค่าเริ่มต้น)
- person Adowrath; 08.04.2017
trait A { def foo: Int = 12 }
และ class C extends A
คุณจะได้: interface A { int foo(); }
, class A$class { public static int foo(A $this) { return 12; } }
และ class C implements A { public int foo() { return A$class.foo(this); } }
ดังนั้นการนำเมธอดเริ่มต้นไปใช้งานจะถูกย้ายไปยังคลาส X$class
และในแต่ละคลาสของการนำไปใช้ของคุณลักษณะ คอมไพลเลอร์จะสร้างเมธอด stub ที่เพิ่งส่งต่อไปยังเมธอดใน X$class
โดยให้ this
เป็นอาร์กิวเมนต์ (เพราะถ้า X.foo
ถูกเรียก X.bar
) .
- person Adowrath; 07.09.2017
java.util.Function
และ scala.Function1
ต่างก็ได้รับการจัดการเหมือนกัน และ a => a + 1
สามารถคอมไพล์ได้ทั้งสองอย่าง) การคอมไพล์โดยใช้ java.lang.invoke.LambdaMetaFactory
- person Adowrath; 08.10.2017
-target:jvm-1.7
ได้ แต่ฉันต้องลอง) 2.11 และต่ำกว่า? ใช่. (ถึงแม้จะคิดว่าเป็น Java 6 ขึ้นไปมานานแล้ว แต่ก็ไม่รู้ว่าจะแม่นหรือเปล่า)
- person Adowrath; 09.10.2017
หากคุณยินดีที่จะใช้ "ตัวแปลย้อนยุค" ลองใช้ Retrolambda ที่ยอดเยี่ยมของ Esko Luontola: https://github.com/orfjackal/retrolambda
คุณสามารถทำ -source 1.7 -target 1.7
จากนั้นมันจะคอมไพล์ แต่มันจะไม่คอมไพล์หากคุณมีคุณสมบัติเฉพาะของ Java 8 เช่นแลมบ์ดา
-source 1.7
จะไม่บินไป
- person toolforger; 28.01.2019