काफी समय से जावा बाइट कोड के साथ काम करने और इस मामले पर कुछ अतिरिक्त शोध करने के बाद, यहाँ मेरे निष्कर्षों का सारांश है:
सुपर कंस्ट्रक्टर या सहायक कंस्ट्रक्टर को कॉल करने से पहले एक कंस्ट्रक्टर में कोड निष्पादित करें
जावा प्रोग्रामिंग लैंग्वेज (JPL) में, एक कंस्ट्रक्टर का पहला स्टेटमेंट एक सुपर कंस्ट्रक्टर या उसी क्लास के दूसरे कंस्ट्रक्टर का इनवोकेशन होना चाहिए। यह जावा बाइट कोड (JBC) के लिए सही नहीं है। बाइट कोड के भीतर, किसी भी कोड को कंस्ट्रक्टर से पहले निष्पादित करना बिल्कुल वैध है, जब तक कि:
- इस कोड ब्लॉक के कुछ समय बाद एक और संगत कंस्ट्रक्टर को बुलाया जाता है।
- यह कॉल सशर्त विवरण के भीतर नहीं है।
- इस निर्माता के कॉल से पहले, निर्मित इंस्टेंस का कोई भी क्षेत्र पढ़ा नहीं जाता है और इसके किसी भी तरीके को लागू नहीं किया जाता है। यह अगले आइटम का तात्पर्य है।
सुपर कंस्ट्रक्टर या सहायक कंस्ट्रक्टर को कॉल करने से पहले उदाहरण फ़ील्ड सेट करें
जैसा कि पहले उल्लेख किया गया है, किसी अन्य कंस्ट्रक्टर को कॉल करने से पहले एक इंस्टेंस का फ़ील्ड मान सेट करना पूरी तरह से कानूनी है। यहां तक कि एक विरासत हैक भी मौजूद है, जो 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();
}
}
इस तरह, सुपर कंस्ट्रक्टर को लागू करने से पहले एक क्षेत्र निर्धारित किया जा सकता है जो अब संभव नहीं है। जेबीसी में, यह व्यवहार अभी भी लागू किया जा सकता है।
सुपर कंस्ट्रक्टर कॉल को ब्रांच करें
जावा में, एक कंस्ट्रक्टर कॉल को परिभाषित करना संभव नहीं है
class Foo {
Foo() { }
Foo(Void v) { }
}
class Bar() {
if(System.currentTimeMillis() % 2 == 0) {
super();
} else {
super(null);
}
}
जावा 7u23 तक, हॉटस्पॉट वीएम के सत्यापनकर्ता ने इस जांच को याद नहीं किया जो कि यह संभव था। इसे कई कोड जनरेशन टूल्स ने हैक की तरह इस्तेमाल किया था लेकिन इस तरह की क्लास को लागू करना अब कानूनी नहीं है।
उत्तरार्द्ध इस संकलक संस्करण में केवल एक बग था। नए संकलक संस्करणों में, यह फिर से संभव है।
किसी भी निर्माता के बिना एक वर्ग को परिभाषित करें
जावा कंपाइलर हमेशा किसी भी वर्ग के लिए कम से कम एक कंस्ट्रक्टर को लागू करेगा। जावा बाइट कोड में, यह आवश्यक नहीं है। यह उन कक्षाओं के निर्माण की अनुमति देता है जिनका निर्माण प्रतिबिंब का उपयोग करते समय भी नहीं किया जा सकता है। हालाँकि, sun.misc.Unsafe
अभी भी इस तरह के उदाहरणों के निर्माण की अनुमति है।
समान हस्ताक्षर के साथ तरीकों को परिभाषित करें लेकिन विभिन्न प्रकार के रिटर्न के साथ
JPL में, एक विधि की पहचान उसके नाम और उसके कच्चे पैरामीटर प्रकारों के रूप में की जाती है। जेबीसी में, कच्चे रिटर्न प्रकार को अतिरिक्त रूप से माना जाता है।
उन क्षेत्रों को परिभाषित करें जो केवल नाम से नहीं बल्कि केवल प्रकार से भिन्न होते हैं
एक वर्ग फ़ाइल में एक ही नाम के कई फ़ील्ड शामिल हो सकते हैं जब तक कि वे एक अलग फ़ील्ड प्रकार की घोषणा नहीं करते हैं। जेवीएम हमेशा एक क्षेत्र को नाम और प्रकार के टपल के रूप में संदर्भित करता है।
उन्हें पकड़ने के बिना अघोषित जाँच अपवाद फेंको
जावा रनटाइम और जावा बाइट कोड चेक किए गए अपवादों की अवधारणा से अवगत नहीं हैं। यह केवल जावा कंपाइलर है जो सत्यापित करता है कि चेक किए गए अपवाद हमेशा पकड़े जाते हैं या घोषित किए जाते हैं यदि उन्हें फेंक दिया जाता है।
लैम्ब्डा एक्सप्रेशन के बाहर डायनामिक मेथड इनवोकेशन का उपयोग करें
तथाकथित गतिशील विधि मंगलाचरण का उपयोग किसी भी चीज के लिए किया जा सकता है, न केवल जावा के लैम्ब्डा अभिव्यक्तियों के लिए। इस सुविधा का उपयोग उदाहरण के लिए रनटाइम पर निष्पादन तर्क को स्विच करने की अनुमति देता है। कई गतिशील प्रोग्रामिंग भाषाएं जो जेबीसी के लिए उबालती हैं उन्होंने इस निर्देश का उपयोग करके अपने प्रदर्शन में सुधार किया। जावा बाइट कोड में, आप जावा 7 में लैम्ब्डा एक्सप्रेशंस का अनुकरण भी कर सकते हैं, जहां कंपाइलर ने अभी तक डायनेमिक विधि के किसी भी उपयोग के लिए अनुमति नहीं दी थी, जबकि जेवीएम ने निर्देश को समझा था।
ऐसे पहचानकर्ताओं का उपयोग करें जिन्हें आमतौर पर कानूनी नहीं माना जाता है
कभी रिक्त स्थान और अपने तरीके के नाम में एक लाइन ब्रेक का उपयोग कर? कोड समीक्षा के लिए अपनी खुद की JBC और शुभकामनाएँ बनाएँ। पहचानकर्ता के लिए केवल अवैध वर्ण मौजूद हैं .
, ;
, [
और /
। साथ ही, तरीकों नामित कि नहीं कर रहे हैं <init>
या <clinit>
नहीं हो सकते <
और >
।
पुन: असाइन किए गए final
पैरामीटर या this
संदर्भ
final
जेबीसी में पैरामीटर मौजूद नहीं हैं और फलस्वरूप इसे फिर से असाइन किया जा सकता है। this
संदर्भ सहित किसी भी पैरामीटर को केवल जेवीएम के भीतर एक सरल सरणी में संग्रहीत किया जाता है जो एक एकल विधि फ्रेम के भीतर this
सूचकांक में संदर्भ को फिर से असाइन करने की अनुमति देता है 0
।
पुन: असाइन किए गए final
फ़ील्ड
जब तक एक निर्माता के भीतर एक अंतिम क्षेत्र सौंपा जाता है, तब तक इस मूल्य को फिर से असाइन करना कानूनी है या यहां तक कि एक मूल्य भी निर्दिष्ट नहीं करना है। इसलिए, निम्नलिखित दो निर्माता कानूनी हैं:
class Foo {
final int bar;
Foo() { } // bar == 0
Foo(Void v) { // bar == 2
bar = 1;
bar = 2;
}
}
के लिए static final
खेतों, तो यह और भी वर्ग प्रारंभकर्ता के बाहर खेतों पुन: असाइन करने की अनुमति है।
कंस्ट्रक्टर और क्लास इनिशियलाइज़र के साथ ऐसा व्यवहार करें जैसे कि वे विधियाँ हों
यह एक वैचारिक विशेषता है, लेकिन कंस्ट्रक्टरों को सामान्य तरीकों की तुलना में जेबीसी के भीतर किसी भी तरह का व्यवहार नहीं किया जाता है। यह केवल जेवीएम का सत्यापनकर्ता है जो विश्वास दिलाता है कि निर्माता दूसरे कानूनी निर्माता को बुलाते हैं। इसके अलावा, यह केवल एक जावा नामकरण सम्मेलन है जिसे कंस्ट्रक्टरों को बुलाया जाना चाहिए <init>
और क्लास इनिशियलाइज़र कहा जाता है <clinit>
। इस अंतर के अलावा, तरीकों और निर्माणकर्ताओं का प्रतिनिधित्व समान है। जैसा कि होल्गर ने एक टिप्पणी में कहा, आप कंस्ट्रक्टरों को अन्य प्रकार के रिटर्न के साथ void
या क्लास इनिशियलाइज़र के साथ तर्कों के साथ परिभाषित कर सकते हैं , भले ही इन तरीकों को कॉल करना संभव न हो।
असममित रिकॉर्ड बनाएं * ।
रिकॉर्ड बनाते समय
record Foo(Object bar) { }
javac एक एकल नाम के साथ एक वर्ग फ़ाइल bar
, एक अभिगम विधि नाम bar()
और एक एकल लेने वाला एक निर्माणकर्ता उत्पन्न करेगा Object
। इसके अतिरिक्त, एक रिकॉर्ड विशेषता bar
जोड़ी जाती है। मैन्युअल रूप से एक रिकॉर्ड बनाने के द्वारा, एक अलग कंस्ट्रक्टर आकार बनाना, क्षेत्र को छोड़ना और एक्सेसर को अलग तरीके से लागू करना संभव है। इसी समय, प्रतिबिंब एपीआई को यह विश्वास करना अभी भी संभव है कि वर्ग वास्तविक रिकॉर्ड का प्रतिनिधित्व करता है।
किसी भी सुपर विधि को कॉल करें (जावा 1.1 तक)
हालाँकि, यह केवल जावा संस्करण 1 और 1.1 के लिए ही संभव है। जेबीसी में, विधियों को हमेशा स्पष्ट लक्ष्य प्रकार पर भेजा जाता है। इसका मतलब है कि के लिए
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
आह्वान करना संभव था । हालांकि प्रत्यक्ष सुपर क्लास की तुलना में एक अन्य सुपर मेथड इम्प्लीमेंटेशन को कॉल करने के लिए एक स्पष्ट आह्वान को परिभाषित करना अभी भी संभव है, यह 1.1 के बाद जावा संस्करणों में कोई प्रभाव नहीं डालता है। जावा 1.1 में, इस व्यवहार को ध्वज को सेट करके नियंत्रित किया गया था जो उसी व्यवहार को सक्षम करेगा जो केवल प्रत्यक्ष सुपर क्लास के कार्यान्वयन को कहता है।Foo#baz
Bar#baz
ACC_SUPER
उसी वर्ग में घोषित की गई विधि की एक गैर-आभासी कॉल को परिभाषित करें
जावा में, एक वर्ग को परिभाषित करना संभव नहीं है
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
जो सीधे bar
तरीके से कॉल Foo::foo
को Foo
's' वर्जन से जोड़ता है । यह ओपकोड आमतौर पर सुपर मेथड इनवोकेशन को लागू करने के लिए उपयोग किया जाता है लेकिन आप वर्णित व्यवहार को लागू करने के लिए ओपकोड का पुन: उपयोग कर सकते हैं।
बारीक-दाने के प्रकार एनोटेशन
जावा में, एनोटेशन को उनके अनुसार लागू किया जाता @Target
है जो एनोटेशन घोषित करता है। बाइट कोड हेरफेर का उपयोग करना, इस नियंत्रण से स्वतंत्र रूप से एनोटेशन को परिभाषित करना संभव है। इसके अलावा, उदाहरण के लिए यह संभव है कि पैरामीटर को एनोटेट किए बिना एक पैरामीटर प्रकार का एनोटेट किया जाए, भले ही @Target
एनोटेशन दोनों तत्वों पर लागू हो।
एक प्रकार या उसके सदस्यों के लिए किसी भी विशेषता को परिभाषित करें
जावा भाषा के भीतर, केवल फ़ील्ड, विधियों या कक्षाओं के लिए एनोटेशन को परिभाषित करना संभव है। जेबीसी में, आप मूल रूप से जावा कक्षाओं में किसी भी जानकारी को एम्बेड कर सकते हैं। इस जानकारी का उपयोग करने के लिए, आप अब जावा क्लास लोडिंग तंत्र पर निर्भर नहीं रह सकते हैं, लेकिन आपको अपने द्वारा मेटा जानकारी निकालने की आवश्यकता है।
ओवरफ्लो और परोक्ष असाइन byte
, short
, char
और boolean
मूल्यों
बाद के आदिम प्रकार सामान्य रूप से जेबीसी में नहीं जाने जाते हैं, लेकिन केवल सरणी प्रकारों के लिए या क्षेत्र और विधि वर्णनकर्ताओं के लिए परिभाषित किए जाते हैं। बाइट कोड निर्देशों के भीतर, सभी प्रकार के नाम 32 बिट स्थान लेते हैं जो उन्हें प्रतिनिधित्व करने की अनुमति देता है int
। आधिकारिक तौर पर, केवल int
, float
, long
और double
प्रकार बाइट कोड के भीतर मौजूद है जो सभी की जरूरत JVM के सत्यापनकर्ता के शासन के द्वारा स्पष्ट रूपांतरण।
मॉनिटर जारी नहीं
एक synchronized
ब्लॉक वास्तव में दो बयानों से बना होता है, एक अधिग्रहित करने के लिए और एक मॉनिटर जारी करने के लिए। जेबीसी में, आप इसे जारी किए बिना एक प्राप्त कर सकते हैं।
नोट : हॉटस्पॉट के हालिया कार्यान्वयन में, इसके बजाय IllegalMonitorStateException
एक विधि के अंत में या एक अंतर्निहित रिलीज के लिए जाता है अगर विधि एक अपवाद द्वारा ही समाप्त हो जाती है।
return
एक टाइप इनिशियलाइज़र में एक से अधिक कथन जोड़ें
जावा में, यहां तक कि एक तुच्छ प्रकार का इनिशियलाइज़र भी
class Foo {
static {
return;
}
}
गैरकानूनी है। बाइट कोड में, टाइप इनिशियलाइज़र को किसी अन्य विधि के रूप में माना जाता है, अर्थात रिटर्न स्टेटमेंट को कहीं भी परिभाषित किया जा सकता है।
इर्रिडिएबल लूप बनाएं
जावा संकलक जावा बाइट कोड में गोटो बयानों के लिए छोरों को परिवर्तित करता है। इस तरह के बयानों का उपयोग इर्रिसेबल लूप बनाने के लिए किया जा सकता है, जो जावा कंपाइलर कभी नहीं करता है।
एक पुनरावर्ती कैच ब्लॉक को परिभाषित करें
जावा बाइट कोड में, आप एक ब्लॉक को परिभाषित कर सकते हैं:
try {
throw new Exception();
} catch (Exception e) {
<goto on exception>
throw Exception();
}
synchronized
जावा में एक ब्लॉक का उपयोग करते समय एक समान बयान को स्पष्ट रूप से बनाया जाता है जहां मॉनिटर जारी करते समय कोई भी अपवाद इस मॉनिटर को जारी करने के निर्देश पर वापस लौटता है। आम तौर पर, इस तरह के निर्देश पर कोई अपवाद नहीं होना चाहिए, लेकिन अगर यह (जैसे पदावनत ThreadDeath
) होगा, तो भी मॉनिटर जारी किया जाएगा।
किसी भी डिफ़ॉल्ट विधि को कॉल करें
जावा कंपाइलर को डिफ़ॉल्ट विधि के आह्वान की अनुमति देने के लिए कई शर्तों को पूरा करने की आवश्यकता होती है:
- यह विधि सबसे विशिष्ट होनी चाहिए (एक उप इंटरफ़ेस द्वारा अतिरंजित नहीं होनी चाहिए जो किसी भी प्रकार से लागू होती है , जिसमें सुपर प्रकार भी शामिल है)।
- डिफ़ॉल्ट विधि का इंटरफ़ेस प्रकार सीधे उस वर्ग द्वारा लागू किया जाना चाहिए जो डिफ़ॉल्ट विधि कह रहा है। हालाँकि, यदि इंटरफ़ेस इंटरफ़ेस का
B
विस्तार करता है, A
लेकिन इसमें एक विधि को ओवरराइड नहीं करता है A
, तो विधि अभी भी लागू की जा सकती है।
जावा बाइट कोड के लिए, केवल दूसरी स्थिति मायने रखती है। पहले वाला हालांकि अप्रासंगिक है।
एक उदाहरण पर एक सुपर विधि लागू करें जो नहीं है this
जावा संकलक केवल उदाहरणों पर एक सुपर (या इंटरफ़ेस डिफ़ॉल्ट) विधि को लागू करने की अनुमति देता है this
। बाइट कोड में, हालांकि निम्न के समान एक ही प्रकार की आवृत्ति पर सुपर विधि को लागू करना भी संभव है:
class Foo {
void m(Foo f) {
f.super.toString(); // calls Object::toString
}
public String toString() {
return "foo";
}
}
सिंथेटिक सदस्यों तक पहुंचें
जावा बाइट कोड में, सिंथेटिक सदस्यों को सीधे एक्सेस करना संभव है। उदाहरण के लिए, निम्न उदाहरण पर विचार करें कि किसी अन्य उदाहरण के बाहरी उदाहरण Bar
तक कैसे पहुँचा जाता है:
class Foo {
class Bar {
void bar(Bar bar) {
Foo foo = bar.Foo.this;
}
}
}
यह किसी भी सिंथेटिक क्षेत्र, वर्ग या विधि के लिए आम तौर पर सच है।
आउट-की-सिंक जेनेरिक प्रकार की जानकारी को परिभाषित करें
जबकि जावा रनटाइम जेनेरिक प्रकारों की प्रक्रिया नहीं करता है (जावा कंपाइलर टाइप इरेज़र लागू होने के बाद), यह जानकारी अभी भी संकलित वर्ग के लिए मेटा जानकारी के रूप में संलग्न है और प्रतिबिंब एपीआई के माध्यम से सुलभ है।
सत्यापनकर्ता मेटा डेटा- String
इनकोड किए गए मानों की निरंतरता की जांच नहीं करता है । इसलिए जेनेरिक प्रकारों के बारे में जानकारी को परिभाषित करना संभव है जो इरेज़र से मेल नहीं खाता है। एक अवधारणा के रूप में, निम्नलिखित दावे सच हो सकते हैं:
Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());
Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);
इसके अलावा, हस्ताक्षर को अमान्य के रूप में परिभाषित किया जा सकता है जैसे कि एक रनटाइम अपवाद को फेंक दिया जाता है। यह अपवाद तब फेंका जाता है जब जानकारी का पहली बार एक्सेस किया जाता है क्योंकि इसका मूल्यांकन आलसी तरीके से किया जाता है। (त्रुटि के साथ एनोटेशन मान के समान।)
केवल कुछ विधियों के लिए पैरामीटर मेटा जानकारी संलग्न करें
जावा कंपाइलर, parameter
सक्षम ध्वज के साथ एक वर्ग को संकलित करते समय पैरामीटर नाम और संशोधक जानकारी एम्बेड करने की अनुमति देता है । जावा वर्ग फ़ाइल प्रारूप में, यह जानकारी हालांकि प्रति-विधि संग्रहीत की जाती है जो केवल कुछ विधियों के लिए ऐसी विधि जानकारी को एम्बेड करना संभव बनाती है।
मेस बातें और अपने जेवीएम को हार्ड-क्रैश करें
एक उदाहरण के रूप में, जावा बाइट कोड में, आप किसी भी प्रकार पर किसी भी विधि को लागू करने के लिए परिभाषित कर सकते हैं। आमतौर पर, सत्यापनकर्ता शिकायत करेगा यदि एक प्रकार ऐसी विधि के बारे में नहीं जानता है। हालाँकि, यदि आप किसी सरणी पर एक अज्ञात विधि का उपयोग करते हैं, तो मुझे कुछ JVM संस्करण में एक बग मिला, जहाँ सत्यापनकर्ता को यह याद होगा और निर्देश के लागू होने के बाद आपका JVM समाप्त हो जाएगा। यह शायद ही एक विशेषता यह है, लेकिन यह तकनीकी रूप से कुछ है कि के साथ संभव नहीं है javac संकलित जावा। जावा में किसी प्रकार का दोहरा सत्यापन है। पहली मान्यता जावा कंपाइलर द्वारा लागू की जाती है, दूसरी जेवीएम द्वारा जब एक कक्षा भरी जाती है। संकलक को छोड़ देने से, आपको सत्यापनकर्ता के सत्यापन में एक कमजोर स्थान मिल सकता है। यह एक सुविधा की बजाय एक सामान्य कथन है, हालाँकि।
बाहरी वर्ग न होने पर एक कंस्ट्रक्टर के रिसीवर के प्रकार को नोट करें
जावा 8 के बाद से, आंतरिक वर्गों के गैर-स्थिर तरीके और निर्माता एक रिसीवर प्रकार की घोषणा कर सकते हैं और इन प्रकारों को एनोटेट कर सकते हैं। शीर्ष-स्तरीय कक्षाओं के निर्माता अपने रिसीवर के प्रकार को एनोटेट नहीं कर सकते हैं क्योंकि वे सबसे अधिक एक घोषित नहीं करते हैं।
class Foo {
class Bar {
Bar(@TypeAnnotation Foo Foo.this) { }
}
Foo() { } // Must not declare a receiver type
}
चूँकि , प्रतिनिधित्व को Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
वापस AnnotatedType
करता है Foo
, इसलिए Foo
क्लास फाइल में सीधे टाइपर्स एनोटेशन को शामिल करना संभव है जहाँ ये एनोटेशन बाद में प्रतिबिंब एपीआई द्वारा पढ़े जाते हैं।
अप्रयुक्त / विरासत बाइट कोड निर्देशों का उपयोग करें
चूंकि अन्य लोगों ने इसका नाम दिया है, मैं इसे भी शामिल करूंगा। जावा पूर्व में JSR
और RET
कथनों द्वारा सबरूटीन्स का उपयोग कर रहा था । JBC को इस उद्देश्य के लिए अपने स्वयं के वापसी पता भी पता था। हालाँकि, सबरूटीन्स के उपयोग ने स्थैतिक कोड विश्लेषण को ओवरप्ले किया था यही कारण है कि इन निर्देशों का उपयोग नहीं किया जाता है। इसके बजाय, जावा कंपाइलर कोड को डुप्लिकेट करेगा। हालांकि, यह मूल रूप से समान तर्क बनाता है यही कारण है कि मैं वास्तव में इसे कुछ अलग हासिल करने के लिए नहीं मानता हूं। इसी तरह, आप उदाहरण के लिए जोड़ सकते हैंNOOP
बाइट कोड निर्देश जो कि जावा कंपाइलर द्वारा उपयोग नहीं किया जाता है लेकिन यह वास्तव में आपको कुछ नया हासिल करने की अनुमति नहीं देगा। जैसा कि संदर्भ में बताया गया है, इन उल्लिखित "फ़ीचर निर्देश" को अब कानूनी ऑपकोड के सेट से हटा दिया जाता है जो उन्हें एक फ़ीचर से कम भी प्रदान करता है।