Шрифт:
Интервал:
Закладка:
// рождение еще одного человека
Для удобства разрешено обращаться к статическим полям и через ссылки:
Human h = new Human();
h.totalCount=100;
Однако такое обращение конвертируется компилятором. Он использует тип ссылки, в данном случае переменная h объявлена как Human, поэтому последняя строка будет неявно преобразована в:
Human.totalCount=100;
В этом можно убедиться на следующем примере:
Human h = null;
h.totalCount+=10;
Значение ссылки равно null, но это не имеет значения в силу описанной конвертации. Данный код успешно скомпилируется и корректно исполнится. Таким образом, в следующем примере
Human h1 = new Human(),
h2 = new Human();
Human.totalCount=5;
h1.totalCount++;
System.out.println(h2.totalCount);
все обращения к переменной totalCount приводят к одному единственному полю, и результатом работы такой программы будет 6. Это поле будет существовать в единственном экземпляре независимо от того, сколько объектов было порождено от данного класса, и был ли вообще создан хоть один объект.
Аналогично объявляются статические методы.
class Human {
private static int totalCount;
public static int getTotalCount() {
return totalCount;
}
}
Для вызова статического метода ссылки на объект не требуется.
Human.getTotalCount();
Хотя для удобства обращения через ссылку разрешены, но принимается во внимание только тип ссылки:
Human h=null;
h.getTotalCount();
// два эквивалентных
Human.getTotalCount();
// обращения к одному
// и тому же методу
Хотя приведенный пример технически корректен, все же использование ссылки на объект для обращения к статическим полям и методам не рекомендуется, поскольку это усложняет код.
Обращение к статическому полю является корректным независимо от того, были ли порождены объекты от этого класса и в каком количестве. Например, стартовый метод main() запускается до того, как программа создаст хотя бы один объект.
Кроме полей и методов, статическими могут быть инициализаторы. Они также называются инициализаторами класса, в отличие от инициализаторов объекта, рассматривавшихся ранее. Их код выполняется один раз во время загрузки класса в память виртуальной машины. Их запись начинается с модификатора static:
class Human {
static {
System.out.println("Class loaded");
}
}
Если объявление статического поля совмещается с его инициализацией, то поле инициализируется также однократно при загрузке класса. На объявление и применение статических полей накладываются те же ограничения, что и для динамических,– нельзя использовать поле в инициализаторах других полей или в инициализаторах класса до того, как это поле объявлено:
class Test {
static int a;
static {
a=5;
// b=7;
// Нельзя использовать до
// объявления!
}
static int b=a;
}
Это правило распространяется только на обращения к полям по простому имени. Если использовать составное имя, то обращаться к полю можно будет раньше (выше в тексте программы), чем оно будет объявлено:
class Test {
static int b=Test.a;
static int a=3;
static {
System.out.println("a="+a+", b="+b);
}
}
Если класс будет загружен в систему, на консоли появится текст:
a=3, b=0
Видно, что поле b при инициализации получило значение по умолчанию поля a, т.е. 0. Затем полю a было присвоено значение 3.
Статические поля также могут быть объявлены как final, это означает, что они должны быть проинициализированы строго один раз и затем уже больше не менять своего значения. Аналогично, статические методы могут быть объявлены как final, а это означает, что их нельзя перекрывать в классах-наследниках.
Для инициализации статических полей можно пользоваться статическими методами и нельзя обращаться к динамическим. Вводят специальные понятия – статический и динамический контексты. К статическому контексту относят статические методы, статические инициализаторы, инициализаторы статических полей. Все остальные части кода имеют динамический контекст.
При выполнении кода в динамическом контексте всегда есть объект, с которым идет работа в данный момент. Например, для динамического метода это объект, у которого он был вызван, и так далее.
Напротив, со статическим контекстом ассоциированных объектов нет. Например, как уже указывалось, стартовый метод main() вызывается в тот момент, когда ни один объект еще не создан. При обращении к статическому методу, например, MyClass.staticMethod(), также может не быть ни одного экземпляра MyClass. Обращаться к статическим методам класса Math можно, а создавать его экземпляры нельзя.
А раз нет ассоциированных объектов, то и пользоваться динамическими конструкциями нельзя. Можно только ссылаться на статические поля и вызывать статические методы. Либо обращаться к объектам через ссылки на них, полученные в результате вызова конструктора или в качестве аргумента метода и т.п.
class Test {
public void process() {
}
public static void main(String s[]) {
// process();
- ошибка!
// у какого объекта его вызывать?
Test test = new Test();
test.process();
// так правильно
}
}
Ключевые слова this и super
Эти ключевые слова уже упоминались, рассматривались и некоторые случаи их применения. Здесь они будут описаны более подробно.
Если выполнение кода происходит в динамическом контексте, то должен быть объект, ассоциированный с ним. В этом случае ключевое слово this возвращает ссылку на данный объект:
class Test {
public Object getThis() {
return this;
// Проверим, куда указывает эта ссылка
}
public static void main(String s[]) {
Test t = new Test();
System.out.println(t.getThis()==t);
// Сравнение
}
}
Результатом работы программы будет:
true
То есть внутри методов слово this возвращает ссылку на объект, у которого этот метод вызван. Оно необходимо, если нужно передать аргумент, равный ссылке на данный объект, в какой-нибудь метод.
class Human {
public static void register(Human h) {
System.out.println(h.name+
" is registered.");
}
private String name;
public Human (String s) {
name = s;
register(this);
// саморегистрация
}
public static void main(String s[]) {
new Human("John");
}
}
Результатом будет:
John is registered.
Другое применение this рассматривалось в случае "затеняющих" объявлений:
class Human {
private String name;
public void setName(String name) {
this.name=name;
}
}
Слово this можно использовать для обращения к полям, которые объявляются ниже:
class Test {
// int b=a; нельзя обращаться к
// необъявленному полю!
int b=this.a;
int a=5;
{
System.out.println("a="+a+", b="+b);
}
public static void main(String s[]) {
new Test();
}
}
Результатом работы программы будет:
a=5, b=0
Все происходит так же, как и для статических полей – b получает значение по умолчанию для a, т.е. ноль, а затем a инициализируется значением 5.
Наконец, слово this применяется в конструкторах для явного вызова в первой строке другого конструктора этого же класса. Там же может применяться и слово super, только уже для обращения к конструктору родительского класса.
Другие применения слова super также связаны с обращением к родительскому классу объекта. Например, оно может потребоваться в случае переопределения (overriding) родительского метода.
Переопределением называют объявление метода, сигнатура которого совпадает с одним из методов родительского класса.
class Parent {
public int getValue() {
return 5;
}
}
class Child extends Parent {
// Переопределение метода
public int getValue() {
return 3;
}
public static void main(String s[]) {
Child c = new Child();
// пример вызова переопределенного метода
System.out.println(c.getValue());
}
}
Вызов переопределенного метода использует механизм полиморфизма, который подробно рассматривается в конце этой лекции. Однако ясно, что результатом выполнения примера будет значение 3. Невозможно, используя ссылку типа Child, получить из метода getValue() значение 5, родительский метод перекрыт и уже недоступен.
Иногда при переопределении бывает полезно воспользоваться результатом работы родительского метода. Предположим, он делал сложные вычисления, а переопределенный метод должен вернуть округленный результат этих вычислений. Понятно, что гораздо удобнее обратиться к родительскому методу, чем заново описывать весь алгоритм. Здесь применяется слово super. Из класса наследника с его помощью можно обращаться к переопределенным методам родителя:
class Parent {
public int getValue() {
return 5;
}
}
class Child extends Parent {
// переопределение метода
public int getValue() {
// обращение к методу родителя
return super.getValue()+1;
}
public static void main(String s[]) {
Child c = new Child();
System.out.println(c.getValue());
}
}
Результатом работы программы будет значение 6.
Обращаться с помощью ключевого слова super к переопределенному методу родителя, т.е. на два уровня наследования вверх, невозможно. Если родительский класс переопределил функциональность своего родителя, значит, она не будет доступна его наследникам.
- Как спроектировать современный сайт - Чои Вин - Программирование
- Сделай видеоигру один и не свихнись - Слава Грис - Программирование / Руководства
- Как почистить сканы книг и сделать книгу - IvanStorogev? KpNemo - Программирование