Материал предоставлен https://it.rfei.ru

Другие модификаторы для членов

Некоторые характеристики полей и/или методов могут быть заданы в их определении с помощью следующих ключевых слов:

  • static
  • final
  • abstract
  • synchronized
  • transient
  • volatile

Статические члены

Объявлению статических членов предшествует ключевое слово static, чтобы отличать их от членов экземпляра.

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

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

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

Обычно статический метод выполняет задачи от имени целого класса и/или для объектов класса. В примере 4.10 статическая переменная counter сохраняет значение количества созданных экземпляров класса Light. В примере показано, что статический метод writeCount может напрямую обратиться только к статическим членам, как показано в строке (2), и не может обратиться к нестатическим, как показано в строке (3). Статическая переменная counter будет проинициализирована значением по умолчанию 0 на этапе загрузки класса во время выполнения. В методе main() в строке (4) в классе Warehouse показано, как по имени можно обратиться к статическим членам класса Light, а также как можно обратиться к ним через ссылки на объекты типа этого же класса.

Резюме по обращению к статическим членам из статического и нестатического кода приведено в табл. 4.1.

Пример 4.10. Доступ к статическим членам

class Light {
    // Fields
    int     noOfWatts;      // Мощность
    boolean indicator;      // Включено или выключено
    String  location;       // Размещение

    // Статическая переменная
    static int counter;     // Источник созданных объектов типа Light       (1)

    // Явный конструктор по умолчанию
    Light() {
        noOfWatts = 50;
        indicator = true;
        location  = "X";
        ++counter;          // Наращиваемый счетчик.
    }

    // Статический метод
    public static void writeCount() {
         System.out.println("Number of lights: " + counter);      // (2)
         // Compile error. Field noOfWatts is not accessible:
         // System.out.println("Number of Watts: " + noOfWatts);  // (3)
    }
}

public class Warehouse {
    public static void main(String[] args) {                      // (4)

        Light.writeCount();                       // Вызов с использованием имени класса
        Light aLight = new Light();               // Создание объекта
        System.out.println(
            "Value of counter: " + Light.counter  // Обращение через имя класса
        );
        Light bLight = new Light();               // Создание другого объекта
        bLight.writeCount();                      // Вызов с использованием ссылки
        Light cLight = new Light();               // Создание другого объекта
        System.out.println(
            "Value of counter: " + cLight.counter // Обращение через ссылку
        );
    }
}

Вывод программы:

Number of lights: 0
Value of counter: 1
Number of lights: 2
Value of counter: 3

Неизменяемые члены

Неизменяемая (final) переменная — это константа, несмотря на то что она называется переменной. Ее значение не может быть изменено после того, как один раз оно было проинициализировано. Это правило применимо к статическим и локальным переменным, переменным экземпляра, а также параметрам, если в их объявление включен модификатор final.

  • Нельзя изменить значение неизменяемой переменной примитивного типа после того, как один раз оно было проинициализировано.
  • Нельзя изменить значение ссылки, объявленной как final, после того как один раз она была проинициализирована, но состояние объекта, который она обозначает, изменять можно.

Такие переменные также называются пустыми неизменяемыми переменными. Неизменяемые статические переменные (final static) обычно используются для описания символических констант (также называемых именованными константами), например Integer.MAX_VALUE, которая содержит максимальное значение типа int. Переменные, определенные в интерфейсе, косвенно являются неизменяемыми (final). Обратите внимание, что во время объявления инициализация неизменяемых параметров не требуется, но они должны быть однократно проинициализированы перед использованием.

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

Неизменяемые переменные гарантируют, что значения не могут быть изменены, а неизменяемые методы гарантируют, что поведение не может быть изменено.

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

В примере 4.11 в классе Light определяется константа в строке (1) и неизменяемый метод в (2). Попытка изменения значения константы в строке (3) в результате приводит к ошибке на этапе компиляции. Подкласс TubeLight пытается переопределить неизменяемый метод setWatts() суперкласса Light в строке (4), что недопустимо. В классе Warehouse определена неизменяемая локальная ссылка aLight в строке (5). Состояние объекта, обозначенного указанной ссылкой aLight, может быть изменено в строке (6), но значение ссылки нет, что показано в строке (7).

Пример 4.11. Доступ к final-методам

class Light {
    // Final static variable                  (1)
    final public static double KWH_PRICE = 3.25;

    int noOfWatts;

    // Final instance method                  (2)
    final public void setWatts(int watt) {
         noOfWatts = watt;
    }
    public void setKWH() {
      // KWH_PRICE = 4.10;                 // (3) Нельзя. Не может быть изменен
    }
}

class TubeLight extends Light {
    // Неизменяемый метод суперкласса нельзя переопределить.
    // Этот метод не откомпилируется
    /*
    public void setWatts(int watt) {       // (4) Попытка переопределения
         noOfWatts = 2*watt;
    }
    */
}

public class Warehouse {
    public static void main(String[] args) {

        final Light aLight = new Light();// (5) Неизменяемая локальная переменная
        aLight.noOfWatts = 100;          // (6) OK. Изменение состояния объекта.
    //  aLight = new Light();            // (7) Нельзя. Изменение неизменяемой ссылки
    }
}

Абстрактные методы

Абстрактный метод имеет следующий синтаксис:

abstract <модификатор доступа> <возвращаемый тип> <имя метода> (<список параметров>)
            <throws-выражение>;

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

Только метод экземпляра может быть объявлен как abstract. Поскольку статические методы не могут быть переопределены, объявление абстрактного статического метода не имеет смысла. Final-метод не может быть абстрактным (т.е. не может быть незавершенным). Ключевое слово abstract не может быть объединено с модификаторами, отказывающими в доступе к методу. Методы, определенные в интерфейсе, неявно являются абстрактными, так как в интерфейсе задаются только прототипы методов.

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

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

В примере 4.12 оба метода push() и pop() в классе StackImpl являются синхронизованными. Теперь только один поток за раз может выполнить синхронизованный метод объекта класса stackImpl. Это означает, что нельзя повредить состояние объекта класса stackImpl в случае, например, когда один поток помещает элемент в стек, а другой одновременно достает элемент из стека.

Пример 4.12. Синхронизованные методы

class StackImpl {
    private Object[] stackArray;
    private int topOfStack;
    // ...
    synchronized public void push(Object elem) { // (1)
        stackArray[++topOfStack] = elem;
    }

    synchronized public Object pop() {           // (2)
        Object obj = stackArray[topOfStack];
        stackArray[topOfStack] = null;
        topOfStack--;
        return obj;
    }

    // Other methods, etc.
    public Object peek() { return stackArray[topOfStack]; }
}

Встраиваемые методы

Встраиваемые (native) методы также называют внешними методами. Их реализация определяется не на Java, а на другом языке программирования (например, C или C++). Такой член может быть объявлен как член Java-класса. Поскольку реализация метода определена где-то в другом месте, только прототип метода задается в описании класса. В прототипе такого метода используется ключевое слово native. Также можно определить выбрасываемые исключения после throws, которые не могут быть обработаны компилятором, так как метод реализован не на Java. В следующем примере показано, как используются встраиваемые методы.

Программный интерфейс Java Native Interface (JNI) — это специальный программный интерфейс API, который позволяет методам Java вызывать встраиваемые функции, например реализованные на C.

В следующем примере в классе Native в строке (2) объявлен встраиваемый метод. В классе также используется блок статической инициализации (см. раздел 8.2) в строке (1) для загрузки встраиваемой библиотеки на этапе загрузки класса. Клиенты класса Native могут вызывать встраиваемые методы, как любые другие, как в строке (3).

class Native {

    /*
     * Статический блок гарантирует, что библиотека native-методов
     * загрузится до вызова native-метода
     */
    static {
        System.loadLibrary("NativeMethodLib");  // (1) Загрузка библиотеки native.
    }

    native void nativeMethod();                 // (2) прототип native-метода.
    // ...

}

class Client {
    //...
    public static void main(String[] args) {
        Native aNative = new Native();
        aNative.nativeMethod();                 // (3) Вызов native-метода.
    }
    //...
}
Модификаторы доступа членов классаТранзитивные и изменчивые поля