แพ็กเกจและการนำเข้าแบบคงที่ใน Java

ในบทช่วยสอนJava 101ก่อนหน้าของฉันคุณได้เรียนรู้วิธีจัดระเบียบโค้ดของคุณให้ดีขึ้นโดยการประกาศประเภทการอ้างอิง (หรือที่เรียกว่าคลาสและอินเทอร์เฟซ) เป็นสมาชิกของประเภทอ้างอิงและบล็อกอื่น ๆ ฉันยังแสดงวิธีใช้การซ้อนเพื่อหลีกเลี่ยงความขัดแย้งของชื่อระหว่างประเภทการอ้างอิงที่ซ้อนกันและประเภทการอ้างอิงระดับบนสุดที่ใช้ชื่อเดียวกัน

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

ดาวน์โหลดรับโค้ดดาวน์โหลดซอร์สโค้ดสำหรับแอปพลิเคชันตัวอย่างในบทช่วยสอน Java นี้ สร้างโดย Jeff Friesen สำหรับ JavaWorld

ประเภทการอ้างอิงบรรจุภัณฑ์

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

ในส่วนนี้คุณจะได้เรียนรู้เกี่ยวกับแพ็คเกจ คุณจะพบว่าสิ่งที่แพคเกจเรียนรู้เกี่ยวกับpackageและimportงบและสำรวจหัวข้อเพิ่มเติมในการป้องกันการเข้าถึงไฟล์ JAR, และการค้นหาประเภท

แพ็คเกจใน Java คืออะไร?

ในการพัฒนาซอฟต์แวร์เรามักจะจัดระเบียบรายการตามความสัมพันธ์ตามลำดับชั้น ตัวอย่างเช่นในบทช่วยสอนก่อนหน้านี้ฉันได้แสดงวิธีการประกาศชั้นเรียนเป็นสมาชิกของชั้นเรียนอื่น ๆ เรายังสามารถใช้ระบบไฟล์เพื่อซ้อนไดเร็กทอรีในไดเร็กทอรีอื่น

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

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

แพ็กเกจมีชื่อซึ่งต้องเป็นตัวระบุที่ไม่สงวนลิขสิทธิ์ ตัวอย่างเช่นjava. ตัวดำเนินการเข้าถึงสมาชิก ( .) แยกชื่อแพ็กเกจออกจากชื่อแพ็กเกจย่อยและแยกชื่อแพ็กเกจหรือแพ็กเกจย่อยออกจากชื่อชนิด ตัวอย่างเช่นตัวดำเนินการเข้าถึงสองสมาชิกในjava.lang.Systemชื่อแพ็กเกจแยกjavaจากlangชื่อแพ็กเกจย่อยและชื่อแพ็กเกจย่อยแยกlangจากSystemชื่อชนิด

ต้องประกาศประเภทการอ้างอิงpublicเพื่อให้เข้าถึงได้จากภายนอกแพ็คเกจ เช่นเดียวกับค่าคงที่ตัวสร้างวิธีการหรือชนิดที่ซ้อนกันซึ่งต้องเข้าถึงได้ คุณจะเห็นตัวอย่างของสิ่งเหล่านี้ในบทช่วยสอนในภายหลัง

คำสั่งแพ็คเกจ

ใน Java เราใช้คำสั่ง packageเพื่อสร้างแพ็คเกจ คำสั่งนี้ปรากฏที่ด้านบนของซอร์สไฟล์และระบุแพ็กเกจที่เป็นประเภทไฟล์ซอร์ส ต้องเป็นไปตามไวยากรณ์ต่อไปนี้:

 package identifier[.identifier]*; 

คำสั่งแพ็กเกจเริ่มต้นด้วยคำสงวนpackageและต่อด้วยตัวระบุซึ่งตามด้วยลำดับตัวระบุที่คั่นด้วยช่วงเวลา เครื่องหมายอัฒภาค ( ;) ยุติคำสั่งนี้

ตัวระบุแรก (ซ้ายสุด) ตั้งชื่อแพ็กเกจและตัวระบุที่ตามมาแต่ละตัวจะตั้งชื่อแพ็กเกจย่อย ตัวอย่างเช่นในpackage a.b;ทุกประเภทที่ประกาศในไฟล์ต้นทางจะอยู่ในbแพ็กเกจย่อยของaแพ็กเกจ

หลักการตั้งชื่อแพ็กเกจ / แพ็กเกจย่อย

ตามแบบแผนเราแสดงชื่อแพ็กเกจหรือแพ็กเกจย่อยเป็นตัวพิมพ์เล็ก เมื่อชื่อประกอบด้วยคำหลายคำคุณอาจต้องใช้ตัวพิมพ์ใหญ่แต่ละคำยกเว้นคำแรก ตัวอย่างเช่นgeneralLedger.

ลำดับของชื่อแพ็กเกจต้องไม่ซ้ำกันเพื่อหลีกเลี่ยงปัญหาการคอมไพล์ ตัวอย่างเช่นสมมติว่าคุณสร้างgraphicsแพ็กเกจที่แตกต่างกันสองแพ็กเกจและสมมติว่าแต่ละgraphicsแพ็กเกจมีTriangleคลาสที่มีอินเทอร์เฟซที่แตกต่างกัน เมื่อคอมไพลเลอร์ Java พบสิ่งที่ต้องการด้านล่างจำเป็นต้องตรวจสอบว่ามีตัวTriangle(int, int, int, int)สร้างอยู่:

 Triangle t = new Triangle(1, 20, 30, 40); 

กรอบรูปสามเหลี่ยม

คิดว่าตัวTriangleสร้างเป็นการระบุกรอบที่จะวาดสามเหลี่ยม พารามิเตอร์สองตัวแรกระบุมุมบนซ้ายของกล่องและพารามิเตอร์สองตัวที่สองกำหนดขอบเขตของกล่อง

คอมไพเลอร์จะค้นหาแพ็กเกจที่สามารถเข้าถึงได้ทั้งหมดจนกว่าจะพบgraphicsแพ็กเกจที่มีTriangleคลาส หากแพ็กเกจที่พบมีTriangleคลาสที่เหมาะสมพร้อมตัวTriangle(int, int, int, int)สร้างทุกอย่างก็เรียบร้อยดี มิฉะนั้นถ้าTriangleคลาสที่พบไม่มีTriangle(int, int, int, int)ตัวสร้างคอมไพลเลอร์จะรายงานข้อผิดพลาด (ฉันจะพูดเพิ่มเติมเกี่ยวกับอัลกอริทึมการค้นหาในบทช่วยสอนนี้ในภายหลัง)

สถานการณ์นี้แสดงให้เห็นถึงความสำคัญของการเลือกลำดับชื่อแพ็กเกจที่ไม่ซ้ำกัน หลักการในการเลือกลำดับชื่อเฉพาะคือการย้อนชื่อโดเมนอินเทอร์เน็ตของคุณและใช้เป็นคำนำหน้าสำหรับลำดับ ตัวอย่างเช่นฉันจะเลือกca.javajeffเป็นคำนำหน้าเพราะjavajeff.caเป็นชื่อโดเมนของฉัน ฉันก็จะระบุในการเข้าถึงca.javajeff.graphics.TriangleTriangle

ส่วนประกอบชื่อโดเมนและชื่อแพ็กเกจที่ถูกต้อง

ส่วนประกอบของชื่อโดเมนไม่ใช่ชื่อแพ็กเกจที่ถูกต้องเสมอไป ชื่อส่วนประกอบอย่างน้อยหนึ่งชื่ออาจขึ้นต้นด้วยตัวเลข ( 3D.com) มีเครื่องหมายยัติภังค์ ( -) หรืออักขระอื่นที่ผิดกฎหมาย ( ab-z.com) หรือเป็นหนึ่งในคำสงวนของ Java ( short.com) อนุสัญญากำหนดให้คุณนำหน้าหลักด้วยเครื่องหมายขีดล่าง ( com._3D) แทนที่อักขระที่ผิดกฎหมายด้วยขีดล่าง ( com.ab_z) และต่อท้ายคำสงวนด้วยเครื่องหมายขีดล่าง ( com.short_)

คุณต้องปฏิบัติตามกฎสองสามข้อเพื่อหลีกเลี่ยงปัญหาเพิ่มเติมเกี่ยวกับคำสั่งแพ็คเกจ:

  1. คุณสามารถประกาศคำสั่งแพ็กเกจได้เพียงรายการเดียวในไฟล์ต้นฉบับ
  2. คุณไม่สามารถนำหน้าคำสั่งแพ็คเกจด้วยสิ่งอื่นนอกเหนือจากความคิดเห็น

กฎข้อแรกซึ่งเป็นกรณีพิเศษของกฎข้อที่สองมีอยู่เนื่องจากไม่เหมาะสมที่จะจัดเก็บประเภทการอ้างอิงในหลายแพ็กเกจ แม้ว่าแพ็กเกจจะสามารถจัดเก็บได้หลายประเภท แต่ประเภทสามารถอยู่ในแพ็กเกจเดียวเท่านั้น

เมื่อแฟ้มแหล่งที่มาไม่ได้ประกาศคำสั่งแพคเกจประเภทแฟ้มแหล่งที่มาที่มีการบอกว่าจะอยู่ในแพคเกจที่ไม่มีชื่อ โดยทั่วไปแล้วประเภทการอ้างอิงที่ไม่สำคัญจะถูกเก็บไว้ในแพ็คเกจของตนเองและหลีกเลี่ยงแพ็คเกจที่ไม่มีชื่อ

Java implementations map package and subpackage names to same-named directories. For example, an implementation would map graphics to a directory named graphics. In the case of the package a.b, the first letter, a would map to a directory named a and b would map to a b subdirectory of a. The compiler stores the class files that implement the package's types in the corresponding directory. Note that the unnamed package corresponds to the current directory.

Example: Packaging an audio library in Java

A practical example is helpful for fully grasping the package statement. In this section I demonstrate packages in the context of an audio library that lets you read audio files and obtain audio data. For brevity, I'll only present a skeletal version of the library.

The audio library currently consists of only two classes: Audio and WavReader. Audio describes an audio clip and is the library's main class. Listing 1 presents its source code.

Listing 1. Package statement example (Audio.java)

 package ca.javajeff.audio; public final class Audio { private int[] samples; private int sampleRate; Audio(int[] samples, int sampleRate) { this.samples = samples; this.sampleRate = sampleRate; } public int[] getSamples() { return samples; } public int getSampleRate() { return sampleRate; } public static Audio newAudio(String filename) { if (filename.toLowerCase().endsWith(".wav")) return WavReader.read(filename); else return null; // unsupported format } } 

Let's go through Listing 1 step by step.

  • The Audio.java file in Listing 1 stores the Audio class. This listing begins with a package statement that identifies ca.javajeff.audio as the class's package.
  • Audio is declared public so that it can be referenced from outside of its package. Also, it's declared final so that it cannot be extended (meaning, subclassed).
  • Audio declares privatesamples and sampleRate fields to store audio data. These fields are initialized to the values passed to Audio's constructor.
  • Audio's constructor is declared package-private (meaning, the constructor isn't declared public, private, or protected) so that this class cannot be instantiated from outside of its package.
  • Audio presents getSamples() and getSampleRate() methods for returning an audio clip's samples and sample rate. Each method is declared public so that it can be called from outside of Audio's package.
  • Audio concludes with a public and staticnewAudio() factory method for returning an Audio object corresponding to the filename argument. If the audio clip cannot be obtained, null is returned.
  • newAudio() compares filename's extension with .wav (this example only supports WAV audio). If they match, it executes return WavReader.read(filename) to return an Audio object with WAV-based audio data.

Listing 2 describes WavReader.

Listing 2. The WavReader helper class (WavReader.java)

 package ca.javajeff.audio; final class WavReader { static Audio read(String filename) { // Read the contents of filename's file and process it // into an array of sample values and a sample rate // value. If the file cannot be read, return null. For // brevity (and because I've yet to discuss Java's // file I/O APIs), I present only skeletal code that // always returns an Audio object with default values. return new Audio(new int[0], 0); } } 

WavReader is intended to read a WAV file's contents into an Audio object. (The class will eventually be larger with additional private fields and methods.) Notice that this class isn't declared public, which makes WavReader accessible to Audio but not to code outside of the ca.javajeff.audio package. Think of WavReader as a helper class whose only reason for existence is to serve Audio.

Complete the following steps to build this library:

  1. Select a suitable location in your file system as the current directory.
  2. Create a ca/javajeff/audio subdirectory hierarchy within the current directory.
  3. Copy Listings 1 and 2 to files Audio.java and WavReader.java, respectively; and store these files in the audio subdirectory.
  4. Assuming that the current directory contains the ca subdirectory, execute javac ca/javajeff/audio/*.java to compile the two source files in ca/javajeff/audio. If all goes well, you should discover Audio.class and WavReader.class files in the audio subdirectory. (Alternatively, for this example, you could switch to the audio subdirectory and execute javac *.java.)

Now that you've created the audio library, you'll want to use it. Soon, we'll look at a small Java application that demonstrates this library. First, you need to learn about the import statement.

Java's import statement

Imagine having to specify ca.javajeff.graphics.Triangle for each occurrence of Triangle in source code, repeatedly. Java provides the import statement as a convenient alternative for omitting lengthy package details.

The import statement imports types from a package by telling the compiler where to look for unqualified (no package prefix) type names during compilation. It appears near the top of a source file and must conform to the following syntax:

 import identifier[.identifier]*.(typeName | *); 

An import statement starts with reserved word import and continues with an identifier, which is optionally followed by a period-separated sequence of identifiers. A type name or asterisk (*) follows, and a semicolon terminates this statement.

The syntax reveals two forms of the import statement. First, you can import a single type name, which is identified via typeName. Second, you can import all types, which is identified via the asterisk.

The * symbol is a wildcard that represents all unqualified type names. It tells the compiler to look for such names in the right-most package of the import statement's package sequence unless the type name is found in a previously searched package. Note that using the wildcard doesn't have a performance penalty or lead to code bloat. However, it can lead to name conflicts, which you will see.

For example, import ca.javajeff.graphics.Triangle; tells the compiler that an unqualified Triangle class exists in the ca.javajeff.graphics package. Similarly, something like

 import ca.javajeff.graphics.*; 

tells the compiler to look in this package when it encounters a Triangle name, a Circle name, or even an Account name (if Account has not already been found).

Avoid the * in multi-developer projects

เมื่อทำงานในโครงการที่มีผู้พัฒนาหลายรายให้หลีกเลี่ยงการใช้*สัญลักษณ์แทนเพื่อให้นักพัฒนารายอื่นสามารถดูได้ว่าประเภทใดที่ใช้ในซอร์สโค้ดของคุณ