sonyps4.ru

Можно ли создать объект интерфейса java. Создание своих интерактивных компонентов

public void paint(Graphics g)

// Рисование изображения типа Image

g.drawlmage(picture, 35, 35, this ); // this -ссылка апплета на себя

Когда требуется устранить неоднозначность при использовании двух переменных с одним именем, одна из которых является полем класса, другая - локальной переменной.

Обращение к полю класса в этом случае: this.имя_поля_класса

 super - используется как ссылка на экземпляр суперклассас целью обеспечения доступа к нестатическимполям и методам суперкласса.

Доступ кполю суперкласса обеспечивается выражением:super.имя_поля_класса

class ClassA { float x;

class ClassB extends ClassA { int x;

public void method l(int x) {

int iX1 = x; // присваивание значения параметра метода int iX2 = this.x; // присваивание значения поля данного класса float fX = super.x; // присваивание значения поля суперкласса

Доступ к методу суперкласса обеспечивается выражением:super.имя_метода_класса()

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

public void method2() {...} ...

class ClassB extends ClassA {

public void method2() { super.method2();

Использование this и super в конструкторах - см. пример ConstrDemo.java с комментариями.

Интерфейсы в Java

Технология множественного наследования , реализованная в языке C++, позволяет некоторому классу наследовать различные наборы свойств от нескольких суперклассов, однако создает множество проблем для разработчиков компиляторов, часто служит причиной появления в программах скрытых ошибок. При разработке Java специалисты Sun предпочли исключить из этого языка возможность прямого множественного наследования классов и использоватьинтерфейсы .

Интерфейс - это подобие абстрактного класса, особый вид класса без реализации. Интерфейс

реализуется классом.

Объекты одного класса используют общее поведение (методы). Интерфейс позволяетдополнить описание поведения объекта новыми функциональными возможностями, объявляя набор методов, которые должен поддерживать класс,

реализующий данный интерфейс. Реализация методов осуществляется в классе, а не в интерфейсе.

Скачано с сайта http://ivc.clan.su

Интерфейс представляет собой набор методов без реализации (без тел), который впоследствии может бытьреализован классом.В интерфейсе указывается, что некоторый класс должен делать, но не

как он это делает . Для реализации интерфейса класс должен определить полный набор методов,

которые объявлены в интерфейсе . При этом класс может реализовывать методы интерфейса любым способом, наиболее предпочтительными точки зрения разработчика класса.

Класс в Java может реализовывать несколько интерфейсов, и один интерфейс -использоваться несколькими классами.

Таким образом, интерфейсы можно использовать для придания определенных функциональных возможностей (иными словами, способностей - "ability " - выполнять определенные функции) самым разнообразным классам:как классам,

которые исходно совершенно не связаны между собой, так и классам, связанным между собой иерархией наследования. В Java есть немало интерфейсов, названии которых имеется характерный суффикс " able " (например, Runnable).

Определение интерфейса , как и определение класса в Java, содержит два компонента:

объявление и тело.

Объявление интерфейса имеет вид:

Interface Имя_интерфейса *extends Список_суперинтерфейсов+

1. Модификаторы интерфейса

Public - спецификатордоступа - указывает, что к данному интерфейсу возможен доступ из других пакетов. В случаеотсутствия спецификатора public , доступ разрешен только внутри пакета, в котором интерфейс находится.

Подобно классам public, интерфейс public должен описываться в файле с именем Имя_интерфейса.jаvа.

Подразумевается, что каждый интерфейс имеет модификатор реализацииabstract ,который не указывается при объявлении .

2. Имя интерфейса

Формируются по тем же правилам, которые приняты для имен класса. Рекомендуется имя любого интерфейса, как и класса, начинать с прописной буквы!

3. Спецификация суперинтерфейсов

Суперинтерфейсы указываются ключевым словом extends .

Производный интерфейс не должен содержать описания реализаций не только собственных методов, но и методов суперинтерфейсов.

Любой класс, реализующий производный интерфейс, должен определить тела всех методов, объявленных как в данном производном интерфейсе, так и в суперинтерфейсе(ах).

Тело интерфейса заключено в,- и может включать только объявленияметодов и объявления констант (static final полей).

Объявления констант

Каждая переменная (поле) интерфейса по умолчанию считается переменной с модификаторамиpublic ,static иfinal . Эти модификаторы не обязательно указывать при объявлении, однако на практике полезно указывать их явно в качестве постоянного напоминания о статусе поля

(открытая статическая константа ) себе и другим программистам.

Инициализация констант выполняется при объявлении.

Поскольку константы являются открытыми, их можно использовать в любых классах, реализующих данный интерфейс. Если интерфейс объявлен какpublic , константы доступны любому классу, независимо от пакета.

Обращение к константе: Имя_интерфейса.Имя_константы

Объявления методов

Объявление метода, после которого ставится знак ";" , имеет вид:

= Integer.toString(i+1); series1Data[i] = new XYChart.Data(categories[i], 50); series1.getData().add(series1Data[i]); } bc.getData().add(series1);

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

Rectangle rectangle = new Rectangle(0, 0, 400, 200); Stop stops = new Stop { new Stop(0, new Color(0, 0, 0, 0.8)), null}; LinearGradient lg2 = new LinearGradient(0, 0, 0, 0, false, CycleMethod.NO_CYCLE, stops); rectangle.setFill(lg2); rectangle.setArcHeight(20); rectangle.setArcWidth(20);

Добавляем оба компонента к группе:

Root.getChildren().addAll(rectangle, bc);

Назначаем слушателей мыши к группе, чтобы двигать окно по экрану:

Root.setOnMousePressed(new EventHandler() { public void handle(MouseEvent me) { initX = me.getScreenX() - primaryStage.getX(); initY = me.getScreenY() - primaryStage.getY(); } }); root.setOnMouseDragged(new EventHandler() { public void handle(MouseEvent me) { primaryStage.setX(me.getScreenX() - initX); primaryStage.setY(me.getScreenY() - initY); } });

Загружаем песню в плеер:

File file = new File("выпусти меня отсюда.mp3"); Media audioMedia = null; audioMedia = new Media(file.toURI().toURL().toString()); audioMediaPlayer = new MediaPlayer(audioMedia);

Добавляем слушатель, который будет обновлять столбиковую диаграмму:

AudioMediaPlayer.setAudioSpectrumListener(new AudioSpectrumListener() { public void spectrumDataUpdate(double timestamp, double duration, float magnitudes, float phases) { for (int i = 0; i < series1Data.length; i++) { series1Data[i].setYValue(magnitudes[i] + 60); } } });

Делаем сцену видимой и запускаем песню:

PrimaryStage.show(); audioMediaPlayer.play();

Запускаем приложение:

Public static void main(String args) { launch(args); }

И наслаждаемся такой вот красотой.

В некоторых ситуациях, в процессе разработки программного обеспечения, возникает необходимость, когда разным группам программистов надо «договориться» о том, как их программы будут взаимодействовать. Каждая группа должна иметь возможность писать свой код не зависимо от того, как пишет его другая группа. Интерфейсы и есть этот «договор». Мы уже пользовались интерфейсами в уроке .
В этом уроке мы изучим их подробнее: для чего они нужны, как их объявлять и т.д.

Представьте себе будущее, где автомобилями управляют компьютеры без участия человека. Производители автомобилей пишут программное обеспечение (на Java, конечно 🙂), которое управляет машиной — остановиться, поехать, повернуть и т.д. Другие разработчики делают системы, которые принимают данные GPS (Global Positioning System) и используют эти данные для управления автомобилем. Производители автомобилей публикуют интерфейс-стандарт, который описывает методы для управления машиной. Таким образом сторонние разработчики могут знать какие методы вызывать, чтобы заставить автомобиль двигаться, а производители автомобилей могут изменять внутреннюю реализацию своего продукта в любое время. Ни одна из групп разработчиков не знает как написаны их программы.

Интерфейсы Java

Интерфейсы в Java во многом напоминают классы, но могут содержать только константы, сигнатуры методов и . В интерфейсах нет описания методов. Нельзя создать объект типа интерфейса (но можно использовать в качестве типа — интерфейсные ссылки, об этом позже) — интерфейсы могут только реализовываться некоторым классом или наследоваться другим интерфейсом. Объявление интерфейса очень похоже на объявление класса:

Public interface OperateCar { // константы, если есть // enum перечисление направлений RIGHT, LEFT // сигнатуры методов int turn(Direction direction, double radius, double startSpeed, double endSpeed); int changeLanes(Direction direction, double startSpeed, double endSpeed); int signalTurn(Direction direction, boolean signalOn); int getRadarFront(double distanceToCar, double speedOfCar); int getRadarRear(double distanceToCar, double speedOfCar); }

Сигнатуры методов объявляются без скобок и заканчиваются точкой с запятой. Для использования интерфейса вы должны написать класс, который будет реализовывать ваш интерфейс. Класс который реализует интерфейс, должен описывать все методы, объявленные в интерфейсе. Например:

Public class OperateBMW760i implements OperateCar { // здесь методы интерфейса OperateCar с описанием // например: int signalTurn(Direction direction, boolean signalOn) { // поворот RIGHT или LEFT поворотник on или off } }

В примере выше — производители автомобилей пишут программное обеспечение каждый по-своему, но с одинаковым интерфейсом. Сторонние разработчики — клиенты интерфейса, могут писать собственно ПО с использованием методов, объявленных в интерфейсе.

Интерфейсы в качестве API

Пример с автомобилями показывает как интерфейсы могут использоваться в качестве API (Application Programming Interface ) или интерфейса программирования приложений. Использование API — обычная практика при разработке коммерческого ПО. Обычно компании-разработчики продают программное обеспечение, содержащее набор методов, которые другие компании хотят использовать в своих продуктах.

Интерфейсы и множественное наследование

Интерфейсы играют еще одну важную роль в языке программировния Java. Java не разрешает множественного наследования, но интерфейсы предоставляют альтернативу. В Java класс может наследовать только один класс, но может реализовывать много интерфейсов. Таким образом объекты могут иметь несколько типов: тип их собственного класса и типы всех реализуемых интерфейсов. При создании объектов класса в качестве типа может указываться имя реализованного в классе интерфейса. Другими словами, если класс реализует интерфейс, то на объект этого класса можно присвоить интерфейсной переменной — переменной, в качестве типа которой указано имя соответствующего интерфейса.

Объявление интерфейса

Объявление интерфейса содержит ключевое слово interface , имя интерфейса, список родительских интерфейсов через запятую (если есть) и тело интерфейса. Например:

Public interface GroupedInterface extends Interface1, Interface2, Interface3 { // число e double E = 2.718282; // сигнатуры методов void doSomething (int i, double x); int doSomethingElse(String s); }

Модификатор доступа public означает что интерфейс может быть использован любым классом в любом пакете. Если не определить интерфейс как публичный, он будет доступен только в рамках своего пакета. Интерфейс может наследовать другие интерфейсы, как классы могут наследовать другой класс. В отличие от классов, интерфейсы могут наследовать любое количество других интерфейсов.

Реализация интерфейса

Для объявления класса, реализующего интерфейс, вы должны использовать ключевое слово implements после которого перечисляются интерфейсы.
Класс может реализовывать много интерфейсов. Объявление реализуемых интерфейсов идет после объявления наследуемого (extends) класса (если есть).

Пример простого интерфейса

Рассмотрим интерфейс, который определяет метод для сравнения объектов.

Public interface Relatable { // this (объект, который вызывает isLargerThan) // объект-параметр должен быть того же класса // возвращет 1, 0, -1 // если объект больше, равен или // меньше чем other public int isLargerThan(Relatable other); }

Чтобы иметь возможность сравнивать объекты мы должны реализовать интерфейс Relatable . Любой класс может реализовать интерфейс Relatable , если есть способ сравнить объекты. Для строк сравнивать можно количество символов, для книг — количество страниц, для студентов — вес и т.д. Для плоских геометрических фигур отличной характеристикой будет площадь, для трехмерных — объем. Все эти классы могут реализовать метод isLargerThan() . Если вы знаете, что класс реализует интерфейс Relatable , то вы смело можете сравнивать объекты этого класса.

Реализация интерфейса Relatable

Напишем класс Rectangle , реализующий интерфейс Relatable .

Public class RectanglePlus implements Relatable { public int width = 0; public int height = 0; public Point origin; // четыре конструктора public RectanglePlus() { origin = new Point(0, 0); } public RectanglePlus(Point p) { origin = p; } public RectanglePlus(int w, int h) { origin = new Point(0, 0); width = w; height = h; } public RectanglePlus(Point p, int w, int h) { origin = p; width = w; height = h; } // метод для передвижения прямоугольника public void move(int x, int y) { origin.x = x; origin.y = y; } // вычисление площади public int getArea() { return width * height; } // метод для реализации интерфейса Relatable public int isLargerThan(Relatable other) { RectanglePlus otherRect = (RectanglePlus)other; if (this.getArea() < otherRect.getArea()) return -1; else if (this.getArea() > otherRect.getArea()) return 1; else return 0; } }

Так как класс RectanglePlus реализует Relatable , размеры любых двух объектов типа RectanglePlus можно сравнивать.

Метод isLargerThan в качестве аргумента принимает объекты типа Relatable . При реализации метода в примере выше мы используем приведение типов, потому что компилятор не поймет что, other — объект типа RectanglePlus и вызов метода other.getArea() без приведения типов приведет к ошибке.

Использование интерфейса в качестве типа

Когда вы объявляете интерфейс, вы объявляете новый ссылочный тип данных. Вы можете использовать название интерфейса в качестве типа данных так же как и любые другие типы. Если вы объявляете переменную типа интерфеса, то вы можете можете присвоить ей объект любого класса, который реализует этот интерфейс.

Рассмотрим пример — метод, который ищет больший объект из двух любых объектов, класс которых реализует интерфейс Relatable:

Public Object findLargest(Object object1, Object object2) { Relatable obj1 = (Relatable)object1; Relatable obj2 = (Relatable)object2; if ((obj1).isLargerThan(obj2) > 0) return object1; else return object2; }

Приведением object1 к типу Relatable , мы делаем возможным вызов метода isLargerThan .

Так же для любого класса, реализующего интерфес Relatable, можно реализвать методы:

Public Object findSmallest(Object object1, Object object2) { Relatable obj1 = (Relatable)object1; Relatable obj2 = (Relatable)object2; if ((obj1).isLargerThan(obj2) < 0) return object1; else return object2; } public boolean isEqual(Object object1, Object object2) { Relatable obj1 = (Relatable)object1; Relatable obj2 = (Relatable)object2; if ((obj1).isLargerThan(obj2) == 0) return true; else return false; }

Переопределение интерфейсов

Допустим, вы написали интерфейс DoIt:

Public interface DoIt { void doSomething(int i, double x); int doSomethingElse(String s); }

Предположим, позже, вы захотели добавить в него третий метод:

Public interface DoIt { void doSomething(int i, double x); int doSomethingElse(String s); boolean didItWork(int i, double x, String s); }

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

Старайтесь избегать подобных изменений и продумывать интерфейс полностью изначально. Но часто на практике это невозможно и выходом из этой ситуации может быть определение нового интерфейса DoItPlus , который расширяет DoIt:

Public interface DoItPlus extends DoIt { boolean didItWork(int i, double x, String s); }

Теперь пользователи вашего интерфейса смогут перейти к использованию нового интерфейса или остаться со старым без боли.

Скажу сразу - при работе с графикой, скорее всего, со временем Вам придется воспользоваться всеми предоставленными инструментами без исключения для достижения наилучшего визуального эффекта. Подробное описание о том «что и как» можно найти - это официальный туториал по Graphics2D. Его должно быть более чем достаточно, чтобы ввести Вас в курс дела.

Я уже привел небольшой пример написания своего UI, но есть и другие варианты кастомизации интерфейса. Каждый отдельный J-компонент производит свою Lightweight-отрисовку при помощи метода paint(), который можно легко переопределить и изменить. Напрямую (не всегда, но чаще всего) его лучше не использовать (не буду вдаваться в подробности, так как это целая тема для отдельного топика). Для следующего примера используем метод paintComponent(). Рассмотрим как его можно применить поближе…

Начну с примера - текстовое поле с визуальным фидбэком при отсутствии содержимого:

JTextField field = new JTextField()
{
private boolean lostFocusOnce = false ;
private boolean incorrect = false ;

{
// Слушатели для обновления состояния проверки
addFocusListener (new FocusAdapter()
{
public void focusLost (FocusEvent e)
{
lostFocusOnce = true ;

repaint ();
}
});
addCaretListener (new CaretListener()
{
public void caretUpdate (CaretEvent e)
{
if (lostFocusOnce)
{
incorrect = getText ().trim ().equals ("" );
}
}
});
}

protected void paintComponent (Graphics g)
{
super.paintComponent (g);

// Расширенная отрисовка при некорректности данных
if (incorrect)
{
Graphics2D g2d = (Graphics2D) g;

// Включаем антиалиасинг для гладкой отрисовки
g2d.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

// Получаем отступы внутри поля
Insets insets;
if (getBorder () == null )
{
insets = new Insets (2, 2, 2, 2);
}
else
{
insets = getBorder ().getBorderInsets (this );
}

// Создаем фигуру в виде подчеркивания текста
GeneralPath gp = new GeneralPath (GeneralPath.WIND_EVEN_ODD);
gp.moveTo (insets.left, getHeight () - insets.bottom);
for (int i = 0; i < getWidth () - insets.right - insets.left; i += 3)
{
gp.lineTo (insets.left + i,
getHeight () - insets.bottom - ((i / 3) % 2 == 1 ? 2: 0));
}

// Отрисовываем её красным цветом
g2d.setPaint (Color.RED);
g2d.draw (gp);
}
}
};

Наличие содержимого перепроверяется при печати и потере фокуса полем. Переключившись на другой компонент мы увидим как отрисовывается наше дополнение к JTextField"у:

Полный код примера можно взять .

Таким образом можно расширить любой доступный компонент быстро переопределив и дополнив метод отрисовки, не прибегая к написанию отдельных громоздких UI или полноценных компонентов. Плюс данный пример можно достаточно легко вынести в отдельный класс и использовать как готовый элемент для своего интерфейса.
Еще одним плюсом данного способа является то, что Вы получаете независимый от текущего установленного приложению/компоненту LaF/UI - он будет работать всегда. Естественно, для некоторых специфичных UI может понадобиться немного иная отрисовка - поддерживать её или нет - Вам решать.

Хотя зачастую и хочется сделать интерфейс ярче и привлекательней, не всегда требуется для этого изобретать новые компоненты - можно воспользоваться стандартными средствами. Приведу пример кастомизации простейшего чекбокса анимированной сменой состояний и фона. В этот раз я опять (да, можете бить меня палками) воспользуюсь заготовленными изображениями из умелых рук дизайнера.

За основу берутся 8 изображений 16х16 - 4 состояния фона чекбокса и 4 состояния галки (5 на самом деле, но 5ое мы добавим програмно):

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

public static List BG_STATES = new ArrayList ();
public static List CHECK_STATES = new ArrayList ();

static
{
// Иконки состояния фона
for (int i = 1; i <= 4; i++)
{
BG_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/" + i + ".png" )));
}

// Дополнительное "пустое" состояние выделения

new BufferedImage (16, 16, BufferedImage.TYPE_INT_ARGB)));

// Состояния выделения
for (int i = 1; i <= 4; i++)
{
CHECK_STATES.add (new ImageIcon (
MyCheckBox.class .getResource ("icons/states/c" + i + ".png" )));
}
}

private Map iconsCache = new HashMap ();

private synchronized void updateIcon ()
{
// Обновляем иконку чекбокса
final String key = bgIcon + "," + checkIcon;
if (iconsCache.containsKey (key))
{
// Необходимая иконка уже была ранее использована
setIcon (iconsCache.get (key));
}
else
{
// Создаем новую иконку совмещающую в себе фон и состояние поверх
BufferedImage b = new BufferedImage (BG_STATES.get (0).getIconWidth (),
BG_STATES.get (0).getIconHeight (), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = b.createGraphics ();
g2d.drawImage (BG_STATES.get (bgIcon).getImage (), 0, 0,
BG_STATES.get (bgIcon).getImageObserver ());
g2d.drawImage (CHECK_STATES.get (checkIcon).getImage (), 0, 0,
CHECK_STATES.get (checkIcon).getImageObserver ());
g2d.dispose ();

ImageIcon icon = new ImageIcon (b);
iconsCache.put (key, icon);
setIcon (icon);
}
}

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

Получившийся компонент очень легко будет видоизменить к примеру в радиобаттон, или же добавить в него больше состояний перехода для более плавной анимации и т.п.
Собственно, полный рабочий код и изображения выложил отдельно .

Таким образом путей кастомизации элементов есть достаточно много (даже я, наверно, не знаю/не догадываюсь о некоторых из них). Какой способ выбирать - зависит от ситуации - что необходимо получить, какие изменения возможно сделать в существующем компоненте и т.д.

В завершение данной главы, приведу еще один пример полностью видоизмененного UI кнопки с анимацией, возможностью загругления отдельных углов, возможностью настройки стиля и некоторыми другими улучшениями. Вот несколько скринов с итоговым внешним видом (конечно, анимация тут не видна):


Не буду врать, на данный UI кнопки ушло достаточно много времени, но он создавался без помощи и подсказок дизайнера чистыми средствами Graphics2D и Swing. можно скачать и изучить полный сурс и демо данного UI, если Вам стало интересно. В нем использован достаточно большой спектр возможностей Graphics2D и применены некоторые хитрости, которые могут зачастую оказаться полезными.

Итак, думаю достаточно разговоров о графике - о ней более подробно я расскажу в будущих топиках, а сейчас приведу немного интересного материала, который я наработал за достаточно долгое время «общения» со Swing и Graphics2D.

DnD и GlassPane

Думаю первое - Вам всем более чем известно, как и проблемы с ним связанные. Насчет второго - вероятно Вы вскользь слышали о GlassPane или может даже видели это старинное изображение (которое до сих пор актуально, между прочем) об устройстве различных слоев стандартных фреймов. Что же тут такого и зачем я вспомнил об этом? И тем более, как связаны DnD и GlassPane спросите Вы? Вот именно о том, как их связать и что из этого может выйти я и хочу рассказать в этой главе.

Чтож, начнем по порядку - что мы знаем о DnD?
У некоторых Swing-компонентов есть готовые реализации для драга (JTree и JList к примеру) - для других можно достаточно легко дописать свою. Чтобы не бросаться словами на ветер - приведу небольшой пример DnD стринга из лэйбла:

JLabel label = new JLabel ("Небольшой текст для DnD" );
label.setTransferHandler (new TransferHandler()
{
public int getSourceActions (JComponent c)
{
return TransferHandler.COPY;
}

public boolean canImport (TransferSupport support)
{
return false ;
}

protected Transferable createTransferable (JComponent c)
{
return new StringSelection (((JLabel) c).getText ());
}
});
label.addMouseListener (new MouseAdapter()
{
public void mousePressed (MouseEvent e)
{
if (SwingUtilities.isLeftMouseButton (e))
{
JComponent c = (JComponent) e.getSource ();
TransferHandler handler = c.getTransferHandler ();
handler.exportAsDrag (c, e, TransferHandler.COPY);
}
}
});

Теперь возможно перетащить текст данного лэйбла прямо из интерфейса в любое другое место, куда возможно вставить текст через дроп.
Фактически - TransferHandler овечает за то, какие данные при драге отдает компонент и как компонент использует входящие данные при драге на него.

Но что делать, если необходимо отследить последовательность действий пользователя при самом перетаскивании?
Для этого есть отдельная возможность повесить слушатель:

DragSourceAdapter dsa = new DragSourceAdapter()
{
public void dragEnter (DragSourceDragEvent dsde)
{
// При входе драга в область какого-либо компонента
}

public void dragExit (DragSourceEvent dse)
{
// При выходе драга в область какого-либо компонента
}

public void dropActionChanged (DragSourceDragEvent dsde)
{
// При смене действия драга
}

public void dragOver (DragSourceDragEvent dsde)
{
// При возможности корректного завершения драга
}

public void dragMouseMoved (DragSourceDragEvent dsde)
{
// При передвижении драга
}

public void dragDropEnd (DragSourceDropEvent dsde)
{
// При завершении или отмене драга
}
};
DragSource.getDefaultDragSource ().addDragSourceListener (dsa);
DragSource.getDefaultDragSource ().addDragSourceMotionListener (dsa);

Остался последний момент - обозначить роль GlassPane. GlassPane, фактически, позволяет располагать/отрисовывать на себе компоненты, как и любой другой контейнер, но его особенность в том, что он лежит поверх всех Swing-компонентов, когда виден. Т.е. если мы что-либо отрисуем на нем, то оно накроет весь находящийся под ним интерфейс. Это позволяет размещать компоненты независимо от основного контейнера в любом месте, создавать любые визуальные эффекты и делать другие занятные вещи.

Приведу для большего понимания небольшой пример подобного «эффекта» - фрейм с несколькими Swing-компонентами на нем. При клике в любой части окна будет появляться эффект «распозающегося» круга, который виден поверх всех элементов. Что самое интересное - подобный эффект не съедает ресурсов и не требует большой груды кода. Не верите? - посмотрите демо и загляните в исходник, вложенный в jar.

Кстати говоря, есть достаточно интересная библиотека на эту тему, заодно предоставляющая дополнительный скролл-функционал и несколько других вкусностей - JXLayer (офф сайт) (описание #1 описание #2 описание #3). К сожалению проекты хостящиеся на сайте java сейчас находтся не в лучшем состоянии, поэтому приходится ссылаться на отдельные ресурсы.

Итак теперь объединим всё что я уже описал в данной главе и сделаем, наконец, что-то полноценное. К примеру - отображение драга панели с компонентами внутри окна:

При драге панели за лэйбл появляется полупрозрачная копия панели показывающая где именно будет размещена панель при завершении драга. Также клавишей ESC можно отменить перемещение.
Рабочий пример и исходный код можно взять .

Конечно, для реализации именно данной функциональности не стоило бы прибегать к использованию DnD - есть более короткие пути. Впрочем, всё зависит от ситуации. Данный вариант позволяет отрисовывать перетаскивание независимо от других компонентов в окне. Также можно на основе него, к примеру, реализовать драг панели между окнами приложения.

AWTUtilities

Так как уже достаточно давно в JDK6 включили некоторые будущие нововведения из 7ки, не могу обойти их стороной, так как с их помощью возможно много чего сделать приложив при этом гораздо меньшие усилия.
Итак, нас интересует несколько методов из AWTUtilities:
  1. AWTUtilities.setWindowShape(Window, Shape) - позволяет установить любому окну определенную форму (будь то круг или хитрый полигон). Для корректной установки формы окно не должно быть декорировано нативным стилем (setUndecorated(true)).
  2. AWTUtilities.setWindowOpacity (Window, float) – позволяет задать прозрачность окна от 0 (полностью прозрачно) до 1 (непрозрачно). Окно при этом может быть декорировано нативным стилем.
  3. AWTUtilities.setWindowOpaque (Window, boolean) – позволяет полностью спрятать отображение фона и оформления окна, но при этом любой размещенный на нем компонент будет виден. Для корректной установки данного параметра окно также как и в п.1 не должно быть декорировано нативным стилем.

Что же нам это дает? На самом деле - достаточно широкий спектр возможностей. Окнам своего приложения можно задавать любые хитрые формы какие Вам только потребуются, делать «дырки» посреди приложения, создавать кастомные тени под окно, делать приятные на вид попапы и пр. и пр.

Если переходить к конкретике - setWindowShape на деле я никогда не использую, так как задаваемая окну форма строго обрезается по краю и не очень приятно выглядит. На помощь приходит setWindowOpaque - спрятав оформление и фон окна можно с помощью контейнера с кастомным отрисованным фоном создавать абсолютно любые окна. Приведу небольшой пример использования (в нем также есть также использованы некоторые приемы из предыдущих глав поста):

можно взять работающий jar с исходным кодом. Честно скажу, что потратил на данный пример не более десяти минут (из них - минут пять прикидывал как расположить элементы внутри диалога:). Естественно это лишь один из вариантов примемения этих новых возможностей - на самом деле их куда больше.

Единственная неприятная мелочь в использовании AWTUtilities – нестабильная работа на Linux-системах. Т.е. Не везде и не всегда корректно отрабатывает прозрачность окон. Не уверен, проблемы ли это текущей JDK или же ОС.

Создание своих интерактивных компонентов

Я уже поверхностно рассказал о том, как создавать компоненты, UI и некоторые «навороты» для интерфейса своего приложения, но что же делать, если нам нужно добавить функциональную часть к компоненту или создать свой совершенно новый компонент с некоей функциональностью и стилизацией? Стилизовать стандартные компоненты и делать из них отдельные части нового компонента достаточно долгое и нудное занятие, тем более что при малейшем изменении в одном из компонентов вся схема может поехать. Именно в таких случаях стоит сделать свой компонент «с нуля».

Итак, за основу лучше лучше всего взять JComponent и используя paint-методы отрисовать его содержимое. Фактически JСomponent сам по себе - чистый холст с некоторыми зашитыми улучшениями для отрисовками и готовыми стандартными методами setEnabled/setFont/setForeground/setBackground и т.п. Как использовать (и использовать ли их) решать Вам. Все, что Вы будете добавлять в методы отрисовки станет частью компонента и будет отображаться при добавлении его в какой-либо контейнер.

Кстати, небольшое отступление, раз уж зашла речь о контейнерах, - любой наследник и сам JComponent являются контейнерами, т.е. могут содержать в себе другие компоненты, которые будет расположены в зависимости от установленного компоненту лэйаута. Что же творится с отрисовкой чайлд-компонентов, лежащих в данном и как она связана с отрисовкой данного компонента? Ранее я не вдавался подробно в то, как устроены и связаны paint-методы Jcomponent"а, теперь же подробно опишу это…

Фактически, paint() метод содержит в себе вызовы 3ех отдельных методов - paintComponent, paintBorder и paintChildren. Конечно же дополнительно он обрабатывает некоторые «особенные» случаи отрисовки как, например печать или перерисовку отдельной области. Эти три метода всегда вызываются в показанной на изображении выше последовательности. Таким образом сперва идет отрисовка самого компонента, затем поверх рисуется бордер и далее вызывается отрисовка чайлд-компонентов, которые в свою очередь также вызывают свой paint() метод и т.д. Естественно есть еще и различные оптимизации, предотвращающие лишние отрисовки, но об этом подробнее я напишу потом.

Компонент отрисован, но статичен и представляет собой лишь изображение. Нам необходимо обработать возможность управления им мышью и различными хоткеями.
Для этого, во-первых, необходимо добавить соответствующие слушатели (MouseListener/MouseMotionListener/KeyListener) к самому компоненту и обрабатывать отдельные действия.

Чтобы не объяснять все на пальцах, приведу пример компонента, позволяющего визуально ресайзить переданный ему ImageIcon:

можно взять рабочий пример с исходным кодом внутри.

При создании данного компонента я бы выделил несколько важных моментов:

  1. Определяемся с функционалом и внешним видом компонента - в данном случае это область с размещенным на ней изображением, бордером вокруг изображения и 4мя ресайзерами по углам. Каждый из ресайзеров позволяет менять размер изображения. Также есть возможность передвигать изображение по области, «схватив» его за центр.
  2. Определяем все необходимые для работы компонента параметры - в данном случае это само изображение и его «опорные» точки (верхний левый и правый нижний углы). Также есть ряд переменных, которые понадобятся при реализации ресайза и драга изображения.
  3. Накидываем заготовку для компонента (желательно отдельный класс, если Вы собираетесь его использовать более раза) - в данном случае создаем класс ImageResizeComponent, определяем все необходимые для отрисовки параметры, переопределяем метод paintComponent() и отрисовываем содержимое. Также переопределяем метод getPreferredSize(), чтобы компонент сам мог определить свой «желаемый» размер.
  4. Реализовываем функциональную часть компонента - в данном случае нам будет достаточно своего MouseAdapter"а для реализации ресайза и передвижения. При нажатии мыши в области проверяем координаты и сверяем имх с координатами углов и самого изображения - если нажатие произошло в районе некого угла - запоминаем его и при драге изменяем его координату, ежели нажатие пришлось на изображение - запоминаем начальные его координаты и при перетаскивании меняем их. И наконец, последний штрих - в mouseMoved() меняем курсор в зависимости от контрола под мышью.
Ничего сложного, правда? С реализацией «кнопочных» частей других компонентов всё еще проще - достаточно проверять, что нажатие пришлось в область кнопки. Параллельно с отслеживанием событий можно также визуально менять отображение компонента (как сделано в данном примере на ресайзерах). В общем сделать можно всё, на что хватит фантазии.

Конечно, я просто описал свои шаги при создании компонентов - они не являются чем-то обязательным и могут быть легко расширены и дополнены.

Важно помнить

Есть несколько вещей, которые стоит при любой работе с итерфейсными элементами в Swing – приведу их в этой отдельной небольшой главе.
  1. Любые операции вызова перерисовки/обновления компонентов должны производится внутри Event Dispatch потока для избежания визуальных «подвисаний» интерфейса Вашего приложения. Выполнить любой код в Event Dispatch потоке достаточно легко:
    SwingUtilities.invokeLater (new Runnable()
    {
    public void run ()
    {
    // Здесь располагаете исполняемый код
    }
    });
    Важно также помнить, что события, вызываемые из различных listener"ов стандартных компонентов (ActionListener/MouseListener и пр.) исходно вызываются из этого потока и не требуют дополнительной обработки.
  2. Из любых paint-методов ни в коем случае нельзя влиять на интерфейс, так как это может привести к зацикливаниям или дедлокам в приложении.
  3. Представьте ситуацию - из метода paint Вы изменяете состояние кнопки, скажем, через setEnabled(enabled). Вызов этого метода заставляет компонент перерисоваться и заново вызвать метод paint и мы опять попадаем на этот злополучный метод. Отрисовка кнопки зациклиться и интерфейс приложения будет попросту висеть в ожидании завершения отрисовки (или как минимум съест добрую часть процессора). Это самый простой вариант подобной ошибки.
  4. Не стоит производить тяжёлые вычисления внутри Event Dispatch потока. Вы можете произвести эти вычисления в отдельном обычном потоке и затем только вызвать обновление через SwingUtilities.invokeLater().
  5. Не стоит, также, производить тяжёлые вычисления внутри методов отрисовки. По возможности стоит выносить их отдельно и кэшировать/запоминать. Это позволит ускорить отрисовку компонентов, улучшить общую отзывчиввость приложения и снизить нагрузку на Event Dispatch поток.
  6. Для обвноления внешнего вида компонентов используйте метод repaint() (или же repaint(Rectangle) – если Вам известна точная область для обновления), сам метод repaint необходимо исполнять в Event Dispatch потоке. Для обновления же расположения элементов в лэйауте используйте метод revalidate() (его нет необходимости исполнять в Event Dispatch потоке). Метод updateUI() может помочь в некоторых случаях для полного обновления элемента (например смене данных таблицы), но его нужно использовать аккуратно, так как он также отменит установленный Вами UI и возьмет UI предоставляемый текущим LaF"ом.
  7. Полная установка LaF всему приложению отменит любые ваши вручную заданные в ходе работы UI компонентов и установит поверх них те UI, которые предлагает устанавливаемый LaF. Поэтому лучше производить установку LaF при загрузке приложения до открытия каких-либо окон и показа визуальных элементов.
Следовние этим простым пунктам позволит Вам не беспокоиться о возникновении непредвиденных «тормозов» или дедлоков в интерфейсе приложения.
Думаю, что этот список можно дополнить еще несколькими пунктами, но они уже будут весьма специфичны/необязательны.

Итоги

Используя, совмещая и варьируя вышеописанные возможности и особенности работы с интерфейсами возможно добиться любого желаемого результата. Необходимо лишь проявить немного фантазии в этом деле. Надеюсь даже базовые примеры данного поста дали Вам достаточно много пищи для размышлений и, возможно, помогли с чем-либо.

Еще хотелось бы добавить, что возможно, название статьи может показаться Вам слишком громким и не полностью раскрытым - действительно, я все же не дизайнер и не юзабилити-специалист. Я лишь предлагаю Вам инструменты/способы, которые позволят расширить и улучшить интерфейс приложения. Непосредственно бОльшая (не обязательно вся) часть работы по созданию графики для слайдера/кнопки или определению общего стиля компонентов и самого приложения всегда остается за дизайнером - от этого никуда не уйти.

Все примеры статьи в едином «флаконе». Из начального окна можно выбрать желаемый пример: Добавить метки

Абстрактные классы

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

Например, классы Point (точка), Circle (круг) и Rectangle (прямоугольник) унаследованы от класса Figure (фигура). В классе Figure есть метод paint() , общий для всех подклассов - он нужен, чтобы нарисовать фигуру. Но между рисованием круга, точки и прямоугольника нет ничего общего, поэтому бессмысленно программировать этот метод в классе Figure .

В таких случаях после заголовка метода и списка параметров вместо фигурных скобок ставится точка с запятой, а перед объявлением метода указывается ключевое слово abstract . Такой метод называется абстрактным , т.е. лишенным реализации.

Класс, в котором есть хотя бы один абстрактный метод, называется абстрактным классом и перед словом class должно также стоять слово abstract .

Создать объект абстрактного класса нельзя. Можно только унаследовать от этого класса другие классы, переопределить в них абстрактные методы (наполнив их конкретным содержимым) и создавать объекты уже этих классов.*

Множественное наследование

о множественном наследовании

Множественным наследованием называется ситуация, когда класс наследует от двух или более классов.

Пусть, например, уже разработаны классы Clock (часы) и Phone (телефон) и возникает необходимость написать новый класс Cellurar (сотовый телефон), который совмещает в себе структуру и поведение обоих этих классов (его можно использовать как телефон, но он обладает также всей функциональностью часов). Очень удобно просто унаследовать класс Cellular от классов Clock и Phone . Во-первых, программисту не приходится заново переписывать многочисленные методы. Во-вторых, сохраняются все преимущества полиморфизма: объект класса Cellular можно использовать в программе и в качестве часов (как объект класса Clock) и в качестве телефона (как объект класса Phone).

Однако множественное наследование потенциально способно породить трудноразрешимые проблемы. Они начинаются, когда у классов-предков есть методы с одинаковыми сигнатурами (т.е. именем и числом параметров).

Например, в классе Clock есть метод ring() , который вызывается, когда срабатывает таймер будильника. Но в классе Phone тоже есть метод ring() , который вызывается, когда кто-то звонит по телефону и надо оповестить об этом владельца. Когда класс Cellular наследует от классов Clock и Phone , он получает метод ring() . Но какой из его вариантов?*

У этой ситуации нет безупречного решения. Поэтому многие языки программирования запрещают множественное наследование.

Java множественное наследование не поддерживает.

Заметим, однако, что если метод ring() хотя бы в одном из классов Clock и Phone является абстрактным, то конфликта возникнуть не может. Абстрактный метод не имеет реализации, а следовательно «побеждает» тот метод, который абстрактным не является. Если же метод является абстрактным в обоих классах, он останется таким же и в их потомке.

Используя эту особенность абстрактных классов, авторы языка Java ввели в него особенную конструкцию - интерфейсы, позволяющие сохранить часть преимуществ множественного наследования (а именно преимущества полиморфизма), избежав при этом главной проблемы множественного наследования.

Понятие интерфейса в Java. Описание интерфейса

Интерфейс представляет собой класс, в котором все поля - константы (т.е. статические - static и неизменяемые - final ), а все методы абстрактные.

При описании интерфейса вместо ключевого слова class используется ключевое слово interface , после которого указывается имя интерфейса, а затем, в фигурных скобках список полей-констант и методов. Никаких модификаторов перед объявлением полей и методов ставить не надо: все поля автоматически становятся public static final , а методы - public abstract . Методы не могут иметь реализации, т.е. после закрывающей круглой скобки сразу ставится точка с запятой.

Опишем, например, интерфейс для объекта, который «умеет» сообщать информацию о себе в формате прайс-листа (т.е. сообщать свое название, цену, и краткое описание).

interface PriceItem { String getTitle(); int getPrice(int count); String getDescription(); }

Для разнообразия метод getPrice() в этом примере требует один целочисленный параметр (количество единиц товара).

Такой интерфейс полезен для программы типа Интернет-магазин, которая должна по запросу пользователя формировать прайс - перечень товаров с указанием их цены и описания.

Реализация интерфейса

Класс может реализовывать интерфейс, т.е. перенимать его поведение. Эта процедура аналогична наследованию, только вместо ключевого слова extends используется ключевое слово implements . Но если после extends может быть указан только один класс, то после implements можно перечислить через запятую произвольное количество интерфейсов. Класс может одновременно наследовать от другого класса и реализовывать интерфейсы.

Мы можем взять любой существующий в программе класс и «научить» его сообщать о себе все необходимые сведения. Например, класс Dog , с которым мы работали на прошлом занятии. Можно изменить этот класс, заставив его реализовывать наш интерфейс, а можно оставить в неприкосновенности* и создать на его базе новый класс. В новом классе обязательно требуется переопределить абстрактные методы интерфейса.

class Dog2 extends Dog implements PriceItem { private int price; String getTitle() { return ("Умная собака" ); }; int getPrice(int count) { return price * count; }; int setPrice(int p) { price = p; } String getDescription() { return ("Умная собака, которая знает свой возраст и умеет сообщать его с помощью лая" ); }; }

Класс Dog2 «умеет» то же самое, что и старый класс Dog , но помимо этого его можно использовать в программе Интернет-магазина для формирования прайса. Обратите внимание, класс Dog ничего не знал о цене, поэтому понадобилось добавить метод setPrice() и поле price , чтобы эту цену можно было бы изменять. А изменять описание собаки не понадобится, поэтому метод getDescription() просто выводит одну и ту же сроку.

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

Переменные интерфейсного типа

Переменные интерфейсного типа могут ссылаться на объект любого класса, реализующего данный интерфейс.

PriceItem pi; // переменная интерфейсного типа Dog2 dog = new Dog2(); // создается объект класса Dog2, на него ссылается переменная dog dog.voice(); // можно вызвать метод лая System.out.println(dog.getTitle()); // можно вывести название товара Dog oldDog = dog; // переменная oldDog ссылается на тот же самый объект oldDog.voice(); // можно работать с объектом нового класса по-старому pi = dog; // переменная pi рассматривает тот же самый объект как товар для прайса pi.voice(); // НЕ ПОЛУЧИТСЯ. Этого метода нет в интерфейсе PriceItem *

Мы можем поместить собак, велосипеды и компьютеры в один массив goods (товары) и в цикле сформировать прайс:

PriceItem goods; ... // создание и заполнение массива элементами, // поддерживающими интерфейс PriceItem for (int i = 0; i < googs.length; i++) { System.out.println("Название: " + goods[i].getTitle() + ", цена за единицу товара: " + goods[i].getPrice(1) + ", описание: " + goods[i].getDescription() + "." ); }

об использовании интерфейсов

Используя интерфейс, программист получает возможность задать описание того, «что класс делает», не заботясь о деталях, «как он это делает». Каждый класс, реализующий интерфейс, сам может выбирать способы реализации.

Приемы программирования: пример применения интерфейсов

Предположим, в нашей программе есть объект, который следит за текущим временем и каждую минуту (когда время меняется) «сообщает» об этом другим объектам. Это очень логичный и удобный способ организации программы, в которой объекты должны что-то делать при наступлении определенного времени (или события). Гораздо лучше, чем учить каждый объект следить за временем.

Один объект может «сообщить» что-то другому объекту, вызвав его метод. Пусть информацию о времени обрабатывает метод sayTime(int hours, int minutes) . Для того, чтобы вызвать этот метод у какого-то объекта, надо быть уверенным, что такой метод описан в классе этого объекта. Можно определить интерфейс, скажем TimeListener , и реализовать его во всех классах, которым нужно следить за временем, не вмешиваясь в основную иерархию этих классов. И тогда у нас может быть разновидность умной собаки, которая лает ровно в полночь и разновидность кнопки, которая может автоматически срабатывать через заданный промежуток времени.

Класс, следящий за временем будет иметь внутренний список объектов типа TimeListener , методы для добавления и удаления объектов в этот список (те объекты, которые «захотят» следить за временем, вызовут этот метод) и каждую минуту объект этого класса (достаточно одного такого объекта на всю программу) будет вызывать метод sayTime(int hours, int minutes) для каждого из объектов в этом списке.

Пакеты и области видимости

Пакеты

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

Каждый пакет имеет имя . Имя представляет собой обычный идентификатор Java. Особенность заключается в том, что это имя одновременно является названием папки, в которой хранятся файлы классов, входящие в пакет. А точка в имени преобразуется в разделитель имен файловой системы. То есть пакет с именем java.util будет представлен папкой util , находящейся внутри папки java .

В папке хранятся файлы с расширением.java , содержащие описания классов, входящих в пакет. В начале такого файла должен стоять оператор package , после которого записывается имя пакета.

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

Полное имя класса состоит из идентификатора, указанного после ключевого слова class и предшествующего ему имени пакета, в котором этот класс находится. Классы ClassA и ClassB , описанные в пакете package1 , имеют полные имена package1.ClassA и package1.ClassB .

Классы, находящиеся внутри одного пакета могут пользоваться сокращенными именами друг друга (что мы до сих пор всегда и делали). В одном из методов класса ClassA можно определить переменную класса ClassB , создать объект класса ClassB и вызвать его метод (например, f()) командами:

ClassB varb = new ClassB(); varb.f();

вместо команд:

Package1.ClassB varb = new package1.ClassB(); varb.f();

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

Это ограничение можно обойти, если импортировать нужный класс из другого пакета.

Для импортирования класса используется ключевое слово import , после которого указывается его полное имя. Например, можно импортировать класс Vector из пакета java.util:

import java.util.Vector;

Теперь можно пользоваться именем Vector вместо java.util.Vector .

Чтобы импортировать пакет полностью (то есть, ко всем классам пакета можно будет обращаться с помощью сокращенного имени), после имени пакета ставится точка и звездочка. Так, команда

import java.util.*;

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

Eclipse позволяет облегчить жизнь разработчику. Если в программе используется класс с неизвестным именем, на полях редактора кода появляется значок предупреждения об ошибке. Щелчок по этому значку выводит варианты решения проблемы. Например, создать новый класс. Или импортировать существующий (при этом выводится список всех доступных пакетов, содержащих класс с таким именем). Если выбрать вариант "Import" соответствующая директива import будет автоматически добавлена в начало пакета.

Все классы, находящиеся в одном пакете, должны иметь разные имена, иначе возникнет ошибка. К классам, которые находятся в разных пакетах, такого требования предъявить, очевидно, невозможно (поскольку пакеты могут разрабатываться совершенно разными людьми).

Поэтому при импортировании нескольких пакетов, в которых содержатся классы с одинаковыми именами, может возникнуть путаница в этих именах. Однако Java справляется с этой проблемой, «отдавая» сокращенное имя первому из импортированных классов. К другому классу можно обратиться с помощью его полного имени.

Если же совпадут и имена пакетов и имена классов, проблема окажется неразрешимой. А поскольку программист может пользоваться множеством пакетов разных разработчиков, такая ситуация вполне вероятна, если разработчики не договорятся. Поэтому существует соглашение об именовании пакетов (особенно тех, которые впоследствии могут распространяться).

Рекомендуется для названия пакета использовать адрес сайта фирмы-разработчика. Адреса сайтов есть практически у всех серьезных разработчиков программ (и, что самое главное, адреса сайтов не могут совпадать). Адрес сайта рекомендуется записывать наоборот. То есть, если адрес - sun.com, то имя пакета должно начинаться с com.sun. Кстати, таких пакетов довольно много в вашей системе, их поставляет фирма Sun Microsystems, разработчик языка Java.

Файловая структура Java-проекта

Итак, Java-проект может состоять из нескольких пакетов. Каждому пакету в файловой структуре операционной системы соответствует одна папка.

В пакете могут содержаться классы и интерфейсы. Они хранятся в файлах с расширением.java.

В каждом файле с расширением.java обязательно описывается один класс или интерфейс, название которого совпадает с названием этого файла. Он должен быть объявлен как открытый (перед объявлением класса указывается ключевое слово public ). Все остальные классы и интерфейсы из этого файла являются закрытыми - их нельзя использовать за пределами этого пакета (ни с помощью длинного, ни с помощью короткого имени). Поэтому с классом Dog , который мы разрабатывали на прошлом занятии, можно работать исключительно в пределах его пакета.

Все классы и интерфейсы, которые впоследствии предполагается использовать в других пакетах, должны быть объявлены открытыми (а значит, находиться в отдельных файлах).

Eclipse создает новый файл с расширением.java автоматически, если выполнить команду New --> Class или New --> Interface .

Файл с расширением.java - это обычный текстовый файл. Его можно открывать и редактировать как с помощью Eclipse, так и в любом другом текстовом редакторе (даже в Блокноте).

Для каждого класса (открытого или закрытого) Java создает файл с расширением.class . Это двоичный файл, в котором хранятся команды на внутреннем языке Java. Эти файлы недоступны для редактирования в Eclipse (если попытаться их открыть, Eclipse на самом деле откроет соответствующий.java -файл). Чтобы они не мешали, их можно скрыть с помощью фильтра. Для этого в представлении Navigator нажмите маленькую треугольную кнопку справа (menu ) и выберите команду Filters... В открывшемся окне поставьте галочку напротив расширения.class , чтобы скрыть из панели Navigator соответствующие файлы.

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

Область видимости класса - это участок программы, в котором можно объявлять переменные и создавать объекты этого класса.

об объектах "невидимых" классов

Объект, созданный в области видимости своего класса, может продолжать свое существование и за ее пределами. Но переменные типа "невидимого" класса объявить нельзя, поэтому обращаться к такому объекту можно только с помощью переменных типов "видимых" классов-предков. Следовательно, и вызывать можно лишь те методы объекта, которые его класс унаследовал от соответствующего предка.

Обычный класс всегда виден в пределах своего пакета. Если класс является открытым, он виден также из других пакетов (в этом случае к нему надо обращаться, используя полное имя или, если предварительно импортировать пакет, сокращенное).

Вложенные классы, объявленные без модификатора public , видны только в методах содержащего их класса. Классы, описанные в методах, видны только в пределах этих методов.

Анонимные классы видны лишь в пределах команды, которой они создаются.

Области видимости членов класса

Члены класса (методы и атрибуты), объявленные как public , видны везде, где виден сам класс.

Члены класса, объявленные как protected видны в самом классе и его потомках.

Члены класса, объявленные как private , видны только в пределах класса.

Если к члену класса не применяется ни один из модификаторов public , private , protected , он виден в пределах текущего пакета.

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

"Видимость" метода означает возможность его вызова.

Для обращения к полям и методам класса используется полное имя, состоящее из имени объекта соответствующего класса и собственно имени атрибута или метода. В теле метода того же класса имя объекта можно опускать (если подразумевается this - объект, для которого вызван данный метод).

Области видимости переменных

Переменные, объявленные в теле метода, видны от места объявления до конца блока, в котором это объявление находится. Границы блока задаются фигурными скобками {} . Поэтому в следующем примере:

{ int x = 0; } { int x = 2; }

используются две разные переменные x (первая переменная, равная 0, перестает существовать за границами своего блока).

Переменные, являющимися параметрами метода, видны во всем теле метода.

Перечисленные переменные называются локальными переменными . Двух локальных переменных с одинаковыми именами и перекрывающимися областями видимости быть не должно. Нельзя, например, объявить две переменных следующим образом:

int x = 0; { int x = 2; }

Нельзя, в частности, объявлять в теле метода переменную, совпадающую (по имени) с одним из параметров метода.

Конфликты имен

Конфликт имен возникает, если в одной области видимости оказываются два объекта с одинаковыми именами, причем такая ситуация является в языке разрешенной (в противном случае это не конфликт имен, а ошибка в программе).

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

В этом случае Java отдает предпочтение классу, который был импортирован первым. Для обращения ко остальным классам следует использовать их полные имена.

Java "просматривает" имена классов в следующем порядке. Сначала - классы, импортированные поодиночке. Потом - классы, определенные в данном пакете. В последнюю очередь классы из пакетов, импортируемых полностью в порядке следования команд import .

Конфликт имен возникает так же, когда имя одного из параметров метода совпадает с именем одного из атрибутов этого же класса. Такая ситуация возникает довольно часто, поскольку для метода, изменяющего значение этого атрибута, такое называние параметра довольно наглядно. Например:

class Dog { int age; ... public void setAge(int age) { ... }; ... }

Такой заголовок метода setAge(int age) лучше, чем использовавшийся нами на прошлом занятии setAge(int a) , поскольку сразу позволяет судить о назначении параметра. Однако возникает вопрос: к чему будет относиться имя age в теле этого метода - к атрибуту или к параметру.

Ответ: к параметру. Имя параметра «перекрывает» имя атрибута.

Для того, чтобы обратиться к атрибуту, следует использовать полное имя (т.е. указатель на объект this ).

Реализация метода должна выглядеть следующим образом:

public void setAge(int age) { this .age = age; // проверку диапазона параметра в этом примере проигнорируем };
Дополнительная литература

1. Вязовик Н.А. Программирование на Java. (глава 8)

2. Хабибуллин И.Ш. Самоучитель Java 2. (глава 3)



Загрузка...