КЛАССЫ И ОБЪЕКТЫ
63
приводит к появлению соответствующего предупреждения и не будет логиче-
ски корректным, хотя и не закончится ошибкой компиляции.
Переопределение статических методов невозможно, так как обращение
к статическому атрибуту или методу осуществляется посредством задания
имени класса, которому они принадлежат.
Статические методы используются при необходимости придать функцио-
нальности метода признак «окончательности», «неизменности» реализации
алгоритма для данного класса.
Модификатор final
Модификатор final используется для определения констант в качестве члена
класса, локальной переменной или параметра метода. Методы, объявленные
как final, нельзя замещать в подклассах. Для классов, объявленных со специ-
фикатором final, нельзя создавать подклассы. Например:
/* # 6 # final-поля и переменные # Card.java */
package
by.bsu.finalvar;
public
class Card {
// инициализированная константа экземпляра
public final int ID = (int)(Math.random() * 10_000_000);
// неинициализированная константа
public final long BANK_ID; // инициализация по умолчанию не производится!
// { BANK_ID = 11111111L; } // только один раз!!!
public Card (long id) {
// инициализация в конструкторе
BANK_ID = id; // только один раз!!!
}
public final boolean checkRights(final int NUMBER) {
final int CODE = 72173394; // антишаблон: "Волшебное Число"
// ID = 1; // ошибка компиляции!
// NUMBER = 1; // ошибка компиляции!
// CODE = 1; // ошибка компиляции!
return CODE == NUMBER + ID;
}
}
Константа может быть объявлена как поле класса, но не проинициали-
зирована. В этом случае она должна быть проинициализирована в логическом
блоке класса, заключенном в {}, или конструкторе, но только в одном из ука-
занных мест. Значение по умолчанию константа получить не может в отличие
от переменных класса. Константы могут быть объявлены в методах как локаль-
ные или как параметры метода. В обоих случаях значения таких констант из-
менять нельзя.
ОСНОВЫ JAVA
64
Абстрактные методы
Абстрактные методы размещаются в абстрактных классах или интерфейсах,
тела у таких методов отсутствуют и должны быть реализованы в подклассах.
/* # 7 # абстрактный класс и метод # AbstractCardAction.java */
public
abstract class AbstractCardAction {
private Long account;
public AbstractCardAction () { }
/* тело абстрактного метода отсутствует */
public abstract void doPayment(double amountPayment);
public void setAccount(Long account) {
this.account = account;
}
}
При этом становится невозможным создание экземпляра
AbstractCardAction ap = new AbstractCardAction(); // compile error
Спецификатор abstract присутствует здесь как в объявлении метода, так
и в объявлении класса.
В отличие от интерфейсов абстрактный класс может содержать и абстрактные,
и неабстрактные методы, а может и не содержать ни одного абстрактного метода.
Подробнее абстрактные класса и интерфейсы изучаются в главе «Наследование
и полиморфизм».
Модификатор native
Приложение на языке Java может вызывать методы, написанные на языке
С++. Такие методы объявляются с ключевым словом native, которое сообщает
компилятору, что метод реализован в другом месте. Например:
public native int
loadCripto(int num);
Методы, помеченные native, можно переопределять обычными методами
в подклассах.
Модификатор synchronized
При использовании нескольких потоков управления в одном приложении
необходимо синхронизировать методы, обращающиеся к общим данным. Когда
интерпретатор обнаруживает synchronized, он включает код, блокирующий до-
ступ к данным при запуске потока и снимающий блок при его завершении.
КЛАССЫ И ОБЪЕКТЫ
65
Вызов методов уведомления о возвращении блокировки объекта notifyAll(),
notify() и метода остановки потока wait() класса Object (суперкласса для всех
классов языка Java) предполагает использование модификатора synchronized,
так как эти методы предназначены для работы с потоками.
Логические блоки
При описании класса могут быть использованы логические блоки. Логическим
блоком называется код, заключенный в фигурные скобки и не принадлежащий
ни одному методу текущего класса, например:
{ /* код */ }
static
{ /* код */ }
Логические блоки чаще всего используются в качестве инициализаторов
полей, но могут содержать вызовы методов и обращения к полям текущего
класса. При создании объекта класса они вызываются последовательно, в по-
рядке размещения, вместе с инициализацией полей как простая последователь-
ность операторов, и только после выполнения последнего блока будет вызван
конструктор класса. Операции с полями класса внутри логического блока
до явного объявления этого поля возможны только при использовании ссылки
this, представляющей собой ссылку на текущий объект.
Логический блок может быть объявлен со спецификатором static. В этом
случае он вызывается только один раз в жизненном цикле приложения при
создании объекта или при обращении к статическому методу (полю) дан-
ного класса.
/* # 8 # использование логических блоков при объявлении класса # Department.java #
DemoLogic.java */
package
by.bsu.logic;
public
class Department {
{
System.out.println("logic (1) id=" + this.id);
// проверка и инициализация параметров конкретного объекта
}
static {
System.out.println("static logic");
/* проверка и инициализация базовых параметров, необходимых
для функционирования приложения (класса) */
}
private int id = 7;
public Department(int id) {
this.id = id;
System.out.println("конструктор id=" + id);
}
ОСНОВЫ JAVA
66
public int getId() {
return id;
}
{ /* не очень хорошее расположение логического блока */
System.out.println("logic (2) id=" + id);
}
}
package
by.bsu.logic;
public
class DemoLogic {
public static void main(String[ ] args) {
new Department(71);
new Department(17);
}
}
В результате выполнения этой программы будет выведено:
static logic
logic (1) id=0
logic (2) id=7
конструктор id=71
logic (1) id=0
logic (2) id=7
конструктор id=17
Во второй строке вывода поле id получит значение по умолчанию, так как
память для него выделена при создании объекта, а значение еще не проиници-
ализировано. В третьей строке выводится значение поля id, равное 7, так как
после инициализации атрибута класса был вызван логический блок, получив-
ший его значение.
Перегрузка методов
Метод называется перегруженным, если существует несколько его версий с од-
ним и тем же именем, но с разным списком параметров. Перегрузка реализует
«раннее связывание», то есть версия вызываемого метода определяется на этапе
компиляции. Перегрузка может ограничиваться одним классом. Методы с одина-
ковыми именами, но с различными списком параметров и возвращаемыми значе-
ниями могут находиться в разных классах одной цепочки наследования и также
будут перегруженными. Если списки параметров идентичны, то имеет место меха-
низм динамического полиморфизма — переопре деление метода.
Статические методы могут перегружаться нестатическими, и наоборот, без
ограничений.
При вызове перегруженных методов следует избегать ситуаций, когда ком-
пилятор будет не в состоянии выбрать тот или иной метод.
КЛАССЫ И ОБЪЕКТЫ
67
/* # 9 # вызов перегруженных методов # NumberInfo.java */
package
by.bsu.overload;
public
class NumberInfo {
public static void viewNum(Integer i) { // 1
System.out.printf("Integer=%d%n", i);
}
public static void viewNum(int i) { // 2
System.out.printf("int=%d%n", i);
}
public static void viewNum(Float f) { // 3
System.out.printf("Float=%.4f%n", f);
}
public static void viewNum(Number n) { // 4
System.out.println("Number=" + n);
}
public static void main(String[ ] args) {
Number[ ] num = {new Integer(7), 71, 3.14f, 7.2 };
for (Number n : num) {
viewNum(n);
}
viewNum(new Integer(8));
viewNum(81);
viewNum(4.14f);
viewNum(8.2);
}
}
Может показаться, что в результате компиляции и выполнения данного кода бу-
дут последовательно вызваны все четыре метода, однако в консоль будет выведено:
Number=7
Number=71
Number=3.14
Number=7.2
Integer=8
int=81
Float=4,1400
Number=8.2
То есть во всех случаях при передаче в метод элементов массива был вызван
четвертый метод. Это произошло вследствие того, что выбор варианта пере-
груженного метода происходит на этапе компиляции и зависит от типа массива
num. То, что на этапе выполнения в метод передается другой тип (для первых
трех элементов массива), не имеет никакого значения, так как выбор уже был
осуществлен заранее.
При непосредственной передаче объекта в метод выбор производится в за-
висимости от типа ссылки на этапе компиляции.
ОСНОВЫ JAVA
68
С одной стороны, этот механизм снижает гибкость, с другой — все возмож-
ные ошибки при обращении к перегруженным методам отслеживаются на эта-
пе компиляции, в отличие от переопределенных методов, когда их некоррект-
ный вызов приводит к возникновению исключений на этапе выполнения.
При перегрузке всегда надо придерживаться следующих правил:
• не использовать сложных вариантов перегрузки;
• не использовать перегрузку с одинаковым числом параметров;
• заменять при возможности перегруженные методы на несколько разных ме-
тодов.
Параметризованные классы
К наиболее важным новшествам версии языка J2SE 5 можно отнести появ-
ление параметризации (generic) классов и методов, позволяющей использовать
гибкую и в то же время достаточно строгую типизацию, что особенно важно
при работе с коллекциями. Применение generic-классов для создания типизи-
рованных коллекций будет рассмотрено в главе «Коллекции». Параметризация
позволяет создавать классы, интерфейсы и методы, в которых тип обрабатыва-
емых данных задается как параметр.
Ниже приведен пример generic-класса с двумя параметрами:
/* # 10 # объявление класса с двумя параметрами # Post.java */
package
by.bsu.forum;
public
class Post extends Number> {
private T1 message;
private T2 id;
// методы
}
Здесь T1, Т2 — фиктивные объектные типы, которые используются при
объявлении членов класса и обрабатываемых данных. В качестве типа T2 до-
пустимо использовать только подклассы класса Number. В качестве параме-
тров классов запрещено применять базовые типы.
Объект класса Post можно создать, например, следующим образом:
Post post1 = new Post();
Post post2 = new Post();
или
Post post2 = new Post<>(); // оператор diamond в Java 7
Параметризированные типы обеспечивают типобезопасность. Присваивание
post1=post2 приводит к ошибке компиляции.
При создании объекта компилятор заменит все фиктивные типы на реаль-
ные и создаст соответствующий им объект, при этом все внешние признаки
КЛАССЫ И ОБЪЕКТЫ
69
параметризации исчезнут, то есть проверка на принадлежность типу осущест-
вима только в виде:
post1 instanceof Post
тогда как, будет ошибочно
post1 instanceof Post
Ниже приведен пример параметризованного класса Message с конструкто-
рами и методами, также инициализация и исследование поведения объектов
при задании различных параметров.
/* # 11 # создание и использование объектов параметризованного класса #
Message.java # Runner.java */
package
by.bsu.template;
public
class Message {
private T value;
public Message() {
}
public Message (T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
public String toString() {
if (value == null) {
return null;
}
return value.getClass().getName() + " :" + value;
}
}
package
by.bsu.template;
public
class Runner {
public static void main(String[ ] args) {
// параметризация типом Integer
Message ob1 = new Message();
ob1.setValue(1); // возможен только тип Integer для метода setValue
int v1 = ob1.getValue();
System.out.println(v1);
// параметризация типом String
Message ob2 = new Message("Java");
String v2 = ob2.getValue();
System.out.println(v2);
// ob1 = ob2; // ошибка компиляции – параметризация нековариантна
ОСНОВЫ JAVA
70
// параметризация по умолчанию – Object
Message ob3 = new Message(); // warning – raw type
ob3 = ob1; // нет ошибки компиляции – нет параметризации
System.out.println(ob3.getValue());
ob3.setValue(new Byte((byte)1));
ob3.setValue("Java SE 7");
System.out.println(ob3); /* выводится тип объекта,
а не тип параметризации */
ob3.setValue(71);
System.out.println(ob3);
ob3.setValue(null);
}
}
В результате выполнения этой программы будет выведено:
1
Java
null
java.lang.String: Java SE 7
java.lang.Integer: 71
В рассмотренном примере были созданы объекты типа Message: ob1 на осно-
ве типа Integer и ob2 на основе типа String при помощи различных конструкто-
ров. При компиляции вся информация о generic-типах стирается и заменяется
для членов класса и методов заданными типами или типом Object, если пара-
метр не задан, как для объекта ob3. Такая реализация необходима для обеспече-
ния совместимости с кодом, созданным в предыдущих версиях языка.
Объявление generic-типа в виде , несмотря на возможность использо-
вать любой тип в качестве параметра, ограничивает область применения разра-
батываемого класса. Переменные такого типа могут вызывать только методы
класса Object. Доступ к другим методам ограничивает компилятор, предупре-
ждая возможные варианты возникновения ошибок.
Чтобы расширить возможности параметризованных членов класса, можно
ввести ограничения на используемые типы при помощи следующего объявле-
ния класса:
public class
ValueExt extends Tип> {
private T value;
// поля, конструкторы, методы
}
Такая запись говорит о том, что в качестве типа Т разрешено применять
только классы, являющиеся наследниками (подклассами) реального класса
Tип, и, соответственно, появляется возможность вызова методов ограничиваю-
щих (bound) типов.
Часто возникает необходимость в метод параметризованного класса одного
допустимого типа передать объект этого же класса, но параметризованного
КЛАССЫ И ОБЪЕКТЫ
71
другим типом. В этом случае при определении метода следует применить
метасимвол «?». Метасимвол также может использоваться с ограничением
extends для передаваемого типа.
/* # 12 # использование метасимвола в параметризованном классе # Exam.java #
Runner.java */
package
by.bsu.exam;
public class
Examextends Number> {
private String name;
private T mark; // параметр поля
public Exam(T mark, String name) { // параметр конструктора
this.name = name;
this.mark = mark;
}
public T getMark() { // параметр метода
return mark;
}
private int roundMark() {
return Math.round(mark.floatValue()); // метод класса Number
}
public boolean equalsToMark(Exam ob) { // параметр метода
return roundMark() == ob.roundMark();
}
}
package
by.bsu.exam;
public
class Runner {
public static void main(String[ ] args) {
Exam md1 = new Exam(71.41D,"Progr");// 71.5d
Exam md2 = new Exam(71.45D, "Progr");// 71.5d
System.out.println(md1.equalsToMark(md2));
Exam mi = new Exam(71,"Progr");
// md1.equalsToMark(mi); // ошибка компиляции: несовместимые типы
}
}
В результате будет выведено:
true
Метод с параметром Exam может принимать исключительно объекты
с инициализацией того же типа, что и вызывающий метод объект. Чтобы метод
equalsToMark() мог распространить свои возможности на экземпляры класса
Exam, инициализированные любым допустимым типом, его следует перепи-
сать с использованием метасимвола «?» в виде:
public
boolean equalsToMark(Exam> ob) {
return roundMark() == ob.roundMark();
}
ОСНОВЫ JAVA
72
Тогда при вызове md1.equalsToMark(mi) ошибки компиляции не возникнет
и метод выполнит свою расширенную функциональность по сравнению объектов
класса Exam, инициализированных объектами различных допустимых типов. В про-
тивном случае было бы необходимо создавать новые перегруженные методы.
Для generic-типов существует целый ряд ограничений. Например, невозмож-
но выполнить явный вызов конструктора generic-типа:
class
FailedOne {
private T value = new T();
}
так как компилятор не знает, какой конструктор может быть вызван и какой
объем памяти должен быть выделен при создании объекта.
По аналогичным причинам generic-поля не могут быть статическими, ста-
тические методы не могут иметь generic-параметры или обращаться к generic-
полям, например:
/* # 13 # неправильное объявление и использование полей параметризованного класса
# FailedTwo.java */
class
FailedTwo {
static T1 value;
T2 id;
static T1 takeValue() {
return value;
}
static void use() {
System.out.print(id);
}
}
Параметризованные методы
Параметризованный (generic) метод определяет базовый набор операций,
которые будут применяться к разным типам данных, получаемых методом в ка-
честве параметра, и может быть записан, например, в виде:
extends Тип> returnType methodName(T arg) { }
> T[ ] methodName(int count, T arg) { }
Описание типа должно находиться перед возвращаемым типом. Запись пер-
вого вида означает, что в метод можно передавать объекты, типы которых явля-
ются подклассами класса, указанного после extends. Второй способ объявле-
ния метода никаких ограничений на передаваемый тип не ставит.
Generic-методы могут находиться как в параметризованных классах, так
и в обычных. Параметр метода может не иметь никакого отношения к параметру
|