एक जावा क्लास एक रिक्त रेखा के साथ अलग-अलग संकलन क्यों करती है?


207

मेरे पास निम्न जावा वर्ग है

public class HelloWorld {
  public static void main(String []args) {
  }
}

जब मैं इस फ़ाइल को संकलित करता हूं और मुझे प्राप्त होने वाली कक्षा की फ़ाइल पर एक sha256 चलाता है

9c8d09e27ea78319ddb85fcf4f8085aa7762b0ab36dc5ba5fd000dccb63960ff  HelloWorld.class

आगे मैंने कक्षा को संशोधित किया और इस तरह एक रिक्त रेखा जोड़ी:

public class HelloWorld {

  public static void main(String []args) {
  }
}

फिर से मैं आउटपुट पर एक समान परिणाम प्राप्त करने की उम्मीद में एक sha256 दौड़ा, लेकिन इसके बजाय मुझे मिल गया

11f7ad3ad03eb9e0bb7bfa3b97bbe0f17d31194d8d92cc683cfbd7852e2d189f  HelloWorld.class

मैंने इस ट्यूटोरियल लेख पर पढ़ा है कि:

एक पंक्ति जिसमें केवल सफेद स्थान होता है, संभवतः एक टिप्पणी के साथ, एक रिक्त रेखा के रूप में जाना जाता है, और जावा इसे पूरी तरह से अनदेखा करता है।

इसलिए मेरा सवाल है, चूंकि जावा रिक्त लाइनों की उपेक्षा करता है, इसलिए संकलित बाईटेकोड दोनों कार्यक्रमों के लिए अलग क्यों है?

HelloWorld.classएक 0x03बाइट में एक बाइट द्वारा प्रतिस्थापित किया गया है 0x04


45
ध्यान दें कि संकलक वर्ग फ़ाइलों के उत्पादन में नियतात्मक होने के लिए बाध्य नहीं है, भले ही वे सामान्य रूप से हों। इस प्रश्न को देखें । डिफ़ॉल्ट रूप से जार फाइलें प्रतिलिपि प्रस्तुत करने योग्य नहीं होती हैं, अर्थात समान कोड को संकलित करने के परिणामस्वरूप दो अलग-अलग JAR हो जाएंगे। ऐसा इसलिए है क्योंकि फाइलों और टाइमस्टैम्प के क्रम का मेल नहीं होगा। विशिष्ट कॉन्फ़िगरेशन के साथ Reproducible बनाता है।
जियाको अल्जेटा

22
TutorialsPoint का दावा है कि "जावा पूरी तरह से" खाली लाइनों की उपेक्षा करता हैजावा भाषा विनिर्देश की धारा 3.4 अन्यथा कहती है। किस पर विश्वास किया जाए? ...
skomisa

37
@skomisa विनिर्देशन।
wizzwizz4

4
@GiacomoAlzetta एक सिंगल बायटेकोड फ़ाइल के लिए एक निर्दिष्ट बायोटेक फॉर्म भी नहीं है। उदाहरण के लिए, सदस्यों का क्रम अनिर्दिष्ट है, इसलिए यदि कंपाइलर Setआंतरिक रूप से यादृच्छिकता के साथ नए अपरिवर्तनीय एस का उपयोग करता है , तो यह प्रत्येक रन पर एक अलग क्रम का उत्पादन कर सकता है। यह संकलन-समय युक्त एक कस्टम विशेषता भी जोड़ सकता है। और इसी तरह…
होलकर

15
@DioPhung ने एक और सबक सीखा: ट्यूटोरियलस्पॉट अच्छे ट्यूटोरियल के लिए एक विश्वसनीय स्रोत नहीं है
3

जवाबों:


331

मूल रूप से, लाइन नंबर डिबगिंग के लिए रखे जाते हैं, इसलिए यदि आप अपने स्रोत कोड को आपके द्वारा किए गए तरीके से बदलते हैं, तो आपका तरीका एक अलग लाइन पर शुरू होता है और संकलित वर्ग अंतर को दर्शाता है।


11
यह भी बताता है कि ओपी द्वारा रिपोर्ट किए गए बाइट्स में इसका अंतर क्यों है: end-of-transmissionASCII कोड 4 end-of-textके लिए और ASCII कोड 3 के लिए खड़ा है
Ferrybig

160
प्रायोगिक रूप से यह साबित करने के लिए मैंने ओपी के स्रोत की कक्षा की फाइलों के -g:noneसंकलन की तुलना करते हुए ध्वज का उपयोग किया (जो सभी डिबगिंग जानकारी को हटाता है, यहां देखें ) और दोनों परिदृश्यों में एक ही हैश मिला।
कप्तान मैन

14
जावा एसई 11 के लिए जावा लैंग्वेज स्पेसिफिकेशन की धारा 3.4 ( "लाइन टर्मिनेटर" ) से आपके उत्तर के औपचारिक समर्थन में : "एक जावा कंपाइलर लाइन टर्मिनेटरों को पहचानकर यूनिकोड इनपुट वर्णों के अनुक्रम को लाइनों में विभाजित करता है ... लाइनों को परिभाषित लाइन टर्मिनेटर एक जावा कंपाइलर द्वारा निर्मित लाइन संख्या निर्धारित कर सकते हैं "
स्किमोइसा

4
इन लाइन नंबरों का एक महत्वपूर्ण उपयोग यह है कि अगर एक अपवाद फेंक दिया जाए; यह आपको स्टैक ट्रेस में अपवाद की पंक्ति संख्या बता सकता है।
gparyani

114

आप परिवर्तन का उपयोग करके देख सकते हैं javap -vजो क्रिया जानकारी को आउटपुट करेगा। अन्य पहले से ही उल्लेख की तरह अंतर लाइन नंबरों में होगा:

$ javap -v HelloWorld.class > with-line.txt
$ javap -v HelloWorld.class > no-line.txt
$ diff -C 1 no-line.txt with-line.txt
*** no-line.txt 2018-10-03 11:43:32.719400000 +0100
--- with-line.txt       2018-10-03 11:43:04.378500000 +0100
***************
*** 2,4 ****
    Last modified 03-Oct-2018; size 373 bytes
!   MD5 checksum 058baea07fb787bdd81c3fb3f9c586bc
    Compiled from "HelloWorld.java"
--- 2,4 ----
    Last modified 03-Oct-2018; size 373 bytes
!   MD5 checksum 435dbce605c21f84dda48de1a76e961f
    Compiled from "HelloWorld.java"
***************
*** 50,52 ****
        LineNumberTable:
!         line 3: 0
        LocalVariableTable:
--- 50,52 ----
        LineNumberTable:
!         line 4: 0
        LocalVariableTable:

अधिक सटीक रूप से वर्ग फ़ाइल LineNumberTableअनुभाग में भिन्न होती है :

LineNumberTable विशेषता कोड विशेषता (§4.7.3) की विशेषता तालिका में एक वैकल्पिक चर-लंबाई विशेषता है। इसका उपयोग डिबगर्स द्वारा यह निर्धारित करने के लिए किया जा सकता है कि कोड सरणी का कौन सा हिस्सा मूल स्रोत फ़ाइल में दिए गए लाइन नंबर से मेल खाता है।

यदि किसी कोड विशेषता की विशेषता तालिका में कई LineNumberTable विशेषताएँ मौजूद हैं, तो वे किसी भी क्रम में दिखाई दे सकते हैं।

किसी कोड विशेषता की तालिका में किसी स्रोत फ़ाइल की प्रति पंक्ति एक से अधिक LineNumberTable विशेषता हो सकती है। अर्थात्, LineNumberTable विशेषताएँ स्रोत फ़ाइल की दी गई रेखा का एक साथ प्रतिनिधित्व कर सकती हैं, और स्रोत लाइनों के साथ एक-से-एक होने की आवश्यकता नहीं है।


57

यह धारणा कि "जावा रिक्त लाइनों की उपेक्षा करता है" गलत है। यहां एक कोड स्निपेट है जो विधि से पहले खाली लाइनों की संख्या के आधार पर अलग-अलग व्यवहार करता है main:

class NewlineDependent {

  public static void main(String[] args) {
    int i = Thread.currentThread().getStackTrace()[1].getLineNumber();
    System.out.println((new String[]{"foo", "bar"})[((i % 2) + 2) % 2]);
  }
}

यदि पहले कोई खाली लाइनें नहीं हैं main, तो यह प्रिंट करता है "foo", लेकिन एक खाली लाइन से पहले main, यह प्रिंट करता है "bar"

चूंकि रनटाइम व्यवहार अलग है, .classफाइलें अलग-अलग होनी चाहिए, किसी भी टाइमस्टैम्प या अन्य मेटाडेटा की परवाह किए बिना।

यह हर भाषा के लिए है, जिसमें केवल जावा के लिए ही नहीं, बल्कि लाइन नंबरों के साथ स्टैक फ्रेम तक पहुंच है।

नोट: यदि इसे -g:none(बिना किसी डीबगिंग जानकारी के) के साथ संकलित किया जाता है , तो लाइन नंबर शामिल नहीं होंगे, getLineNumber()हमेशा रिटर्न करते हैं -1, और प्रोग्राम हमेशा प्रिंट करता है "bar", चाहे लाइन ब्रेक की संख्या कितनी भी हो।


11
इसे प्रिंट भी कर सकते हैं Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
xehpuk

1
@xehpuk ध्वज -1का उपयोग करने का एकमात्र तरीका मुझे मिल सकता है -g:none। क्या सामान्य का उपयोग करके इस अपवाद को प्राप्त करने का कोई अन्य तरीका है javac?
एंड्री टायुकिन

3
मैं केवल -gविकल्प के साथ अनुमान लगाता हूं । वहाँ भी है -g:varsऔर -g:sourceजो की पीढ़ी को रोकता है LineNumberTable
xehpuk

14

डिबगिंग के लिए किसी भी पंक्ति संख्या के विवरण के साथ-साथ, आपका प्रकट निर्माण समय और दिनांक भी संग्रहीत कर सकता है। यह स्वाभाविक रूप से हर बार जब आप संकलन करते हैं तो अलग होगा।


14
C # के पास भी यह मुद्दा है; हाल ही में जब तक कंपाइलर हमेशा एक ताजा GUID को असेंबली असेंबली में एम्बेड करता है ताकि आपको इस बात की गारंटी हो जाए कि दो बिल्ड बाइनरी समान नहीं होंगे, ताकि आप उन्हें अलग-अलग बता सकें!
एरिक लिपर्ट

3
@ EricLippert यदि दो बिल्ड केवल उनके उत्पन्न समय (यानी समान कोड आधार) से अलग हैं, तो क्या हमें उन्हें एक ही नहीं मानना ​​चाहिए? आधुनिक CI / CD बिल्ड पाइपलाइन (जेनकिंस, टीमसिटी, सर्कलसीआई) के साथ, हमारे पास बिल्ड के बीच अंतर करने का एक तरीका होगा, लेकिन एप्लिकेशन परिप्रेक्ष्य से, समान कोड आधार के साथ नए बायनेरिज़ को तैनात करना उपयोगी नहीं लगता है।
डियो

2
@DioPhung यह दूसरा रास्ता है। आप एक ही GUID के लिए दो अलग-अलग बिल्ड नहीं चाहते , क्योंकि यही सिस्टम तय कर सकता है कि किसका उपयोग करना है। इसलिए हर बार एक नया GUID उत्पन्न करना सबसे आसान है; और फिर आपको वह साइड इफेक्ट मिलता है जो एरिक एक अनपेक्षित परिणाम के रूप में बताता है।
ग्राहम

3
@vikingsteve जैसा मैंने कहा, यह एक ही GUID के साथ रिपोर्ट किए जाने वाले दो अलग-अलग बिल्ड के लिए और भी कम मददगार होगा, जो तब एक ही सॉफ्टवेयर के रूप में सिस्टम को रिपोर्ट किया जाएगा। यह किसी भी प्रकार की प्रोविज़निंग योजना की कुल विफलता का कारण होगा, इसलिए यह मिशन-महत्वपूर्ण है कि GUID को कभी भी (उचित संभावना के भीतर) दोहराया नहीं जाता है! एक ही स्रोत कोड के दो अलग-अलग बिल्ड के लिए अलग-अलग GUID का होना एक तुच्छ झुंझलाहट है। तो मिशन-क्रिटिकल विफलता परिदृश्य के सामने, आपको जो लगता है वह थोड़ा अनहेल्दी है, वास्तव में नहीं है।
ग्राहम

4
@vikingsteve बाइनरी का कोड हिस्सा अभी भी समान है (यदि मैं समझ रहा हूं, मैं सी # देव नहीं हूं), यह सिर्फ कुछ मेटाडेटा है जो बाइनरी से जुड़ा हुआ है।
कप्तान मैन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.