חבילות וייבוא ​​סטטי בג'אווה

במדריך הקודם שלי ב- Java 101 למדת כיצד לארגן טוב יותר את הקוד שלך על ידי הצהרת סוגי התייחסות (המכונים גם מחלקות וממשקים) כחברים מסוגים ובלוקים אחרים. הראיתי לך גם כיצד להשתמש בקינון כדי למנוע התנגשויות שם בין סוגי התייחסות מקוננים לבין סוגי התייחסות ברמה העליונה החולקים את אותו שם.

לצד הקינון, Java משתמשת בחבילות כדי לפתור בעיות בעלות שם זהה בסוגי הפניה ברמה העליונה. שימוש בייבוא ​​סטטי מפשט גם את הגישה לחברים הסטטיים בסוגי התייחסות ארוזים ברמה העליונה. יבוא סטטי יחסוך לך הקשות בעת גישה לחברים אלה בקוד שלך, אך יש להיזהר מכמה דברים כאשר אתה משתמש בהם. במדריך זה אכיר לכם שימוש בחבילות וייבוא ​​סטטי בתוכניות הג'אווה שלכם.

הורד קבל את הקוד הורד את קוד המקור למשל יישומים במדריך זה של Java. נוצר על ידי ג'ף פרייזן עבור JavaWorld.

סוגי התייחסות לאריזות

מפתחי Java מקבצים שיעורים וממשקים קשורים לחבילות. השימוש בחבילות מקל על איתור ושימוש בסוגי הפניה, הימנעות מהתנגשויות שמות בין סוגים בעלי שם זהה ושליטה בגישה לסוגים.

בחלק זה תוכלו ללמוד על חבילות. אתה תמצא את מה חבילות הן, ללמוד על packageואת importההצהרות, ולחקור את הנושאים הנוספים של גישה מוגנת, קבצי JAR, וחיפושי סוג.

מהן חבילות בג'אווה?

בפיתוח תוכנה אנו בדרך כלל מארגנים פריטים על פי מערכות היחסים ההיררכיות שלהם. לדוגמא, במדריך הקודם הראיתי לך כיצד להכריז על שיעורים כחברים בכיתות אחרות. אנו יכולים גם להשתמש במערכות קבצים כדי לקנן ספריות בספריות אחרות.

שימוש במבנים היררכיים אלה יעזור לך למנוע התנגשויות בשמות. לדוגמא, במערכת קבצים לא היררכית (ספריה אחת), לא ניתן להקצות את אותו שם למספר קבצים. לעומת זאת, מערכת קבצים היררכית מאפשרת לקבצים בעלי שם זהה להתקיים בספריות שונות. באופן דומה, שתי כיתות סגורות יכולות להכיל כיתות מקוננות זהות. התנגשויות שמות אינן קיימות מכיוון שהפריטים מחולקים למרחבי שמות שונים.

ג'אווה מאפשרת לנו גם לחלק סוגי התייחסות ברמה העליונה (לא מקוננת) למרחבי שמות מרובים כדי שנוכל לארגן טוב יותר את הסוגים הללו ולמנוע התנגשויות שם. ב- Java אנו משתמשים בתכונת שפת החבילה כדי לחלק סוגי הפניות ברמה העליונה למרחבי שמות מרובים. במקרה זה, חבילה היא מרחב שמות ייחודי לאחסון סוגי הפניה. חבילות יכולות לאחסן שיעורים וממשקים, כמו גם חבילות משנה, שהן חבילות המקוננות בתוך חבילות אחרות.

לחבילה יש שם, שחייב להיות מזהה שאינו שמור; למשל java,. מפעיל הגישה לחברים ( .) מפריד בין שם חבילה לשם חבילה משנה ומפריד בין שם חבילה או שם חבילה לשם סוג. לדוגמה, מפעילי גישה דו-חבר java.lang.Systemבשם חבילה נפרדת javaמן langשם מחבילת משנה ו שם מחבילת משנה נפרד langמן Systemשם סוג.

יש להכריז על סוגי הפניה publicכנגישים מחוץ לחבילות שלהם. הדבר תקף לכל קבועים, קונסטרוקטורים, שיטות או סוגים מקוננים אשר חייבים להיות נגישים. תראה דוגמאות להלן בהדרכה.

הצהרת החבילה

בג'אווה אנו משתמשים בהצהרת החבילה כדי ליצור חבילה. הצהרה זו מופיעה בראש קובץ מקור ומזהה את החבילה אליה שייכים סוגי קבצי המקור. עליו להתאים לתחביר הבא:

 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.Triangleגישה Triangle.

רכיבי שמות מתחם ושמות חבילה תקפים

רכיבי שמות מתחם אינם תמיד שמות חבילות חוקיים. שמות רכיבים אחד או יותר עשויים להתחיל בספרה ( 3D.com), להכיל מקף ( -) או תו לא חוקי אחר ( ab-z.com), או להיות אחת ממילותיו השמורות של Java ( short.com). האמנה מכתיבה שתקדם את הספרה עם קו תחתון ( com._3D), תחליף את התו הלא חוקי עם קו תחתון ( com.ab_z), ותספק את המילה השמורה עם קו תחתון ( com.short_).

עליך לעקוב אחר כמה כללים כדי להימנע מבעיות נוספות בהצהרת החבילה:

  1. אתה יכול להכריז רק על הצהרת חבילה אחת בקובץ המקור.
  2. אינך יכול להקדים את הצהרת החבילה בשום דבר מלבד הערות.

הכלל הראשון, שהוא מקרה מיוחד של הכלל השני, קיים מכיוון שלא הגיוני לאחסן סוג הפניה במספר חבילות. למרות שחבילה יכולה לאחסן מספר סוגים, סוג יכול להיות שייך לחבילה אחת בלבד.

כאשר קובץ מקור אינו מכריז על הצהרת חבילה, נאמר כי סוגי קובץ המקור שייכים לחבילה ללא שם . סוגי התייחסות לא טריוויאליים נשמרים בדרך כלל בחבילות משלהם ונמנעים מהחבילה ללא שם.

יישומי Java ממפים חבילות ושמות של אריזות משנה לספריות בעלות שם זהה. לדוגמא, יישום יתמפה graphicsלספרייה בשם graphics. במקרה של החבילה a.b, האות הראשונה, a ימפה לספרייה בשם aו- b ימפה bלספריית משנה של a. המהדר שומר את קבצי הכיתה המיישמים את סוגי החבילה בספריה המתאימה. שים לב שהחבילה ללא שם תואמת לספריה הנוכחית.

דוגמה: אריזת ספריית שמע בג'אווה

דוגמה מעשית מועילה לתפיסה מלאה של packageההצהרה. בחלק זה אני מדגים חבילות בהקשר של ספריית שמע המאפשרת לך לקרוא קבצי שמע ולקבל נתוני שמע. בקיצור, אציג רק גרסת שלד לספריה.

ספריית האודיו מורכבת כיום משני שיעורים בלבד: Audioו WavReader. Audioמתאר קליפ שמע והוא המעמד הראשי של הספרייה. רישום 1 מציג את קוד המקור שלו.

רישום 1. דוגמה להצהרת חבילה (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 } } 

בואו נעבור רישום 1 שלב אחר שלב.

  • Audio.javaקובץ מודעת 1 מאחסן את Audioהכיתה. רישום זה מתחיל בהצהרת חבילה שמזוהה ca.javajeff.audioכחבילה של הכיתה.
  • 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

כשעובדים על פרויקט רב-מפתח, הימנע משימוש *בתו כללי כדי שמפתחים אחרים יוכלו לראות בקלות אילו סוגים משתמשים בקוד המקור שלך.