หลังจากทำงานกับโค้ด Java byte มาระยะหนึ่งแล้วทำการวิจัยเพิ่มเติมเกี่ยวกับเรื่องนี้นี่เป็นบทสรุปของการค้นพบของฉัน:
เรียกใช้งานรหัสในตัวสร้างก่อนเรียก super constructor หรือตัวสร้างเสริม
ใน Java programming language (JPL) คำสั่งแรกของนวกรรมิกจะต้องเป็นการเรียกใช้ super constructor หรือนวกรรมิกอื่นของคลาสเดียวกัน สิ่งนี้ไม่เป็นจริงสำหรับรหัส Java byte (JBC) ภายในโค้ดไบต์มันถูกต้องตามกฎหมายที่จะรันโค้ดใด ๆ ต่อหน้าตัวสร้างตราบใดที่:
- ตัวสร้างที่เข้ากันได้อื่นถูกเรียกในบางครั้งหลังจากบล็อกรหัสนี้
- การโทรนี้ไม่ได้อยู่ในคำสั่งที่มีเงื่อนไข
- ก่อนการเรียก Constructor นี้จะไม่มีการอ่านฟิลด์ของอินสแตนซ์ที่สร้างและไม่มีวิธีการใดที่เรียกใช้ สิ่งนี้แสดงถึงรายการถัดไป
ตั้งค่าฟิลด์อินสแตนซ์ก่อนเรียก super constructor หรือตัวสร้างเสริม
ดังที่ได้กล่าวมาแล้วมันถูกกฎหมายอย่างสมบูรณ์ในการตั้งค่าฟิลด์ของอินสแตนซ์ก่อนที่จะเรียกคอนสตรัคเตอร์อื่น แม้จะมีแฮ็กแบบดั้งเดิมซึ่งทำให้สามารถใช้ประโยชน์จาก "คุณสมบัติ" นี้ในเวอร์ชัน Java ก่อน 6:
class Foo {
public String s;
public Foo() {
System.out.println(s);
}
}
class Bar extends Foo {
public Bar() {
this(s = "Hello World!");
}
private Bar(String helper) {
super();
}
}
วิธีนี้สามารถตั้งค่าฟิลด์ก่อนที่จะเรียกใช้ตัวสร้าง super ซึ่งเป็นไปไม่ได้อีกต่อไป ใน JBC พฤติกรรมนี้ยังสามารถใช้ได้
แยกการเรียก super constructor
ในจาวามันเป็นไปไม่ได้ที่จะกำหนดสายเรียกการก่อสร้าง
class Foo {
Foo() { }
Foo(Void v) { }
}
class Bar() {
if(System.currentTimeMillis() % 2 == 0) {
super();
} else {
super(null);
}
}
จนถึง Java 7u23 ตัวตรวจสอบของ HotSpot VM ทำพลาดการตรวจสอบนี้ซึ่งเป็นสาเหตุที่เป็นไปได้ เครื่องมือนี้ถูกใช้โดยเครื่องมือสร้างรหัสหลายแบบเพื่อการแฮ็ก แต่ก็ไม่ถูกกฎหมายที่จะใช้คลาสเช่นนี้อีกต่อไป
หลังเป็นเพียงข้อบกพร่องในรุ่นคอมไพเลอร์นี้ ในเวอร์ชั่นคอมไพเลอร์ใหม่กว่านี้เป็นไปได้อีกครั้ง
กำหนดคลาสโดยไม่มีตัวสร้างใด ๆ
คอมไพเลอร์ Java จะใช้ตัวสร้างอย่างน้อยหนึ่งตัวสำหรับคลาสใด ๆ ในโค้ด Java byte ไม่จำเป็นต้องใช้ สิ่งนี้ช่วยให้การสร้างคลาสที่ไม่สามารถสร้างได้แม้ว่าจะใช้การสะท้อนกลับ อย่างไรก็ตามการใช้sun.misc.Unsafe
ยังอนุญาตให้มีการสร้างอินสแตนซ์ดังกล่าว
กำหนดวิธีการที่มีลายเซ็นเหมือนกัน แต่มีประเภทผลตอบแทนต่างกัน
ใน JPL วิธีการจะถูกระบุว่าไม่ซ้ำกันโดยชื่อและประเภทของพารามิเตอร์ดิบ ใน JBC จะมีการพิจารณาประเภทผลตอบแทนดิบเพิ่มเติมด้วย
กำหนดเขตข้อมูลที่ไม่แตกต่างกันตามชื่อ แต่ตามประเภท
ไฟล์คลาสสามารถมีหลายฟิลด์ในชื่อเดียวกันตราบใดที่พวกเขาประกาศประเภทของฟิลด์ที่แตกต่างกัน JVM อ้างถึงฟิลด์เป็น tuple ของชื่อและชนิดเสมอ
โยนข้อยกเว้นที่ตรวจสอบไม่ได้ประกาศโดยไม่ต้องจับพวกมัน
Java runtime และโค้ด Java byte ไม่ทราบถึงแนวคิดของข้อยกเว้นที่ตรวจสอบ มันเป็นเพียงคอมไพเลอร์ Java ที่ตรวจสอบว่าข้อยกเว้นที่ตรวจสอบมักจะถูกจับหรือประกาศว่าพวกเขาจะถูกโยน
ใช้การเรียกใช้เมธอดไดนามิกนอกการแสดงออกแลมบ์ดา
การเรียกใช้เมธอดไดนามิกที่เรียกว่าสามารถใช้กับทุกสิ่งไม่เพียง แต่สำหรับแลมบ์ดานิพจน์ของ Java การใช้คุณสมบัตินี้อนุญาตให้ตัวอย่างเช่นเปลี่ยนตรรกะการดำเนินการที่รันไทม์ ภาษาโปรแกรมพลวัตหลายภาษาที่ใช้ใน JBC พัฒนาประสิทธิภาพโดยใช้คำสั่งนี้ ในโค้ด Java byte คุณสามารถเลียนแบบแลมบ์ดานิพจน์ใน Java 7 ที่คอมไพเลอร์ยังไม่อนุญาตให้ใช้วิธีการเรียกใช้แบบไดนามิกในขณะที่ JVM เข้าใจคำสั่งแล้ว
ใช้ตัวระบุที่ไม่ปกติถือว่าถูกกฎหมาย
เคยลองใช้ช่องว่างและตัวแบ่งบรรทัดในชื่อวิธีการของคุณหรือไม่? สร้าง JBC ของคุณเองและขอให้โชคดีในการตรวจสอบโค้ด เพียงตัวอักษรผิดกฎหมายสำหรับตัวระบุมี.
, ;
, และ[
/
นอกจากนี้วิธีการที่ไม่ได้ตั้งชื่อ<init>
หรือ<clinit>
ไม่สามารถมีและ<
>
กำหนดfinal
พารามิเตอร์หรือการthis
อ้างอิงอีกครั้ง
final
พารามิเตอร์ไม่มีอยู่ใน JBC และสามารถกำหนดใหม่ได้ พารามิเตอร์ใด ๆ รวมถึงการthis
อ้างอิงจะถูกเก็บไว้ในอาเรย์ง่ายๆภายใน JVM สิ่งที่อนุญาตให้กำหนดการthis
อ้างอิงที่ดัชนี0
ภายในกรอบวิธีการเดียว
มอบหมายfinal
ฟิลด์ใหม่
ตราบใดที่เขตข้อมูลสุดท้ายได้รับการกำหนดภายในนวกรรมิกมันเป็นเรื่องถูกกฎหมายที่จะกำหนดค่านี้อีกครั้งหรือไม่ได้กำหนดค่าเลย ดังนั้นตัวสร้างสองตัวต่อไปนี้จึงถูกกฎหมาย:
class Foo {
final int bar;
Foo() { } // bar == 0
Foo(Void v) { // bar == 2
bar = 1;
bar = 2;
}
}
สำหรับstatic final
เขตข้อมูลก็ยังได้รับอนุญาตให้มอบหมายเขตข้อมูลที่อยู่นอกตัวเริ่มต้นของชั้นเรียน
รักษาคอนสตรัคเตอร์และคลาสเริ่มต้นราวกับว่าพวกเขาเป็นวิธีการ
นี่เป็นคุณลักษณะเชิงแนวคิดมากกว่าแต่ตัวสร้างจะไม่ได้รับการปฏิบัติแตกต่างกันภายใน JBC มากกว่าวิธีการทั่วไป มันเป็นเพียงเครื่องตรวจสอบของ JVM ที่รับรองว่าผู้สร้างจะเรียกผู้สร้างตามกฎหมายคนอื่น อื่น ๆ กว่าที่มันเป็นเพียงการตั้งชื่อ Java ที่ก่อสร้างจะต้องเรียกว่าและว่าการเริ่มต้นระดับที่เรียกว่า<init>
<clinit>
นอกเหนือจากความแตกต่างนี้การเป็นตัวแทนของวิธีการและการก่อสร้างเป็นเหมือนกัน ดังที่โฮลเกอร์ชี้ให้เห็นในความคิดเห็นคุณสามารถกำหนดตัวสร้างด้วยประเภทส่งคืนที่ไม่ใช่void
หรือตัวเริ่มต้นคลาสที่มีอาร์กิวเมนต์แม้ว่ามันจะเป็นไปไม่ได้ที่จะเรียกวิธีการเหล่านี้
สร้างระเบียนไม่สมมาตร *
เมื่อสร้างบันทึก
record Foo(Object bar) { }
javac จะสร้างไฟล์ที่เรียนกับเขตข้อมูลเดียวชื่อbar
วิธีการเข้าถึงชื่อและตัวสร้างการเป็นหนึ่งเดียวbar()
Object
นอกจากนี้ยังมีการbar
เพิ่มคุณสมบัติบันทึกสำหรับ โดยการสร้างเรกคอร์ดด้วยตนเองมันเป็นไปได้ที่จะสร้างรูปร่างคอนสตรัคเตอร์ที่แตกต่างกันเพื่อข้ามเขตข้อมูลและการใช้ accessor ที่แตกต่างกัน ในเวลาเดียวกันก็ยังคงเป็นไปได้ที่จะทำให้การสะท้อน API เชื่อว่าคลาสเป็นตัวแทนของบันทึกจริง
เรียกเมธอด super ใด ๆ (จนถึง Java 1.1)
อย่างไรก็ตามนี่เป็นไปได้สำหรับ Java เวอร์ชัน 1 และ 1.1 เท่านั้น ใน JBC มีการจัดส่งวิธีการในประเภทเป้าหมายที่ชัดเจนเสมอ ซึ่งหมายความว่าสำหรับ
class Foo {
void baz() { System.out.println("Foo"); }
}
class Bar extends Foo {
@Override
void baz() { System.out.println("Bar"); }
}
class Qux extends Bar {
@Override
void baz() { System.out.println("Qux"); }
}
มันเป็นไปได้ที่จะใช้Qux#baz
ในการเรียกในขณะที่กระโดดข้ามFoo#baz
Bar#baz
ในขณะที่ยังคงเป็นไปได้ที่จะกำหนดการเรียกที่ชัดเจนเพื่อเรียกใช้การดำเนินการตามวิธีการพิเศษอื่น ๆ ของชั้นซุปเปอร์โดยตรง แต่ไม่มีผลใด ๆ ในรุ่น Java หลังจาก 1.1 ใน Java 1.1 ลักษณะการทำงานนี้ถูกควบคุมโดยการตั้งค่าACC_SUPER
สถานะซึ่งจะเปิดใช้งานลักษณะการทำงานเดียวกันที่เรียกเฉพาะการใช้งานของซูเปอร์คลาสโดยตรงเท่านั้น
กำหนดการโทรที่ไม่ใช่เสมือนของเมธอดที่ประกาศไว้ในคลาสเดียวกัน
ใน Java ไม่สามารถกำหนดคลาสได้
class Foo {
void foo() {
bar();
}
void bar() { }
}
class Bar extends Foo {
@Override void bar() {
throw new RuntimeException();
}
}
รหัสดังกล่าวจะส่งผลให้RuntimeException
เมื่อมีการเรียกใช้อินสแตนซ์ของfoo
Bar
มันเป็นไปไม่ได้ที่จะกำหนดFoo::foo
วิธีการที่จะเรียกของตัวเอง วิธีการที่กำหนดไว้ในbar
Foo
เนื่องจากbar
เป็นวิธีการที่ไม่ใช่แบบส่วนตัวการโทรจะเป็นเสมือนเสมอ ด้วยรหัสไบต์ผู้ใช้สามารถกำหนดการเรียกใช้INVOKESPECIAL
opcode ซึ่งเชื่อมโยงbar
การเรียกใช้เมธอดเข้าFoo::foo
กับFoo
เวอร์ชันของโดยตรง โดยทั่วไป opcode นี้จะใช้ในการใช้การเรียกใช้เมธอดขั้นสูง แต่คุณสามารถนำ opcode กลับมาใช้ใหม่เพื่อใช้งานตามที่อธิบายไว้
คำอธิบายประกอบประเภทเม็ดละเอียด
ใน Java คำอธิบายประกอบจะถูกนำไปใช้ตาม@Target
ที่คำอธิบายประกอบประกาศไว้ การใช้การจัดการโค้ดไบต์คุณสามารถกำหนดคำอธิบายประกอบอย่างอิสระของตัวควบคุมนี้ นอกจากนี้ยังเป็นตัวอย่างที่เป็นไปได้ในการเพิ่มความคิดเห็นชนิดพารามิเตอร์โดยไม่ต้องเพิ่มความคิดเห็นพารามิเตอร์แม้ว่า@Target
คำอธิบายประกอบจะใช้กับทั้งสององค์ประกอบ
กำหนดแอตทริบิวต์สำหรับประเภทหรือสมาชิก
ภายในภาษา Java มันเป็นไปได้เฉพาะในการกำหนดคำอธิบายประกอบสำหรับเขตข้อมูลวิธีการหรือชั้นเรียน ใน JBC คุณสามารถฝังข้อมูลใด ๆ ลงในคลาส Java ได้ ในการใช้ประโยชน์จากข้อมูลนี้คุณไม่สามารถพึ่งพากลไกการโหลดคลาส Java ได้อีกต่อไป แต่คุณต้องแยกข้อมูลเมตาด้วยตัวเอง
มากเกินและกำหนดโดยปริยายbyte
, short
, char
และboolean
ค่า
ชนิดดั้งเดิมดั้งเดิมไม่เป็นที่รู้จักใน JBC ปกติ แต่มีการกำหนดไว้สำหรับประเภทอาร์เรย์หรือสำหรับฟิลด์และตัวอธิบายวิธีเท่านั้น ภายในคำแนะนำรหัสไบต์ทุกประเภทที่มีชื่อใช้เวลาบิตพื้นที่ 32 int
ซึ่งจะช่วยให้ตัวแทนของพวกเขาเป็น อย่างเป็นทางการเท่านั้นint
, float
, long
และdouble
ประเภทที่มีอยู่ภายในรหัสไบต์ซึ่งทุกคนต้องแปลงอย่างชัดเจนตามกฎของการตรวจสอบ JVM ที่ของ
ไม่ปล่อยจอภาพ
synchronized
บล็อกจะทำจริงขึ้นจากสองงบหนึ่งที่จะได้รับและเป็นหนึ่งที่จะปล่อยจอภาพ ใน JBC คุณสามารถรับได้โดยไม่ต้องเปิดตัว
หมายเหตุ : ในการนำไปใช้งานล่าสุดของ HotSpot สิ่งนี้จะนำไปสู่การIllegalMonitorStateException
สิ้นสุดของเมธอดหรือการรีลีสโดยปริยายหากเมธอดถูกยกเลิกโดยข้อยกเว้น
เพิ่มreturn
คำสั่งมากกว่าหนึ่งคำในประเภท initializer
ใน Java แม้กระทั่ง initializer ประเภทเล็กน้อยเช่น
class Foo {
static {
return;
}
}
ผิดกฎหมาย ในรหัสไบต์, initializer ประเภทได้รับการปฏิบัติเช่นเดียวกับวิธีอื่น ๆ เช่นงบกลับสามารถกำหนดได้ทุกที่
สร้างลูปลดไม่ได้
คอมไพเลอร์ Java จะแปลงลูปเป็นคำสั่ง goto ในโค้ด Java byte คำสั่งดังกล่าวสามารถใช้ในการสร้างลูปลดไม่ได้ซึ่งคอมไพเลอร์ Java ไม่เคยทำ
กำหนดบล็อก catch แบบเรียกซ้ำ
ในโค้ด Java byte คุณสามารถกำหนดบล็อก:
try {
throw new Exception();
} catch (Exception e) {
<goto on exception>
throw Exception();
}
คำสั่งที่คล้ายกันจะถูกสร้างขึ้นโดยนัยเมื่อใช้synchronized
บล็อกใน Java ที่มีข้อยกเว้นใด ๆ ในขณะที่การปล่อยจอภาพกลับสู่คำสั่งสำหรับการปล่อยจอภาพนี้ โดยปกติจะไม่มีข้อยกเว้นเกิดขึ้นกับคำแนะนำดังกล่าว แต่ถ้ามันจะ (เช่นเลิกใช้แล้วThreadDeath
) จอภาพจะยังคงได้รับการปล่อยตัว
เรียกวิธีการเริ่มต้นใด ๆ
คอมไพเลอร์ Java ต้องการเงื่อนไขหลายประการที่จะต้องปฏิบัติตามเพื่อให้สามารถเรียกใช้เมธอดดีฟอลต์ได้:
- วิธีการนั้นจะต้องเป็นวิธีที่เฉพาะเจาะจงที่สุด (ต้องไม่ถูกเขียนทับโดยอินเตอร์เฟสย่อยที่ถูกนำไปใช้โดยชนิดใด ๆรวมถึงชนิดพิเศษ)
- ประเภทอินเตอร์เฟสของวิธีการเริ่มต้นจะต้องดำเนินการโดยตรงโดยชั้นเรียนที่เรียกวิธีการเริ่มต้น อย่างไรก็ตามหากอินเตอร์เฟส
B
ขยายอินเตอร์เฟสA
แต่ไม่ได้แทนที่เมธอดในA
เมธอดนั้นยังคงสามารถเรียกใช้เมธอดได้
สำหรับโค้ด Java byte จะนับเงื่อนไขที่สองเท่านั้น อย่างไรก็ตามอันแรกนั้นไม่เกี่ยวข้องเลย
เรียกใช้เมธอด super บนอินสแตนซ์ที่ไม่ใช่ this
คอมไพเลอร์ Java เพียง แต่ช่วยให้การเรียกซุปเปอร์ (หรือเริ่มต้นอินเตอร์เฟซ) this
วิธีการในกรณีของ ในโค้ดไบต์มันเป็นไปได้ที่จะเรียกใช้เมธอด super บนอินสแตนซ์ของชนิดเดียวกันที่คล้ายกับต่อไปนี้:
class Foo {
void m(Foo f) {
f.super.toString(); // calls Object::toString
}
public String toString() {
return "foo";
}
}
เข้าถึงสมาชิกสังเคราะห์
ในโค้ด Java byte เป็นไปได้ที่จะเข้าถึงสมาชิกสังเคราะห์โดยตรง ตัวอย่างเช่นพิจารณาวิธีในตัวอย่างต่อไปนี้เข้าถึงอินสแตนซ์ด้านนอกของBar
อินสแตนซ์อื่น:
class Foo {
class Bar {
void bar(Bar bar) {
Foo foo = bar.Foo.this;
}
}
}
นี่เป็นเรื่องจริงสำหรับฟิลด์สังเคราะห์คลาสหรือวิธีการใด ๆ
กำหนดข้อมูลประเภททั่วไปที่ไม่ซิงค์กัน
ในขณะที่ Java runtime ไม่ประมวลผลประเภททั่วไป (หลังจากคอมไพเลอร์ Java ใช้การลบประเภท) ข้อมูลนี้ยังคงเชื่อมโยงกับคลาสที่คอมไพล์เป็นข้อมูลเมตาและทำให้สามารถเข้าถึงได้ผ่านทาง API การสะท้อนกลับ
ตัวตรวจสอบไม่ได้ตรวจสอบความสอดคล้องกันของString
ค่าที่เข้ารหัสด้วยข้อมูลเมตาเหล่านี้ ดังนั้นจึงเป็นไปได้ที่จะกำหนดข้อมูลเกี่ยวกับประเภททั่วไปที่ไม่ตรงกับการลบ การยืนยันดังต่อไปนี้อาจเป็นจริง:
Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());
Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);
นอกจากนี้ลายเซ็นสามารถกำหนดเป็นไม่ถูกต้องเช่นว่ามีข้อยกเว้นรันไทม์ ข้อยกเว้นนี้จะถูกโยนทิ้งเมื่อเข้าถึงข้อมูลเป็นครั้งแรกเนื่องจากมีการประเมินอย่างเกียจคร้าน (คล้ายกับค่าคำอธิบายประกอบที่มีข้อผิดพลาด)
ผนวกข้อมูลเมตาของพารามิเตอร์สำหรับวิธีการบางอย่างเท่านั้น
คอมไพเลอร์ Java อนุญาตให้ฝังชื่อพารามิเตอร์และข้อมูลตัวดัดแปลงเมื่อรวบรวมคลาสที่parameter
เปิดใช้งานแฟล็ก ในรูปแบบไฟล์คลาส Java ข้อมูลนี้จะถูกจัดเก็บตามวิธีการสิ่งที่ทำให้เป็นไปได้เพียงฝังข้อมูลวิธีดังกล่าวสำหรับวิธีการบางอย่าง
เลอะสิ่งต่างๆและทำให้ JVM ของคุณเกิดข้อผิดพลาด
ตัวอย่างเช่นในโค้ด Java byte คุณสามารถกำหนดให้เรียกใช้เมธอดใด ๆ กับชนิดใดก็ได้ โดยปกติแล้วตัวตรวจสอบจะบ่นหากประเภทไม่รู้จักวิธีดังกล่าว อย่างไรก็ตามหากคุณเรียกใช้เมธอดที่ไม่รู้จักในอาร์เรย์ฉันพบข้อบกพร่องในเวอร์ชัน JVM บางส่วนซึ่งตัวตรวจสอบจะพลาดสิ่งนี้และ JVM ของคุณจะเสร็จสิ้นเมื่อคำสั่งถูกเรียกใช้ นี่เป็นคุณสมบัติที่แทบจะไม่ แต่มันเป็นสิ่งที่ไม่สามารถทำได้ด้วยjavac ที่คอมไพล์ด้วย Java Java มีการตรวจสอบสองประเภท การตรวจสอบครั้งแรกถูกนำไปใช้โดยคอมไพเลอร์ Java ส่วนที่สองโดย JVM เมื่อโหลดคลาส โดยการข้ามคอมไพเลอร์คุณอาจพบจุดอ่อนในการตรวจสอบของผู้ตรวจสอบ นี่เป็นคำสั่งทั่วไปมากกว่าคุณลักษณะ
ใส่คำอธิบายประเภทผู้รับของคอนสตรัคเตอร์เมื่อไม่มีคลาสภายนอก
ตั้งแต่ Java 8 วิธีการไม่คงที่และตัวสร้างของคลาสภายในสามารถประกาศประเภทผู้รับและใส่คำอธิบายประกอบประเภทเหล่านี้ ตัวสร้างของคลาสระดับบนสุดไม่สามารถใส่คำอธิบายประกอบชนิดของผู้รับเนื่องจากพวกเขาส่วนใหญ่ไม่ประกาศ
class Foo {
class Bar {
Bar(@TypeAnnotation Foo Foo.this) { }
}
Foo() { } // Must not declare a receiver type
}
Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
อย่างไรก็ตามเนื่องจากส่งคืนการAnnotatedType
แสดงFoo
จึงมีความเป็นไปได้ที่จะรวมคำอธิบายประกอบประเภทสำหรับตัวFoo
สร้างของโดยตรงในไฟล์คลาสที่คำอธิบายประกอบเหล่านี้อ่านได้ในภายหลังโดย API การสะท้อนกลับ
ใช้คำแนะนำรหัสไบต์ที่ไม่ได้ใช้ / เก่า
เนื่องจากคนอื่นตั้งชื่อฉันจะรวมไว้ด้วย Java เคยใช้รูทีนย่อยโดยคำสั่งJSR
และ RET
JBC รู้ว่าที่อยู่ผู้ส่งเป็นประเภทของตนเองเพื่อจุดประสงค์นี้ อย่างไรก็ตามการใช้รูทีนย่อยทำให้การวิเคราะห์รหัสคงที่ซับซ้อนเกินไปซึ่งเป็นสาเหตุที่ไม่ได้ใช้คำสั่งเหล่านี้อีกต่อไป คอมไพเลอร์ Java จะทำซ้ำโค้ดที่คอมไพล์แทน อย่างไรก็ตามนี่เป็นการสร้างตรรกะที่เหมือนกันซึ่งเป็นเหตุผลว่าทำไมฉันจึงไม่คิดที่จะทำสิ่งที่แตกต่าง ในทำนองเดียวกันคุณสามารถเพิ่มตัวอย่างเช่นNOOP
การเรียนการสอนรหัสไบต์ซึ่งไม่ได้ใช้โดยคอมไพเลอร์ Java อย่างใดอย่างหนึ่ง แต่นี่จะไม่อนุญาตให้คุณได้รับสิ่งใหม่ ๆ ดังที่อธิบายไว้ในบริบทนี้ "คำแนะนำคุณลักษณะ" ที่กล่าวถึงเหล่านี้จะถูกลบออกจากชุดของตัวเลือกรหัสทางกฎหมายซึ่งจะทำให้คุณลักษณะเหล่านั้นมีน้อย