Пакеты

From AsIsWiki
Jump to: navigation, search
Форум

Назад | Оглавление | Дальше


Contents

Пакеты

Java позволяет объединять классы в наборы, называемые package (пакеты). Пакеты облегчают организацию работы и позволяют отделить классы, созданные одним разработчиком, от классов, разработанных другими программистами.

Стандартная библиотека Java содержит большое количество пакетов: java.lang, java.util, java.net и др. Стандартные пакеты Java представляют собой иерархические структуры. Подобно каталогам, пакеты могут быть вложены один в другой. Все стандартные пакеты принадлежат иерархиям java и javax.

В основном, пакеты используются для обеспечения уникальности имен классов. Предположим, двух программистов осенила идея создать класс Employee. Если оба класса будут находиться в разных пакетах, конфликт не возникнет. Для обеспечения абсолютной уникальности имени пакета, компания Sun рекомендует использовать доменное имя разработчика в Internet, записанное в обратном порядке. В составе пакета можно создавать подпакеты и использовать их в разных проектах. Например, имя пакета может быть таким: org.asistech. В дальнейшем, в этот пакет можно добавить подпакет: org.asistech.math3d

Единственная цель подпакетов - гарантия уникальности имен. С точки зрения компилятора между ними нет никакой связи. Например, пакеты java.util и java.util.jar никак не связаны друг с другом. Каждый из них представляет собой независимую коллекцию классов.


Импортирование классов

Класс может использовать все классы из собственного пакета и все общедоступные классы из других пакетов.

Доступ к классам из других пакетов можно получить двумя путями. Во-первых, можно указать полное имя пакета перед именем каждого класса, например:

java.util.Date today = new java.util.Date();

Более простой способ - использовать ключевое слово import. В этом случае имя пакета перед именем класса записывать не обязательно.

Импортировать можно как один конкретный класс, так и весь пакет. Оператор import следует поместить в начало исходного файла (после всех операторов package). Например, импорт всех классов из пакета java.util выглядит так:

import java.util.*;

После этого, имя пакета в строке кода не указывается:

Date today = new Date();

Можно также импортировать отдельный класс из пакета:

import java.util.Date;

Импортировать все классы проще. На размер кода это не влияет. Но, если явно указать импортируемый класс, становится ясно, какие именно классы использованы в программе.

Работая с Eclipse, можно использовать пункт меню Source->Organize Import. В результате выражения типа import java.util.*; будут преобразованы в последовательности строк, предназначенных для импортирования отдельных классов:

import java.util.ArrayList;

import java.util.Date;

Оператор import со звездочкой можно применять для импортирования только одного пакета. Нельзя использовать обозначение import java.* или import java.*.*, для импорта всех пакетов, имена которых содержат префикс java.

Обычно импортируется весь пакет, независимо от его размера. Единственный случай, при котором на пакет следует обратить особое внимание - конфликт имен. Например, пакеты java.util и java.sql содержат класс Date. Допустим, в программе импортируются оба пакета:

import java.util.*;

import java.sql.*;

Попытка использовать класс Date приведет к ошибке компиляции:

Date today;  // Error -- java.util.Date or java.sql.Date?

Компилятор не может определить, какой класс Date вам нужен. Эта проблема решается импортированием конкретного класса:

import java.util.*;

import java.sql.*;

import java.util.Date;

Если требуются оба класса Date, то следует указать полное имя пакета для каждого из них:

java.util.Date deadline = new java.util.Date();

java.sql.Date today = new java.sql.Date();

Обнаружение классов в пакетах является задачей компилятора. В файлах классов находятся ссылки, содержащие полные имена пакетов.

Программисты C++, считают, что оператор import является аналогом директивы #include. Но между ними нет ничего общего. Директива #include обязательна, поскольку компилятор C++ не просматривает файлы, за исключением компилируемого, и файлов, указанных в директиве #include. Java-компилятор "видит" содержимое всех файлов, при условии, что он "знает", где их искать.

В Java можно не применять импорт, явно указывая пакеты в теле класса. В C++ избежать использования директивы #include нельзя.

Единственное преимущество оператора import - удобство. Он позволяет использовать более короткие имена классов, не указывая полное имя пакета. Например, после оператора import java.util.* (или import java.util.Date) на класс java.util.Date можно ссылаться по имени Date.

Механизм работы с пакетами в C++ реализован в виде директивы namespace. Ключевые слова package и import в Java можно считать аналогами директив namespace и using в C++.


Импортирование статических методов и полей

В JDK 5.0 выражение import было расширено. Теперь можно импортировать классы, статические методы и поля. Например, в начале исходника размещена следующая строка:

import static java.lang.System.*;

Теперь можно использовать статические методы и поля, определенные в классе System, не указывая имена класса:

out.println("Goodbye, World!");  // вместо System.out

exit(0);                         // вместо System.exit

Аналогично импортируются статические методы или поля:

import static java.lang.System.out;

Не каждый захочет сокращать такие выражения, как System.out или System.exit. Полученный в результате код станет более сложным для восприятия. Однако, новая возможность полезна как минимум в двух случаях:

  • Если импортируются статические методы класса Math, то можно непосредственно задавать в программе имена математических функций:
    sqrt(pow(x, 2) + pow(y, 2))
    
    Что выглядит гораздо естественнее, чем выражение:
    
    Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
    
  • Если используется множество различных констант:
    if (d.get(DAY_OF_WEEK) == MONDAY)
    
    Что проще для восприятия, чем строка:
    
    if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY)
    


Добавление классов в пакеты

Чтобы поместить класс в пакет, нужно указать имя пакета в начале исходного файла перед определением класса, например:

package com.horstmann.corejava;

public class Employee {
    ...
}

Если оператор package в исходном файле не указан, то классы, описанные в этом файле, помещаются в default package (пакет по умолчанию). Пакет по умолчанию не имеет имени. Все рассмотренные выше классы принадлежали пакету по умолчанию.

Пакеты следует помещать в каталог, путь к которому соответствует полному имени пакета. Например, все файлы пакета com.horstmann.corejava лежат в каталоге com/horstmann/corejava.

Программы, приведенные ниже, распределены по двум пакетам: класс PackageTest принадлежит пакету по умолчанию, а класс Employee - пакету com.horstmann.corejava. Следовательно, файл Employee.class должен находиться в каталоге com/horstmann/corejava:

.(базовый каталог)
  !-- PackageTest.java
  !-- PackageTest.class
  !-- com/
       !-- horstmann/
            !-- corejava/
	         !-- Employee.java
		 !-- Employee.class

Для компиляции программы, следует перейти в каталог с файлом PackageTest.java, и выполнить команду:

javac Package.java

Компилятор находит файл com/horstmann/corejava/Employee.java и компилирует его.

Если пакет по умолчанию не используется, а классы распределены по разным пакетам:

.(базовый каталог)
  !-- com/
       !-- horstmann/
       !    !-- corejava/
       !         !-- Employee.java
       !	 !-- Employee.class
       !-- mycompany/
            !-- PayrollApp.java
	    !-- PayrollApp.class

то компиляция осуществляется из базового каталога, в котором содержится подкаталог com:

javac com/mycompany/PayrollApp.java

java com.mycompany.PayrollApp

Компилятор работает с файлами (при указании файла задается путь и расширение .java), а интерпретатор оперирует классами (для класса указывается пакет).

Компилятор не проверяет структуру каталогов. Предположим, например, что исходный файл начинается со следующей директивы:

package com.mycompany;

Этот файл можно скомпилировать, даже если он не находится в каталоге com/mycompany. Исходный файл будет скомпилирован без ошибок. Однако, при попытке выполнить эту программу, виртуальная машина не найдет классы, полученные в результате компиляции.

Следующие два класса демонстрируют работу с пакетами.

Файл PackageTest/PackageTest.java:

import com.horstmann.corejava.*;  // класс Employee определен в этом пакете

import static java.lang.System.*;

public class PackageTest {

    public static void main(String[] args) {
        // т.к. пакет импортирован, то можно не указывать полный путь
        // к классу com.horstmann.corejava.Employee
        Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);

        // повышаем зарплату на 5%
        harry.raiseSalary(5);

        // выводим информацию о harry, используя java.lang.System.out
        out.println("name="
                + harry.getName()
                + ",salary="
                + harry.getSalary());
    }
}

Файл PackageTest/com/horstmann/corejava/Employee.java:

package com.horstmann.corejava;  // the classes in this file are part of this package

import java.util.*;  // import statements come after the package statement

public class Employee {
    
    public Employee(String n, double s, int year, int month, int day) {
        name = n;
        salary = s;
        // GregorianCalendar uses 0 for January
        GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
        hireDay = calendar.getTime();
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }

    public Date getHireDay() {
        return hireDay;
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }
    
    private String name;
    private double salary;
    private Date hireDay;
}


Как виртуальная машина определяет, где находятся классы

Классы хранятся в каталогах файловой системы. Путь к классу должен совпадать с именем пакета. Для размещения классов в архиве, используется утилита jar. В одном архиве может содержаться множество файлов классов и каталогов, что позволяет экономить память и сокращает время доступа к ним.

Например, в подкаталоге jre/lib набора инструментальных средств JDK, находится файл rt.jar, содержащий тысячи классов рабочей библиотеки.

Для организации файлов и каталогов в файлах JAR используется формат ZIP. Обработать rt.jar и другие JAR-файлы можно с помощью любой утилиты, поддерживающей ZIP.

Каталог com/horstmann/corejava содержащий файлы пакета, не очень удобен. Как правило, к файлам пакета должны обращаться различные программы. Для обеспечения совместного доступа к пакету, необходимо сделать следующее:

  • Поместить классы в один или несколько каталогов, например:
    /home/user/classdir
    

    Этот каталог является базовым по отношению к дереву пакета. Если нужно добавить класс com.horstmann.corejava.Employee, то файл класса необходимо разместить в каталоге

    /home/user/classdir/com/horstmann/corejava.
    
  • Установить путь к классу. Путь к классу - это набор всех базовых каталогов, которые могут содержать файлы классов.

Установка пути к классу зависит от среды, в которой компилируется программа. Если используется пакет JDK, есть две возможности:

  • указывать при вызове компилятора и интерпретатора опцию - classpath;
  • задать значение переменной окружения CLASSPATH.

В среде Unix, элементы пути к классу отделяются друг от друга двоеточиями:

/home/user/classdir:.:/home/user/archives/archive.jar

В Windows они разделяются точками с запятой:

c:\classes;.;c:\archives\archive.jar

В обоих случаях точка означает текущий каталог.

Путь к классу содержит:

  • имя базового каталога /home/user/classdir или c:\classes;
  • обозначение текущего каталога (.);
  • имя jar-файла /home/user/archives/archive.jar или c:\archives\archive.jar.

Поиск классов всегда выполняется в файлах рабочей библиотеки (rt.jar и других jar-файлах в каталогах jrelib и jre/lib/ext). Имена этих файлов явно указывать не обязательно.

В JDK 1.0 и 1.1 системные классы хранились в файле classes.zip, который указывался в составе пути к классам.

Установка пути к классам с помощью опции компилятора:

javac -classpath /home/user/classdir:.:/home/user/archives archive.jar MyProg.java

В Windows для отделения элементов пути друг от друга используется точка с запятой.

Опцию -classpath можно заменить на -cp. До JDK 5.0, эта опция распознавалась только интерпретатором java. При работе с компилятором javac требовалась опция -classpath.

В составе пути перечисляются все каталоги и архивные файлы, рассматриваемые как стартовые точки при поиске классов. Рассмотрим простой путь:

/home/user/classdir:.:/home/user/archives/archive.jar

Предположим, что интерпретатор ищет файл класса com.horstmann.corejava.Employee. Сначала он проверяет архивы системных файлов, находящихся в каталогах jre/lib и jre/lib/ext. Не обнаружив искомого класса, интерпретатор анализирует пути:

  • /home/user/classdir/com/horstmann/corejava/Employee.class
  • com.horstmann/corejava/Employee.class, начиная с текущего каталога.
  • com.horstmann/corejava/Employee.class в каталоге /home/user/archives/archive.jar

Компилятор javac всегда ищет файлы в текущем каталоге, а интерпретатор java обращается к текущему каталогу, только если в пути указана точка ".". Если путь не указан, никаких проблем не возникает - по умолчанию в него включается текущий каталог ".". Но если путь задан, а точка не указана, то программа скомпилируется без ошибок, но работать не сможет.

Путь к классам можно задать с помощью опции -classpath, указываемой при вызове программ javac и java. Но такой способ не очень удобен. Можно воспользоваться альтернативной возможностью и задать значение переменной окружения CLASSPATH:

  • В среде Unix редактируем файл с параметрами оболочки. Если используется оболочка C, ищем в рабочем каталоге файл .cshrc, и добавляем в него строку:
    setnv CLASSPATH /home/user/classdir:
    

    Если используется оболочка Bourne Again, или bash, ищем в рабочем каталоге файл .bashrc или .bash_profile, и добавляем в него строку:

    export CLASSPATH=/home/user/classdir:
    
  • В среде Windows 95/98/Me редактируем файл autoexec.bat:
    SET CLASSPATH=c:\user\classdir;
    

    Слева и справа от символа "=" не должно быть пробелов.

  • В среде Windows XP открываем панель управления; выбираем пиктограмму System и вкладку Environment. В поле Variable добавляем/изменяем переменную CLASSPATH:
    c:\user\classdir;
    


Область видимости пакета

Элементы с модификатором public могут использоваться любым классом. Элементы, определенные как private, могут использоваться только тем классом, в котором они были определены. Если модификаторы доступа не указаны, то элемент (класс, метод или переменная) доступен всем методам из одного пакета.

В примере из раздела Добавление классов в пакеты, класс Employee не имеет модификатора доступа. Поэтому, он становится доступен всем классам "своего" пакета, - например классу EmployeeTest. Для классов такой подход вполне разумен. Но для переменных этот способ доступа неудачен.

Переменные следует явно определять как private, или по умолчанию их область видимости будет расширена до пределов пакета. А это нарушает принцип инкапсуляции. В процессе работы, программисты часто забывают указать модификатор private. Вот пример из класса Window, принадлежащего пакету java.awt, который поставляется с пакетом JDK:

public class Window extends Container {
    String warningString;
    ...
}

Переменная warningString не имет модификатора доступа private! И метод любого класса из пакета java.awt может изменить ее значение (например, присвоить ей строку Trust me!).

Фактически, все методы, имеющие доступ к warningString, принадлежат классу Window. Значит, эту переменную можно смело объявить как закрытую. Похоже, что программисты, писавшие этот код, торопились и просто забыли указать модификатор private.

Со временем, в класс Window были добавлены новые поля, и снова половина из них не была объявлена как private.

Может это и не проблема, однозначно ответить сложно. По умолчанию пакеты не являются закрытыми, и каждый может добавлять в них свои классы. Хакер может модифицировать переменные, область видимости которых ограничена пакетом. В ранних версиях Java для проникновения в любой класс пакета java.awt, достаточно было начать свой класс строкой:

package java.awt;

Далее, класс помещался в каталог java/awt, и доступ к содержимому пакета java.awt открыт.

В версии JDK 1.2 создатели пакета запретили загрузку пользовательских классов, если их имя начиналось с java. Эта защита не распространяется на ваши собственные классы. Более того, используя герметизацию пакета - package sealing, можно ограничить к нему доступ, запретив добавлять в пакет новые классы.

Далее будет показано, как создать jar-файл, содержащий герметичные пакеты.



Форум

Назад | Оглавление | Дальше

Personal tools
Namespaces

Variants
Actions
Navigation
Tools