Спецификатор final. Неизменные данные, методы, классы. Пустые константы. Статические неизменные данные. Inline методы

Спецификатор final. Неизменные данные, методы, классы. Пустые константы. Статические неизменные данные. Inline методы


Содержание


1. Какое назначение использования спецификатора final в программах на Java?

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

Если при объявлении некоторой переменной применяется спецификатор final, то эта переменная автоматически становится константой. Попытка изменить значение final-константы приведет к ошибке.

2. В каких элементах языка программирования Java может применяться спецификатор final?

Спецификатор final может применяться к таким элементам языка как:

  • данные (константы, переменные);
  • методы;
  • классы.

3. Какими способами можно инициализировать неизменные данные в классе, которые объявлены со спецификатором final?

Существуют два способа установить начальное значение неизменных данных:

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

4. Какое отличие между неизменными и изменяемыми данными? Какие особенности неизменных данных в классе?

Между неизменными данными и изменяемыми данными есть два следующих отличия:

  • неизменные данные объявляются со спецификатором final;
  • неизменные данные получают значение только один раз при их объявлении или с использованием конструктора класса. Данные, которые изменяются, могут получать разные значения сколько угодно. То есть, неизменные данные нельзя использовать в левой части оператора присваивания = (в отличие от данных, которые изменяются).

5. Пример, который демонстрирует применение спецификатора final с одиночными данными (переменными)

Объявляется класс ConstData, содержащий неизменные данные в виде переменных a, c. При попытке изменить значения a, c компилятор выдает ошибку. Действие спецификатора final распространяется и на статические переменные (переменная c). Ниже приведен программный код класса ConstData

// класс, который содержит неизменные данные
class ConstData {
  public final int a=32; // переменная a имеет неизменное значение
  public int b=50; // переменная, значение которой можно изменять
  public final static double c=3.85; // значение статического члена c есть неизменным

  // метод, который изменяет значение b
  public void Change() {
    // a = 45; - ошибка, нельзя изменять значение final-переменной
    b = 120; // это работает, так как b объявленная без final
    //ConstData.c = 320.53; - ошибка, переменная c объявлена как final
  }
}

Следующий программный код демонстрирует применение класса ConstData.

// использование объекта класса ConstData
ConstData fd = new ConstData();
fd.Change();

int t = fd.b; // t = 120
t = fd.a; // t = 32, final-данные можно только читать

// нельзя изменять значение статического члена ConstData.c
// ConstData.c = 230.55; - ошибка!
double d = ConstData.c; // d = 3.85

6. Каким образом осуществляется начальная инициализация final-данных с помощью конструктора? Пример

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

Например. Объявляется класс SetConstData, в котором объявляются разные виды данных:

  • обычные данные (переменные), значения которых можно изменять;
  • неизменные нестатические данные, которые объявлены с использованием спецификатора final;
  • неизменные статические данные, которые объявлены с объединением спецификаторов static и final.

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

Реализация класса имеет следующий вид:

// класс, который содержит различные виды данных
class SetConstData {
  public final int a; // константу a обязательно нужно инициализировать в конструкторе
  public int b=50; // переменная, значение которой можно изменять много раз
  public final static double c=0; // значение статического члена c инициализируется при объявлении
  public final char d = 'Z'; // константа d уже инициализирована, в конструкторе ее нельзя инициализировать
  public final int e = 25; // присваивается значение при объявлении
  public final boolean f; // обязательно должно присваиваться значение в конструкторе

  // конструктор, который изменяет значения a, b, f
  SetConstData() {
    a = 100; // можно установить только один раз значение final-переменной a
    b = 200;

    // SetConstData.c = 300.0;
    // запрещено инициализировать final-переменную d, так как она уже инициализирована
    // d = 'F'; - ошибка

    // обязательно нужно 1 раз инициализировать final-константу
    f = true;
  }

  // второй конструктор, который инициализирует неизменные данные
  SetConstData(int _a, int _b, boolean _f) {
    // можно инициализировать только неизменные данные a, f
    a = _a;
    b = _b;
    f = _f;
  }

  // третий конструктор, который инициализирует неизменные данные
  SetConstData(int _a, boolean _f) {
    a = _a;
    f = _f;
  }

  // метод, который изменяет значение b
  public void Change() {
    // a = 45; - ошибка, нельзя изменять значение final-переменной
    b = 120; // это работает, так как b объявлена без final
    //FinalData.c = 320.53; - ошибка, переменная c объявлена как final
  }
}

В вышеприведенном коде, неизменные данные (поля) a и f есть пустыми константами, которые обязательно должны быть инициализированы в конструкторе класса.

Использование класса SetConstData может быть следующим:

// Использование разных видов конструкторов для инициализации неизменных данных
//SetConstData.c = 32309; - нельзя присваивать final-статическим данным значения
double d = SetConstData.c; // d = 0.0, читать неизменные статические данные можно

// создать экземпляр класса SetConstData с использованием конструктора без параметров
SetConstData sd = new SetConstData();
boolean f = sd.f; // f = true
int t = sd.a; // t = 100
t = sd.e; // t = 25

// создать экземпляр SetConstData с использованием конструктора с 3 параметрами
SetConstData sd2 = new SetConstData(15,35,false); // инициализируются неизменные данные
t = sd2.a; // t = 15
t = sd2.b; // t = 35
f = sd2.f; // f = false

// создать экземпляр SetConstData с использованием конструктора с 2 параметрами
SetConstData sd3 = new SetConstData(9, true);
t = sd3.a; // t = 9
f = sd3.f; // f = true

7. Что произойдет, если данные, которые объявляются со спецификатором final, не инициализировать значениями в классе?

Если в классе объявляются final-данные, то они должны быть один раз инициализированы некоторым значением. Если не инициализировать final-данные ни одним из известных способов, то компилятор выдаст предупреждение о возможной ошибке.

Например. В нижеследующем классе объявляется final-поле класса

// инициализация
class InitFinalData {
  final double pi = 3.1415;
  final int d; // значение d неинициализировано - может быть ошибка
}

Компилятор Java такое описание пропускает с предупреждением

The blank final field d may not have been initialized

что означает, что данные могли быть неинициализированы.

8. Какие особенности неизменных данных, которые объявлены как статические (static)?

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

  • статические неизменные данные объявляются с объединением спецификаторов final static;
  • значение статического неизменного члена данных инициализируется при объявлении. Нельзя инициализировать значение статического неизменного члена данных в конструкторе класса;
  • статическому неизменному члену данных класса значение присваивается только 1 раз при объявлении.


9. Что такое пустые константы?

Пустые константы – это поля класса, которые объявляются со спецификатором final но которые не инициализированы начальным значением (которым не было присвоенное начальное значение).

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

10. Могут ли статические (static) поля данных класса быть пустой константой?

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

final static int si = 25;
final static double pi = 3.1415;
final static char c = '0';
final static int t; // это может быть ошибка, значение t неинициализировано

11. Что такое неизменные аргументы? Пример

Неизменные аргументы – это аргументы, которые, при передаче в метод, объявляются со спецификатором final. Неизменные аргументы объявляются при реализации метода. Значение неизменного аргумента в методе изменять нельзя. Если попробовать изменить значение неизменного аргумента, то выйдет ошибка компиляции.

Например. Объявляется класс FinalArguments, содержащий два метода которые получают final-аргументы. Первый метод Power() получает два неизменных (final) параметры x, y. Метод возводит x в степень y. Второй метод Sqr() возвращает квадрат числа, которое есть входным параметром x, который объявляется как неизменный (final).

Программный код класса FinalArguments следующий

class FinalArguments {
  // возводит x в степень y
  public double Power(final int x, final int y) {
    double res = 1.0;
    for (int i=0; i<y; i++)
      res = res * x;
    return res;
  }

  // возвращает квадрат числа
  public double Sqr(final double x) {
    return x*x;
  }
}

Ниже приведено использование методов класса FinalArguments

// использование методов класса FinalArguments
FinalArguments fa = new FinalArguments();
double d;
d = fa.Power(8, 5); // d = 32768.0
d = fa.Sqr(5); // d = 25.0

Если метод Sqr() в классе FinalArguments переписать, например, следующим образом

public double Sqr(final double x) {
  x = x*x; // это есть ошибка, поскольку изменяется значение x
  return x;
}

то компилятор выдаст ошибку

The final local variable x cannot be assigned

Это происходит по той причине, что в методе Sqr() осуществляется попытка изменить значение неизменного аргумента x.

12. Для чего используются неизменные методы?

В языке программирования Java спецификатор final можно ставить перед объявлением метода. Такой метод называется неизменным методом.

Неизменный метод объявляется в следующих случаях:

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

13. Что такое inline-методы?

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

  • занести аргументы в стек (если метод имеет параметры);
  • перейти к телу метода;
  • выполнить код (тело) метода;
  • возвратить управление из метода;
  • удалить аргументы из стека;
  • обработать возвращенное из метода значение.

Inline-методы – это методы, тело которых непосредственно вставляется в программный код вызывающей программы (метода). В этом случае отсутствуют лишние шаги при вызове метода: занесение аргументов в стек, переход и возврат из метода. Отсутствие лишних шагов приводит к повышению скорости выполнения вызванного метода. То есть, увеличивается общее быстродействие программы, которая вызвала метод.

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

14. Пример объявления и использования неизменных методов

Задан класс A, который есть базовым для класса B. В классе A объявляются

  • одна внутренняя переменная a;
  • два неизменных метода Set() и Get(). Эти методы запрещено переопределять в унаследованном классе B;
  • два обычных метода Set() и Get(), которые можно переопределять в унаследованном классе.

Объявление классов A и B имеет вид:

// класс A - базовый класс
class A {
  public int a;

  // методы, которые запрещено переопределять в унаследованных классах
  final void Set(int _a) { a = _a;   }
  final int Get() { return a; }

  // методы, которые разрешено переопределять в унаследованных классах
  void SetA(int _a) { a = _a; }
  int GetA() { return a; }
}

// класс B наследует (расширяет) возможности класса A
class B extends A {
  public int b;

  // методы класса B, переопределяют методы класса A
  void SetA(int _a) { a = _a; }
  int GetA() { return a; }

  // запрещено объявлять методы Set() и Get() в классе B
  // void Set(int _a) { a = _a; }
  // int Get() { return a; }
}

Использование экземпляров классов A и B может быть, например, следующим

// использование объектов классов A и B
A objA = new A();
objA.Set(5); // objA.a = 5
int t = objA.Get(); // t = 5
t = objA.GetA(); // t = 5
B objB = new B();
objB.Set(12);
t = objB.Get(); // t = 12
objB.SetA(54);
t = obj.Get(); // t = 54
t = obj.Get(); // t = 54

Если в классе B попробовать объявить методы Set() и Get() следующим образом

class B extends A {
  ...

  // запрещено объявлять методы Set() и Get() в классе B
  void Set(int _a) { a = _a; }
  int Get() { return a; }
}

то компилятор выдаст ошибку

Cannot override the final method from A

15. Какие особенности использования спецификаторов final и private для методов?

Спецификатор private делает метод класса невидимым. То есть, унаследованные классы не могут его переопределить или изменить точно также как при объявлении final-метода. Это означает, что совместное объединение этих спецификаторов создает избыточность в объявлении. Если метод объявлен как private, то нецелесообразно к его объявлению добавлять спецификатор final, так как это ничего не изменит.

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

16. Может ли неизменный метод быть объявлен как статический (static)?

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

17. Что такое неизменные классы? В каких случаях целесообразно применять неизменные классы? Пример

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

// неизменный класс
final class ClassName {
  // тело класса
  // ...
}

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

Пример. Пусть задано объявление неизменного класса A:

// неизменный класс A
final class A {
  int a;
}

После такого объявления, объявить класс, который наследует (расширяет) класс A нельзя. Если попробовать это сделать, то компилятор выдаст ошибку.

При следующей попытке объявить класс B, который наследует (расширяет) возможности класса A

// нельзя наследовать класс A
class B extends A {
  int b;
}

компилятор выдает ошибку:

The type B cannot subclass the final class A

что значит

Тип B не может наследовать конечный класс A

Использование final-класса A в другом программном коде может быть следующим:

// объявить объект класса A
A objA = new A();
objA.a = 32;
int t = objA.a; // t = 32


Связанные темы