การรวบรวมเวลาเทียบกับการพึ่งพาเวลาทำงาน - Java


95

อะไรคือความแตกต่างระหว่างเวลาคอมไพล์และการอ้างอิงเวลาทำงานใน Java? มันเกี่ยวข้องกับ class path แต่แตกต่างกันอย่างไร?

คำตอบ:


82
  • การพึ่งพาเวลาคอมไพล์ : คุณต้องการการพึ่งพาในCLASSPATHการคอมไพล์สิ่งประดิษฐ์ของคุณ พวกมันถูกสร้างขึ้นเนื่องจากคุณมี "การอ้างอิง" บางอย่างสำหรับการอ้างอิงที่เข้ารหัสในโค้ดของคุณเช่นการเรียกnewคลาสบางคลาสการขยายหรือการใช้งานบางสิ่งบางอย่าง (ทั้งทางตรงหรือทางอ้อม) หรือการเรียกใช้เมธอดโดยใช้reference.method()สัญกรณ์โดยตรง

  • พึ่งพาเวลาทำงาน : คุณจำเป็นต้องพึ่งพาในคุณCLASSPATHจะเรียกใช้สิ่งประดิษฐ์ของคุณ พวกมันถูกสร้างขึ้นเนื่องจากคุณรันโค้ดที่เข้าถึงการอ้างอิง (ไม่ว่าจะด้วยวิธีฮาร์ดโค้ดหรือผ่านการสะท้อนหรืออะไรก็ตาม)

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

ตัวอย่างนี้

ใน C.java (สร้างคลาส C. ):

package dependencies;
public class C { }

ใน A.java (สร้าง A.class):

package dependencies;
public class A {
    public static class B {
        public String toString() {
            C c = new C();
            return c.toString();
        }
    }
    public static void main(String[] args) {
        if (args.length > 0) {
            B b = new B();
            System.out.println(b.toString());
        }
    }
}

ในกรณีนี้Aมีการพึ่งพารวบรวมเวลาในการCผ่านBแต่มันจะมีเพียงการพึ่งพาเวลาทำงานใน C ถ้าคุณส่งผ่านพารามิเตอร์บางอย่างเมื่อการดำเนินการjava dependencies.Aเป็น JVM จะพยายามที่จะแก้B's พึ่งพาCเมื่อได้รับการดำเนินการB b = new B(). คุณลักษณะนี้ช่วยให้คุณระบุเฉพาะการอ้างอิงของคลาสที่คุณใช้ในพา ธ โค้ดของคุณในขณะรันไทม์และละเว้นการอ้างอิงของคลาสอื่น ๆ ที่เหลือในอาร์ติแฟกต์


1
ฉันรู้ว่านี่เป็นคำตอบที่เก่ามาก แต่ JVM จะไม่มี C เป็นตัวอ้างอิงรันไทม์ตั้งแต่เริ่มต้นได้อย่างไร หากสามารถรับรู้ได้ว่า "นี่คือการอ้างอิงถึง C ถึงเวลาที่จะเพิ่มการอ้างอิง" แล้ว C ก็ไม่ได้เป็นที่พึ่งพิงเนื่องจาก JVM รับรู้และรู้ว่าอยู่ที่ไหน?
wearebob

@wearebob อาจมีการระบุแบบนั้นฉันเดา แต่พวกเขาตัดสินใจว่าการเชื่อมโยงแบบขี้เกียจนั้นดีกว่าและโดยส่วนตัวฉันเห็นด้วยกับเหตุผลที่ระบุไว้ข้างต้น: อนุญาตให้คุณใช้รหัสบางอย่างหากจำเป็น แต่ไม่บังคับให้คุณรวมไว้ใน การปรับใช้ของคุณหากคุณไม่ต้องการ ซึ่งค่อนข้างมีประโยชน์เมื่อจัดการกับรหัสของบุคคลที่สาม
gpeche

ถ้าฉันมีโถที่ติดตั้งที่ไหนสักแห่งมันจะต้องมีการอ้างอิงทั้งหมดอยู่แล้ว มันไม่รู้ว่ามันจะถูกรันโดยมีอาร์กิวเมนต์หรือไม่ (มันไม่รู้ว่าจะใช้ C หรือไม่) ดังนั้นมันจะต้องมี C อยู่ไม่ว่าจะด้วยวิธีใดก็ตาม ฉันไม่เห็นว่าหน่วยความจำ / เวลาถูกบันทึกไว้อย่างไรโดยไม่มี C บน classpath ตั้งแต่เริ่มต้น
wearebob

1
@wearebob JAR ไม่จำเป็นต้องรวมการอ้างอิงทั้งหมด นั่นเป็นเหตุผลที่แอปพลิเคชันที่ไม่สำคัญเกือบทุกตัวมีไดเร็กทอรี / lib หรือคล้ายกันที่มี JAR หลายตัว
gpeche

34

ตัวอย่างง่ายๆคือดู api เช่น servlet api ในการคอมไพล์ servlet ของคุณคุณต้องมี servlet-api.jar แต่ที่รันไทม์คอนเทนเนอร์ servlet จะจัดเตรียมการใช้งาน servlet api ดังนั้นคุณไม่จำเป็นต้องเพิ่ม servlet-api.jar ในพา ธ คลาสรันไทม์ของคุณ


เพื่อความกระจ่าง (สิ่งนี้ทำให้ฉันสับสน) หากคุณใช้ maven และสร้างสงคราม "servlet-api" มักจะเป็นการพึ่งพา "ให้" แทนการพึ่งพา "รันไทม์" ซึ่งจะทำให้รวมอยู่ในสงครามหาก ฉันถูกต้อง
xdhmoore

2
'ให้' หมายถึงรวมในเวลาคอมไพล์ แต่อย่ารวมไว้ใน WAR หรือคอลเล็กชันการอ้างอิงอื่น ๆ 'รันไทม์' ทำสิ่งที่ตรงกันข้าม (ไม่มีในคอมไพล์ แต่มาพร้อมกับ WAR)
KC Baltz

30

คอมไพเลอร์ต้องการคลาสพา ธ ที่ถูกต้องเพื่อรวบรวมการเรียกไปยังไลบรารี (การอ้างอิงเวลาคอมไพล์)

JVM ต้องการคลาสพา ธ ที่ถูกต้องเพื่อโหลดคลาสในไลบรารีที่คุณกำลังเรียกใช้ (การอ้างอิงรันไทม์)

อาจแตกต่างกันในสองวิธี:

1) ถ้าคลาส C1 ของคุณเรียกไลบรารีคลาส L1 และ L1 เรียกไลบรารีคลาส L2 ดังนั้น C1 จะมีการพึ่งพารันไทม์บน L1 และ L2 แต่มีเพียงการพึ่งพาเวลาคอมไพล์บน L1

2) ถ้าคลาส C1 ของคุณสร้างอินสแตนซ์อินเทอร์เฟซ I1 แบบไดนามิกโดยใช้ Class.forName () หรือกลไกอื่น ๆ และคลาสการนำไปใช้สำหรับอินเทอร์เฟซ I1 คือคลาส L1 ดังนั้น C1 จะมีการพึ่งพารันไทม์บน I1 และ L1 แต่มีเพียงการพึ่งพาเวลาคอมไพล์เท่านั้น บน I1.

การอ้างอิง "ทางอ้อม" อื่น ๆ ซึ่งเหมือนกันสำหรับเวลาคอมไพล์และรันไทม์:

3) คลาส C1 ของคุณขยายคลาสไลบรารี L1 และ L1 ใช้อินเทอร์เฟซ I1 และขยายคลาสไลบรารี L2: C1 มีการพึ่งพาเวลาคอมไพล์บน L1, L2 และ I1

4) คลาส C1 ของคุณมีเมธอดfoo(I1 i1)และเมธอดbar(L1 l1)โดยที่ I1 เป็นอินเทอร์เฟซและ L1 เป็นคลาสที่รับพารามิเตอร์ซึ่งเป็นอินเทอร์เฟซ I1: C1 มีการพึ่งพาเวลาคอมไพล์บน I1 และ L1

โดยพื้นฐานแล้วในการทำสิ่งที่น่าสนใจคลาสของคุณจะต้องเชื่อมต่อกับคลาสและอินเทอร์เฟซอื่น ๆ ในคลาสพา ธ กราฟคลาส / อินเทอร์เฟซที่สร้างขึ้นโดยชุดของอินเตอร์เฟสไลบรารีนั้นให้ผลต่อห่วงโซ่การพึ่งพาเวลาคอมไพล์ การใช้งานไลบรารีให้ผลเชนการพึ่งพารัน โปรดสังเกตว่าห่วงโซ่การพึ่งพาเวลาทำงานนั้นขึ้นอยู่กับเวลาทำงานหรือล้มเหลว - ช้า: ถ้าการใช้งาน L1 บางครั้งขึ้นอยู่กับการสร้างอินสแตนซ์วัตถุของคลาส L2 และคลาสนั้นจะได้รับการสร้างอินสแตนซ์ในสถานการณ์เฉพาะเท่านั้นดังนั้นจึงไม่มีการพึ่งพายกเว้นใน สถานการณ์นั้น


1
การพึ่งพาเวลาคอมไพล์ไทม์ในตัวอย่างที่ 1 ควรเป็น L1 ไม่ใช่หรือ
BalusC

ขอบคุณ แต่การโหลดคลาสทำงานอย่างไรในขณะรันไทม์ เวลาคอมไพล์จะเข้าใจง่าย แต่ที่รันไทม์มันทำงานอย่างไรในกรณีที่ฉันมีสอง Jars เวอร์ชันที่แตกต่างกัน จะเลือกอันไหน
Kunal

1
ฉันค่อนข้างแน่ใจว่า classloader เริ่มต้นใช้ classpath และดำเนินการตามลำดับดังนั้นหากคุณมีสองไหใน classpath ที่ทั้งสองมีคลาสเดียวกัน (เช่น com.example.fooutils.Foo) มันจะใช้อันที่ เป็นอันดับแรกใน classpath ไม่ว่าคุณจะได้รับข้อผิดพลาดที่ระบุความไม่ชัดเจน แต่ถ้าคุณต้องการข้อมูลเพิ่มเติมเฉพาะสำหรับ classloaders คุณควรถามคำถามแยกต่างหาก
Jason S

ฉันคิดว่าในกรณีแรกการอ้างอิงเวลาคอมไพล์ควรมีอยู่ใน L2 เช่นประโยคควรเป็น: 1) ถ้าคลาส C1 ของคุณเรียกไลบรารีคลาส L1 และ L1 เรียกไลบรารีคลาส L2 ดังนั้น C1 มีการพึ่งพารันไทม์บน L1 และ L2 แต่เป็นเพียงการพึ่งพาเวลาในการคอมไพล์บน L1 & L2 นี่เป็นเช่นนั้นเช่นเดียวกับในเวลาคอมไพล์เมื่อคอมไพเลอร์ java ตรวจสอบ L1 จากนั้นจะตรวจสอบคลาสอื่น ๆ ทั้งหมดที่อ้างอิงโดย L1 (ยกเว้นการอ้างอิงแบบไดนามิกเช่น Class.forName ("myclassname)) ... มิฉะนั้นจะตรวจสอบได้อย่างไร การรวบรวมใช้งานได้ดีโปรดอธิบายหากคุณคิดเป็นอย่างอื่น
Rajesh Goel

1
ไม่ได้คุณจำเป็นต้องอ่านวิธีการคอมไพล์และการเชื่อมโยงทำงานใน Java คอมไพเลอร์ทั้งหมดให้ความสำคัญเมื่อกล่าวถึงคลาสภายนอกคือวิธีใช้คลาสนั้นเช่นวิธีการและฟิลด์คืออะไร ไม่สนใจว่าจะเกิดอะไรขึ้นในเมธอดของคลาสภายนอกนั้น ถ้า L1 เรียก L2 นั่นคือรายละเอียดการใช้งานของ L1 และ L1 ได้ถูกรวบรวมไว้ที่อื่นแล้ว
Jason S

13

Java ไม่ได้เชื่อมโยงอะไรเลยในเวลาคอมไพล์ ตรวจสอบไวยากรณ์โดยใช้คลาสที่ตรงกันที่พบใน CLASSPATH เท่านั้น ไม่ใช่จนกว่ารันไทม์ที่ทุกอย่างจะรวมกันและดำเนินการตาม CLASSPATH ในขณะนั้น


ยังไม่ถึงเวลาโหลด ... รันไทม์แตกต่างจากเวลาโหลด
Overexchange

11

การพึ่งพา Compiletime เป็นเพียงการอ้างอิง (คลาสอื่น ๆ ) ซึ่งคุณใช้โดยตรงในคลาสที่คุณกำลังรวบรวม การอ้างอิงรันไทม์ครอบคลุมทั้งการอ้างอิงโดยตรงและโดยอ้อมของคลาสที่คุณกำลังเรียกใช้ ดังนั้นการอ้างอิงรันไทม์รวมถึงการอ้างอิงการอ้างอิงและการอ้างอิงสะท้อนใด ๆ เช่น classnames ที่คุณมีในแต่จะใช้ในการStringClass#forName()


ขอบคุณ แต่การโหลดคลาสทำงานอย่างไรในขณะรันไทม์ เวลาคอมไพล์จะเข้าใจง่าย แต่ที่รันไทม์มันจะทำงานอย่างไรในกรณีที่ฉันมีสอง Jars เวอร์ชันที่แตกต่างกัน Class.forName () จะรับคลาสใดในกรณีที่มีคลาสหลายคลาสที่แตกต่างกันในพา ธ คลาส
Kunal

ชื่อที่ตรงกับชื่อแน่นอน ถ้าคุณจริงหมายถึง "หลายรุ่นของชั้นเดียวกัน" จากนั้นก็ขึ้นอยู่กับ ClassLoader รายการที่ "ใกล้เคียงที่สุด" จะโหลดขึ้น
BalusC

ฉันคิดว่าถ้าคุณมี A.jar กับ AB.jar ด้วยB extends Aและ C.jar ด้วยC extends BC.jar นั้นขึ้นอยู่กับเวลาในการรวบรวมของ A.jar แม้ว่าการพึ่งพา C กับ A จะเป็นทางอ้อม
gpeche

1
ปัญหาในการพึ่งพาเวลาคอมไพล์ทั้งหมดคือการพึ่งพาอินเทอร์เฟซ (ไม่ว่าอินเทอร์เฟซจะใช้วิธีการของคลาสหรือผ่านวิธีการของอินเทอร์เฟซหรือผ่านวิธีการที่มีอาร์กิวเมนต์ที่เป็นคลาสหรืออินเทอร์เฟซ)
Jason S

2

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

การพึ่งพารันไทม์เกี่ยวข้องกับโฟลว์ของการควบคุมที่แท้จริงมากกว่า มันเรียกใช้ที่อยู่หน่วยความจำจริง เป็นการพึ่งพาที่คุณมีเมื่อโปรแกรมของคุณกำลังทำงาน คุณต้องการรายละเอียดคลาส B ที่นี่เช่นการใช้งานไม่ใช่เฉพาะข้อมูลประเภท หากคลาสไม่มีอยู่คุณจะได้รับ RuntimeException และ JVM จะออก

การอ้างอิงทั้งสองโดยทั่วไปและไม่ควรไหลไปในทิศทางเดียวกัน นี่เป็นเรื่องของการออกแบบ OO แม้ว่า

ใน C ++ การคอมไพล์จะแตกต่างกันเล็กน้อย (ไม่ใช่เฉพาะเวลา) แต่มีตัวเชื่อมโยงด้วย ดังนั้นกระบวนการอาจคิดว่าคล้ายกับ Java ฉันเดา

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