คุณสมบัติ Bytecode ไม่มีให้บริการในภาษา Java


146

ปัจจุบันมี (Java 6) สิ่งที่คุณสามารถทำได้ใน Java bytecode ที่คุณไม่สามารถทำได้จากในภาษา Java หรือไม่?

ฉันรู้ว่าทั้งทัวริงสมบูรณ์ดังนั้นอ่าน "สามารถทำได้" ในฐานะ "สามารถทำได้เร็วกว่า / ดีกว่าหรือในวิธีที่ต่างออกไป"

ฉันกำลังคิดว่าจะใช้โค้ดไบต์พิเศษinvokedynamicซึ่งไม่สามารถสร้างขึ้นได้โดยใช้ Java ยกเว้นว่าจะเป็นรุ่นเฉพาะสำหรับรุ่นในอนาคต


3
กำหนด "สิ่งของ" ในที่สุดภาษาจาวาและ Java bytecode นั้นก็เป็นทัวริงที่สมบูรณ์ ...
Michael Borgwardt

2
คำถามที่แท้จริงคือ; มีข้อได้เปรียบการเขียนโปรแกรมในรหัสไบต์เช่นการใช้ Jasmin แทน Java?
Peter Lawrey

2
เช่นเดียวกับrolในแอสเซมเบลอร์ซึ่งคุณไม่สามารถเขียนใน C ++
Martijn Courteaux

1
มันเป็นคอมไพเลอร์เพิ่มประสิทธิภาพที่ดีมากที่ไม่สามารถรวบรวม(x<<n)|(x>>(32-n))กับrolการเรียนการสอน
Random832

คำตอบ:


62

เท่าที่ฉันรู้ไม่มีคุณสมบัติที่สำคัญใน bytecodes รองรับ Java 6 ที่ยังไม่สามารถเข้าถึงได้จากซอร์สโค้ด Java เหตุผลหลักของเรื่องนี้คือการที่ Java bytecode ได้รับการออกแบบโดยคำนึงถึงภาษาจาวา

มีคุณสมบัติบางอย่างที่ไม่ได้ผลิตโดยคอมไพเลอร์ Java ที่ทันสมัยอย่างไรก็ตาม:

  • ACC_SUPERธง :

    นี่คือการตั้งค่าสถานะที่สามารถตั้งค่าในชั้นเรียนและระบุวิธีจัดการกรณีมุมที่เฉพาะเจาะจงของinvokespecialbytecode สำหรับคลาสนี้ มันถูกตั้งค่าโดยคอมไพเลอร์ Java ที่ทันสมัยทั้งหมด (โดยที่ "modern" คือ> = Java 1.1 หากฉันจำได้ถูกต้อง) และคอมไพเลอร์ Java โบราณเท่านั้นที่ผลิตไฟล์คลาสที่นี่ไม่ได้ตั้งค่าไว้ การตั้งค่าสถานะนี้มีไว้สำหรับเหตุผลที่เข้ากันได้ย้อนหลังเท่านั้น โปรดทราบว่าเริ่มต้นด้วย Java 7u51, ACC_SUPER จะถูกละเว้นอย่างสมบูรณ์เนื่องจากเหตุผลด้านความปลอดภัย

  • กระบวนการjsr/ retไบต์

    โค้ดไบต์เหล่านี้ถูกใช้เพื่อสร้างรูทีนย่อย (ส่วนใหญ่ใช้สำหรับการนำfinallyบล็อกไปใช้) พวกเขาจะไม่ได้ผลิตตั้งแต่ Java 6 เหตุผลของการคัดค้านคือพวกเขามีความซับซ้อนในการตรวจสอบแบบคงที่จำนวนมากเพื่อผลประโยชน์ที่ไม่ดี (เช่นรหัสที่ใช้สามารถนำกลับมาใช้ใหม่ได้ตลอดเวลาด้วยการกระโดดปกติที่มีค่าใช้จ่ายน้อยมาก)

  • มีสองวิธีในชั้นเรียนที่แตกต่างกันในประเภทผลตอบแทน

    ข้อกำหนดภาษา Java ไม่อนุญาตให้มีสองวิธีในระดับเดียวกันเมื่อพวกเขาแตกต่างกันเพียง แต่ในประเภทพวกเขากลับมา (เช่นชื่อเดียวกันรายการอาร์กิวเมนต์เดียวกัน ... ) อย่างไรก็ตามข้อกำหนด JVM ไม่มีข้อ จำกัด ดังกล่าวดังนั้นไฟล์คลาสสามารถมีสองวิธีดังกล่าวจึงไม่มีวิธีสร้างไฟล์คลาสดังกล่าวโดยใช้คอมไพเลอร์ Java ปกติ มีตัวอย่าง / คำอธิบายที่ดีในคำตอบนี้


5
ฉันสามารถเพิ่มคำตอบอื่นได้ แต่เราอาจทำให้คำตอบของคุณเป็นที่ยอมรับ คุณอาจต้องการที่จะพูดถึงว่าลายเซ็นวิธีของใน bytecode รวมถึงชนิดการส่งคืน นั่นคือคุณสามารถมีสองวิธีที่มีประเภทพารามิเตอร์เหมือนกันทุกประการ แต่มีประเภทส่งคืนที่แตกต่างกัน ดูการสนทนานี้: stackoverflow.com/questions/3110014/is-this-valid-java/ …
Adam Paynter

8
คุณสามารถมีคลาสคลาสชื่อเมธอดและฟิลด์ชื่อที่มีอักขระใดก็ได้ ฉันทำงานในโครงการหนึ่งที่ "ทุ่งนา" มีช่องว่างและยัติภังค์ในชื่อของพวกเขา : P
Peter Lawrey

3
@ ปีเตอร์: เมื่อพูดถึงตัวอักษรของระบบไฟล์ฉันพบว่าตัวจัดการ obfuscator เปลี่ยนชื่อคลาสเป็นaและอีกAอันเป็นไฟล์ JAR ฉันใช้เวลาประมาณครึ่งชั่วโมงของการคลายซิปบนเครื่อง Windowsก่อนที่ฉันจะรู้ว่าคลาสที่หายไปอยู่ที่ไหน :)
Adam Paynter

3
@JoachimSauer: ถอดความ JVM สเป็ค, หน้า 75: ชื่อชั้นวิธีการเขตข้อมูลและตัวแปรท้องถิ่นสามารถมีใด ๆของตัวละครยกเว้น'.', ';', หรือ'[' '/'ชื่อวิธีเหมือนกัน แต่พวกเขายังไม่สามารถมีหรือ'<' '>'(ด้วยข้อยกเว้นที่น่าสังเกตของ<init>และ<clinit>ตัวอย่างและตัวสร้างแบบสแตติก) ฉันควรชี้ให้เห็นว่าถ้าคุณทำตามสเปคอย่างเคร่งครัดชื่อคลาสจะถูก จำกัด มากขึ้นจริง ๆ แต่ไม่มีการบังคับใช้ข้อ จำกัด
leviathanbadger

3
@JoachimSauer: นอกจากนี้ยังไม่มีเอกสารของตัวเอง: ภาษา java รวมถึง"throws ex1, ex2, ..., exn"เป็นส่วนหนึ่งของลายเซ็นวิธีการ; คุณไม่สามารถเพิ่มข้อยกเว้นการขว้างปาประโยคลงในเมธอดที่แทนที่ได้ แต่ JVM ไม่สามารถดูแลน้อยลง ดังนั้นfinalวิธีการเท่านั้นที่รับประกันโดย JVM ที่จะไม่มีข้อยกเว้น - นอกเหนือจากRuntimeExceptions และErrors แน่นอน มีมากสำหรับการจัดการข้อยกเว้นที่ตรวจสอบแล้ว: D
leviathanbadger

401

หลังจากทำงานกับโค้ด 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เป็นวิธีการที่ไม่ใช่แบบส่วนตัวการโทรจะเป็นเสมือนเสมอ ด้วยรหัสไบต์ผู้ใช้สามารถกำหนดการเรียกใช้INVOKESPECIALopcode ซึ่งเชื่อมโยง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 ต้องการเงื่อนไขหลายประการที่จะต้องปฏิบัติตามเพื่อให้สามารถเรียกใช้เมธอดดีฟอลต์ได้:

  1. วิธีการนั้นจะต้องเป็นวิธีที่เฉพาะเจาะจงที่สุด (ต้องไม่ถูกเขียนทับโดยอินเตอร์เฟสย่อยที่ถูกนำไปใช้โดยชนิดใด ๆรวมถึงชนิดพิเศษ)
  2. ประเภทอินเตอร์เฟสของวิธีการเริ่มต้นจะต้องดำเนินการโดยตรงโดยชั้นเรียนที่เรียกวิธีการเริ่มต้น อย่างไรก็ตามหากอินเตอร์เฟส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และ RETJBC รู้ว่าที่อยู่ผู้ส่งเป็นประเภทของตนเองเพื่อจุดประสงค์นี้ อย่างไรก็ตามการใช้รูทีนย่อยทำให้การวิเคราะห์รหัสคงที่ซับซ้อนเกินไปซึ่งเป็นสาเหตุที่ไม่ได้ใช้คำสั่งเหล่านี้อีกต่อไป คอมไพเลอร์ Java จะทำซ้ำโค้ดที่คอมไพล์แทน อย่างไรก็ตามนี่เป็นการสร้างตรรกะที่เหมือนกันซึ่งเป็นเหตุผลว่าทำไมฉันจึงไม่คิดที่จะทำสิ่งที่แตกต่าง ในทำนองเดียวกันคุณสามารถเพิ่มตัวอย่างเช่นNOOPการเรียนการสอนรหัสไบต์ซึ่งไม่ได้ใช้โดยคอมไพเลอร์ Java อย่างใดอย่างหนึ่ง แต่นี่จะไม่อนุญาตให้คุณได้รับสิ่งใหม่ ๆ ดังที่อธิบายไว้ในบริบทนี้ "คำแนะนำคุณลักษณะ" ที่กล่าวถึงเหล่านี้จะถูกลบออกจากชุดของตัวเลือกรหัสทางกฎหมายซึ่งจะทำให้คุณลักษณะเหล่านั้นมีน้อย


3
เกี่ยวกับชื่อเมธอดคุณสามารถมีได้มากกว่าหนึ่ง<clinit>วิธีโดยการกำหนดเมธอดที่มีชื่อ<clinit>แต่ยอมรับพารามิเตอร์หรือมีvoidชนิดที่ไม่ส่งคืน แต่วิธีการเหล่านี้ไม่มีประโยชน์มาก JVM จะเพิกเฉยและรหัสไบต์ไม่สามารถเรียกใช้ได้ การใช้งานเพียงอย่างเดียวคือทำให้ผู้อ่านเกิดความสับสน
Holger

2
ฉันเพิ่งค้นพบว่า JVM ของ Oracle ตรวจพบจอมอนิเตอร์ที่ยังไม่เผยแพร่ที่วิธีการออกและส่งIllegalMonitorStateExceptionหากคุณไม่ได้ทำตามmonitorexitคำแนะนำ และในกรณีที่การออกจากวิธีการพิเศษที่ล้มเหลวในการทำ a monitorexitก็เป็นการรีเซ็ตจอภาพแบบเงียบ ๆ
Holger

1
@ โฮลเกอร์ - ไม่ทราบว่าฉันรู้ว่านี่เป็นไปได้ใน JVM ก่อนหน้านี้อย่างน้อย JRockit ยังมีผู้จัดการของตัวเองสำหรับการใช้งานประเภทนี้ ฉันจะอัพเดทรายการ
Rafael Winterhalter

1
ข้อกำหนดของ JVM นั้นไม่ได้เป็นพฤติกรรมดังกล่าว ฉันเพิ่งค้นพบมันเพราะฉันพยายามสร้างการล็อคที่แท้จริงโดยใช้รหัสไบต์ที่ไม่ได้มาตรฐาน
Holger

3
ตกลงฉันพบข้อมูลจำเพาะที่เกี่ยวข้อง :“ การล็อกที่มีโครงสร้างคือสถานการณ์เมื่อในระหว่างการเรียกใช้เมธอดทุกครั้งที่ออกบนจอภาพที่กำหนดจะตรงกับรายการก่อนหน้าบนจอภาพนั้น เนื่องจากไม่มีความมั่นใจว่ารหัสทั้งหมดที่ส่งไปยัง Java Virtual Machine จะทำการล็อคแบบโครงสร้างการอนุญาตให้ใช้งาน Java Virtual Machine จะได้รับอนุญาต แต่ไม่จำเป็นต้องบังคับใช้กฎทั้งสองต่อไปนี้เพื่อรับประกันการล็อคแบบมีโครงสร้าง …”
โฮลเจอร์

14

นี่คือคุณสมบัติบางอย่างที่สามารถทำได้ใน Java bytecode แต่ไม่ได้อยู่ในซอร์สโค้ด Java:

  • โยนข้อยกเว้นที่ตรวจสอบจากวิธีการโดยไม่ต้องประกาศว่าวิธีการโยน ข้อยกเว้นที่ตรวจสอบและไม่ถูกตรวจสอบเป็นสิ่งที่ถูกตรวจสอบโดยคอมไพเลอร์ Java เท่านั้นไม่ใช่ JVM ด้วยเหตุนี้เช่น Scala สามารถโยนข้อยกเว้นที่ตรวจสอบจากวิธีการโดยไม่ต้องประกาศ แม้ว่าจะมี generics Java มีวิธีแก้ปัญหาที่เรียกว่าโยนส่อเสียด

  • มีสองวิธีในชั้นเรียนเท่านั้นที่แตกต่างกันในประเภทผลตอบแทนดังกล่าวแล้วในคำตอบของโจอาคิมของ : สเปคภาษา Java ไม่อนุญาตให้มีสองวิธีในระดับเดียวกันเมื่อพวกเขาแตกต่างกันเพียงในรูปแบบการกลับมาของพวกเขา (เช่นชื่อเดียวกันรายการอาร์กิวเมนต์เดียวกัน ... ) อย่างไรก็ตามข้อกำหนด JVM ไม่มีข้อ จำกัด ดังกล่าวดังนั้นไฟล์คลาสสามารถมีสองวิธีดังกล่าวจึงไม่มีวิธีสร้างไฟล์คลาสดังกล่าวโดยใช้คอมไพเลอร์ Java ปกติ มีตัวอย่าง / คำอธิบายที่ดีในคำตอบนี้


4
ทราบว่ามีเป็นวิธีที่จะทำในสิ่งที่เป็นครั้งแรกในจาวา มันเป็นบางครั้งเรียกว่าโยนส่อเสียด
Joachim Sauer

ตอนนี้ลับ ๆ ล่อ ๆ ! : D ขอบคุณสำหรับการแบ่งปัน
Esko Luontola

ฉันคิดว่าคุณยังสามารถใช้Thread.stop(Throwable)ในการโยนส่อเสียด ฉันคิดว่าเชื่อมโยงแล้วเร็วกว่า
Bart van Heukelom

2
คุณไม่สามารถสร้างอินสแตนซ์โดยไม่ต้องเรียก constructor ใน Java bytecode ตัวตรวจสอบจะปฏิเสธรหัสใด ๆ ที่พยายามใช้อินสแตนซ์ที่ไม่มีการกำหนดค่าเริ่มต้น การใช้งานการดีซีเรียลไลซ์ชันของวัตถุใช้ตัวช่วยสร้างรหัสพื้นเมืองสำหรับการสร้างอินสแตนซ์ที่ไม่มีการเรียกคอนสตรัค
Holger

สำหรับคลาสที่ขยายวัตถุ Foo คุณไม่สามารถสร้างอินสแตนซ์ Foo ได้โดยการเรียกตัวสร้างที่ประกาศใน Object ผู้ตรวจสอบจะปฏิเสธ คุณสามารถสร้างคอนสตรัคเตอร์ดังกล่าวโดยใช้ ReflectionFactory ของ Java แต่สิ่งนี้แทบจะไม่เป็นคุณลักษณะโค้ดไบต์ แต่ Jni รับรู้ได้ คำตอบของคุณไม่ถูกต้องและ Holger นั้นถูกต้อง
Rafael Winterhalter

8
  • GOTOสามารถใช้กับป้ายกำกับเพื่อสร้างโครงสร้างการควบคุมของคุณเอง (นอกเหนือจากfor whileอื่น ๆ )
  • คุณสามารถแทนที่thisตัวแปรโลคัลภายในเมธอด
  • รวมทั้งสองเหล่านี้คุณสามารถสร้างสร้างสายหางเพิ่มประสิทธิภาพ bytecode (ฉันทำเช่นนี้ในJCompilo )

ในฐานะที่เกี่ยวข้องคุณจะได้รับชื่อพารามิเตอร์สำหรับวิธีการหากคอมไพล์ด้วย debug ( Paranamer ทำสิ่งนี้โดยการอ่าน bytecode


คุณoverrideเป็นตัวแปรท้องถิ่นนี้ได้อย่างไร?
Michael

2
@Michael การเอาชนะเป็นคำที่แข็งแกร่งเกินไป ในระดับ bytecode ตัวแปรโลคัลทั้งหมดถูกเข้าถึงโดยดัชนีตัวเลขและไม่มีความแตกต่างระหว่างการเขียนไปยังตัวแปรที่มีอยู่หรือการกำหนดค่าเริ่มต้นตัวแปรใหม่ thisตัวแปรมีดัชนี zero แต่นอกเหนือจากการถูกก่อนเริ่มต้นด้วยthisการอ้างอิงเมื่อเข้าสู่วิธีการเช่นนั้นจะเป็นเพียงตัวแปรท้องถิ่น ดังนั้นคุณสามารถเขียนค่าที่แตกต่างกันไปซึ่งสามารถทำหน้าที่เหมือนสิ้นสุดthisขอบเขตหรือเปลี่ยนthisตัวแปรขึ้นอยู่กับวิธีที่คุณใช้
Holger

ฉันเห็น! จริงๆแล้วมันthisสามารถกำหนดใหม่ได้? ฉันคิดว่ามันเป็นเพียงคำแทนที่ซึ่งทำให้ฉันสงสัยว่ามันหมายถึงอะไรกันแน่
Michael

5

บางทีส่วน 7A ในเอกสารนี้เป็นที่น่าสนใจแม้ว่ามันจะเป็นเรื่องของ bytecode ข้อผิดพลาดมากกว่า bytecode คุณสมบัติ


การอ่านที่น่าสนใจ แต่ดูเหมือนว่าไม่มีใครต้องการ (ab) ใช้สิ่งเหล่านี้
Bart van Heukelom

4

ในภาษาจาวาคำสั่งแรกในตัวสร้างจะต้องเรียกไปยังตัวสร้างระดับซุปเปอร์ Bytecode ไม่มีข้อ จำกัด นี้แทนกฎคือ super constructor หรือ Constructor อื่นในคลาสเดียวกันต้องถูกเรียกใช้สำหรับอ็อบเจกต์ก่อนที่จะเข้าถึงสมาชิก สิ่งนี้จะช่วยให้มีอิสระมากขึ้นเช่น:

ฉันยังไม่ได้ทดสอบสิ่งเหล่านี้ดังนั้นโปรดแก้ไขให้ฉันถ้าฉันผิด


คุณสามารถตั้งค่าสมาชิกของอินสแตนซ์ก่อนเรียก superclass constructor การอ่านช่องหรือวิธีการโทรไม่สามารถทำได้ก่อนหน้านั้น
Rafael Winterhalter

3

สิ่งที่คุณสามารถทำได้ด้วยโค้ดไบต์แทนที่จะเป็นโค้ด Java ธรรมดาคือสร้างโค้ดที่สามารถโหลดและรันได้โดยไม่ต้องมีคอมไพเลอร์ ระบบจำนวนมากมี JRE มากกว่า JDK และหากคุณต้องการสร้างรหัสแบบไดนามิกมันอาจจะดีกว่าถ้าไม่ง่ายกว่าในการสร้างรหัสไบต์แทนรหัส Java จะต้องรวบรวมก่อนจึงจะสามารถใช้งานได้


6
แต่คุณเพียงแค่ข้ามคอมไพเลอร์ไม่ใช่ผลิตสิ่งที่ไม่สามารถผลิตได้โดยใช้คอมไพเลอร์ (ถ้ามี)
Bart van Heukelom

2

ฉันเขียนเครื่องมือเพิ่มประสิทธิภาพ bytecode เมื่อฉันเป็น I-Play (มันถูกออกแบบมาเพื่อลดขนาดรหัสสำหรับแอปพลิเคชัน J2ME) ฟีเจอร์หนึ่งที่ฉันเพิ่มคือความสามารถในการใช้อินไลน์ไบต์ (คล้ายกับภาษาแอสเซมบลีแบบอินไลน์ใน C ++) ฉันจัดการเพื่อลดขนาดของฟังก์ชั่นที่เป็นส่วนหนึ่งของวิธีการห้องสมุดโดยใช้คำสั่ง DUP เนื่องจากฉันต้องการค่าสองครั้ง ฉันยังมีศูนย์คำแนะนำไบต์ (ถ้าคุณกำลังเรียกวิธีการที่ใช้ถ่านและคุณต้องการที่จะผ่าน int ที่คุณรู้ว่าไม่จำเป็นต้องถูกโยนฉันเพิ่ม int2char (var) เพื่อแทนที่ char (var) และมันจะลบ คำสั่ง i2c เพื่อลดขนาดของรหัสฉันยังทำให้มันลอย a = 2.3; float b = 3.4; float c = a + b และที่จะถูกแปลงเป็นจุดคงที่ (เร็วขึ้นและ J2ME บางคนไม่ได้ สนับสนุนจุดลอย)


2

ใน Java หากคุณพยายามแทนที่วิธีสาธารณะด้วยวิธีการป้องกัน (หรือการลดการเข้าถึงอื่น ๆ ) คุณจะได้รับข้อผิดพลาด: "พยายามกำหนดสิทธิ์การเข้าถึงที่อ่อนแอกว่า" ถ้าคุณทำกับ JVM bytecode ตัวตรวจสอบก็ใช้ได้ดีและคุณสามารถเรียกวิธีการเหล่านี้ผ่านคลาสผู้ปกครองราวกับว่าพวกเขาเป็นแบบสาธารณะ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.