Paketi i statički uvoz u Javi

U mojem prethodnom uputstvu za Java 101 naučili ste kako bolje organizirati svoj kôd deklarirajući referentne tipove (također poznate kao klase i sučelja) kao članove drugih referentnih tipova i blokova. Također sam vam pokazao kako koristiti gniježđenje kako biste izbjegli sukobe imena između ugniježđenih referentnih tipova i tipova referenci najviše razine koji dijele isto ime.

Zajedno s gniježđenjem, Java koristi pakete za rješavanje problema s istim imenom u referentnim vrstama najviše razine. Korištenje statičkog uvoza također pojednostavljuje pristup statičkim članovima u zapakiranim referentnim vrstama najviše razine. Statični uvoz uštedjet će vam pritiske tipki prilikom pristupa tim članovima u kodu, ali ima nekoliko stvari na koje morate paziti kada ih koristite. U ovom uputstvu upoznat ću vas s korištenjem paketa i statičkim uvozom u vašim Java programima.

preuzimanje Preuzmite kod Preuzmite izvorni kod za primjere aplikacija u ovom Java tutorialu. Stvorio Jeff Friesen za JavaWorld.

Referentne vrste ambalaže

Programeri Java grupiraju srodne klase i sučelja u pakete. Korištenje paketa olakšava pronalaženje i upotrebu referentnih tipova, izbjegavanje sukoba imena između istoimenih tipova i kontrolu pristupa tipovima.

U ovom ćete odjeljku saznati više o paketima. Saznat ćete koji su paketi, saznati više o iskazima packagei importiskazima i istražiti dodatne teme zaštićenog pristupa, JAR datoteke i pretraživanja tipa.

Što su paketi u Javi?

U razvoju softvera stavke obično organiziramo prema njihovim hijerarhijskim odnosima. Na primjer, u prethodnom uputstvu pokazao sam vam kako prijaviti klase kao članove drugih klasa. Sustave datoteka možemo koristiti i za gniježđenje direktorija u drugim direktorijima.

Korištenje ovih hijerarhijskih struktura pomoći će vam u izbjegavanju sukoba imena. Na primjer, u nehijerarhijskom datotečnom sustavu (jedan direktorij) nije moguće dodijeliti isto ime više datoteka. Suprotno tome, hijerarhijski sustav datoteka omogućuje datotekama istog imena da postoje u različitim direktorijima. Slično tome, dvije zatvorene klase mogu sadržavati istoimenovane ugniježđene klase. Sukobi imena ne postoje jer su stavke podijeljene u različite prostore imena.

Java nam također omogućuje da particioniramo referentne tipove najviše razine (ne ugniježđene) u više prostora imena kako bismo mogli bolje organizirati te tipove i spriječiti sukobe imena. U Javi koristimo značajku jezika paketa za particioniranje referentnih tipova najviše razine u više prostora imena. U ovom je slučaju paket jedinstveni prostor imena za pohranu referentnih tipova. Paketi mogu pohraniti klase i sučelja, kao i potpakete, koji su paketi ugniježđeni unutar ostalih paketa.

Paket ima ime, koje mora biti nerezervirani identifikator; npr java. Operator pristupa članom ( .) razdvaja naziv paketa od naziva podpaketa i odvaja naziv paketa ili podpaketa od imena tipa. Na primjer, dvočlani operatori pristupa u java.lang.Systemodvojenom nazivu paketa javaod naziva langpodpaketa i odvojenom nazivu podpaketa langod naziva Systemtipa.

Referentne vrste moraju se deklarirati publickao dostupne izvan njihovih paketa. Isto se odnosi na bilo koje konstante, konstruktore, metode ili ugniježđene tipove kojima moraju biti dostupni. Primjere za njih vidjet ćete kasnije u vodiču.

Izjava o paketu

U Javi koristimo izjavu o paketu za izradu paketa. Ova se izjava pojavljuje na vrhu izvorne datoteke i identificira paket kojem pripadaju vrste izvornih datoteka. Mora odgovarati sljedećoj sintaksi:

 package identifier[.identifier]*; 

Izjava o paketu započinje rezerviranom riječju, packagea nastavlja se identifikatorom, koji je po želji slijedom razdoblja odvojenih identifikatora. Tačka i zarez ( ;) završava ovu izjavu.

Prvi (krajnji lijevi) identifikator imenuje paket, a svaki sljedeći identifikator imenuje podpaket. Na primjer, u package a.b;, svi tipovi deklarirani u izvornoj datoteci pripadaju bpodpaketu apaketa.

Konvencija o imenovanju paketa / potpaketa

Prema dogovoru, naziv paketa ili potpaketa izražavamo malim slovima. Kad se ime sastoji od više riječi, možda biste željeli napisati velikim slovima svaku riječ, osim prve; npr generalLedger.

Slijed naziva paketa mora biti jedinstven kako bi se izbjegli problemi s kompilacijom. Na primjer, pretpostavimo da izradite dva različita graphicspaketa i pretpostavite da svaki graphicspaket sadrži Triangleklasu s različitim sučeljem. Kada Java kompajler naiđe na nešto poput onoga što je dolje, mora provjeriti Triangle(int, int, int, int)postoji li konstruktor:

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

Okvir za trokut

Zamislite Trianglekonstruktor kao određivanje graničnog okvira u kojem ćete nacrtati trokut. Prva dva parametra identificiraju gornji lijevi kut okvira, a druga dva parametra definiraju opseg okvira.

Prevoditelj će pretraživati ​​sve dostupne pakete dok ne pronađe graphicspaket koji sadrži Triangleklasu. Ako pronađeni paket uključuje odgovarajuću Triangleklasu s Triangle(int, int, int, int)konstruktorom, sve je u redu. Inače, ako pronađena Triangleklasa nema Triangle(int, int, int, int)konstruktor, kompajler prijavljuje pogrešku. (O algoritmu pretraživanja reći ću kasnije u ovom vodiču.)

Ovaj scenarij ilustrira važnost odabira jedinstvenih sljedova naziva paketa. Dogovor za odabir jedinstvenog slijeda imena je preokrenuti naziv vaše internetske domene i koristiti ga kao prefiks za niz. Na primjer, odabrao bih ca.javajeffkao svoj prefiks jer javajeff.caje moje ime domene. Tada bih odredio ca.javajeff.graphics.Trianglepristup Triangle.

Komponente imena domene i važeći nazivi paketa

Komponente imena domene nisu uvijek valjana imena paketa. Jedno ili više imena komponenata može počinjati znamenkom ( 3D.com), sadržavati crticu ( -) ili drugi nedozvoljeni znak ( ab-z.com) ili biti jedna od Java rezerviranih riječi ( short.com). Konvencija nalaže da cifri dodate predizbornik ( com._3D), nedozvoljeni znak zamijenite podvlakom ( com.ab_z), a rezerviranu riječ sufiksirajte podvlakom ( com.short_).

Morate slijediti nekoliko pravila kako biste izbjegli dodatne probleme s izjavom o paketu:

  1. U izvornoj datoteci možete prijaviti samo jedan izraz paketa.
  2. Izvješću o paketu ne možete prethoditi ništa osim komentara.

Prvo pravilo, koje je poseban slučaj drugog pravila, postoji jer nema smisla pohraniti referentni tip u više paketa. Iako paket može pohraniti više vrsta, tip može pripadati samo jednom paketu.

Kada izvorna datoteka ne deklarira izjavu o paketu, kaže se da vrste izvorne datoteke pripadaju neimenovanom paketu . Netrivijalne referentne vrste obično se pohranjuju u vlastitim paketima i izbjegavaju neimenovani paket.

Java implementacije mapiraju nazive paketa i potpaketa u istoimene direktorije. Na primjer, implementacija bi se preslikala graphicsu direktorij s imenom graphics. U slučaju paketa a.b, prvo slovo, a preslikalo bi se u imenovani direktorij, aa b bi se preslikalo u bpoddirektorij a. Prevoditelj pohranjuje datoteke klase koje implementiraju tipove paketa u odgovarajući direktorij. Imajte na umu da neimenovani paket odgovara trenutnom direktoriju.

Primjer: Pakiranje audioteke u Javi

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

Kada radite na projektu za više razvojnih programera, izbjegavajte upotrebu *zamjenskog znaka kako bi drugi programeri mogli lako vidjeti koje se vrste koriste u vašem izvornom kodu.