Зачем использовать ключевое слово final при создании анонимных классов
Перейти к содержимому

Зачем использовать ключевое слово final при создании анонимных классов

  • автор:

Зачем использовать ключевое слово final при создании анонимных классов

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

// Использование явного класса
class Logger
public function log ( $msg )
echo $msg ;
>
>

$util -> setLogger (new Logger ());

// Использование анонимного класса
$util -> setLogger (new class public function log ( $msg )
echo $msg ;
>
>);

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

class SomeClass <>
interface SomeInterface <>
trait SomeTrait <>

var_dump (new class( 10 ) extends SomeClass implements SomeInterface private $num ;

public function __construct ( $num )
$this -> num = $num ;
>

Результат выполнения приведённого примера:

object(class@anonymous)#1 (1) < ["Command line code0x104c5b612":"class@anonymous":private]=>int(10) >

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

class Outer
private $prop = 1 ;
protected $prop2 = 2 ;

protected function func1 ()
return 3 ;
>

public function func2 ()
return new class( $this -> prop ) extends Outer private $prop3 ;

public function __construct ( $prop )
$this -> prop3 = $prop ;
>

public function func3 ()
return $this -> prop2 + $this -> prop3 + $this -> func1 ();
>
>;
>
>

echo (new Outer )-> func2 ()-> func3 ();

Результат выполнения приведённого примера:

Все объекты, созданные одним и тем же объявлением анонимного класса, являются экземплярами этого самого класса.

if ( get_class ( anonymous_class ()) === get_class ( anonymous_class ())) echo ‘Тот же класс’ ;
> else echo ‘Другой класс’ ;
>

Результат выполнения приведённого примера:

Тот же класс

Замечание:

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

echo get_class (new class <>);

Вывод приведённого примера будет похож на:

class@anonymous/in/oNi1A0x7f8636ad2021

Improve This Page

User Contributed Notes 9 notes

8 years ago

Below three examples describe anonymous class with very simple and basic but quite understandable example

// First way — anonymous class assigned directly to variable
$ano_class_obj = new class public $prop1 = ‘hello’ ;
public $prop2 = 754 ;
const SETT = ‘some config’ ;

public function getValue ()
// do some operation
return ‘some returned value’ ;
>

public function getValueWithArgu ( $str )
// do some operation
return ‘returned value is ‘ . $str ;
>
>;

var_dump ( $ano_class_obj );
echo «\n» ;

echo $ano_class_obj -> prop1 ;
echo «\n» ;

echo $ano_class_obj -> prop2 ;
echo «\n» ;

echo $ano_class_obj :: SETT ;
echo «\n» ;

echo $ano_class_obj -> getValue ();
echo «\n» ;

echo $ano_class_obj -> getValueWithArgu ( ‘OOP’ );
echo «\n» ;

// Second way — anonymous class assigned to variable via defined function
$ano_class_obj_with_func = ano_func ();

function ano_func ()
return new class public $prop1 = ‘hello’ ;
public $prop2 = 754 ;
const SETT = ‘some config’ ;

public function getValue ()
// do some operation
return ‘some returned value’ ;
>

public function getValueWithArgu ( $str )
// do some operation
return ‘returned value is ‘ . $str ;
>
>;
>

var_dump ( $ano_class_obj_with_func );
echo «\n» ;

echo $ano_class_obj_with_func -> prop1 ;
echo «\n» ;

echo $ano_class_obj_with_func -> prop2 ;
echo «\n» ;

echo $ano_class_obj_with_func :: SETT ;
echo «\n» ;

echo $ano_class_obj_with_func -> getValue ();
echo «\n» ;

echo $ano_class_obj_with_func -> getValueWithArgu ( ‘OOP’ );
echo «\n» ;

// Third way — passing argument to anonymous class via constructors
$arg = 1 ; // we got it by some operation
$config = [ 2 , false ]; // we got it by some operation
$ano_class_obj_with_arg = ano_func_with_arg ( $arg , $config );

function ano_func_with_arg ( $arg , $config )
return new class( $arg , $config ) public $prop1 = ‘hello’ ;
public $prop2 = 754 ;
public $prop3 , $config ;
const SETT = ‘some config’ ;

public function __construct ( $arg , $config )
$this -> prop3 = $arg ;
$this -> config = $config ;
>

public function getValue ()
// do some operation
return ‘some returned value’ ;
>

public function getValueWithArgu ( $str )
// do some operation
return ‘returned value is ‘ . $str ;
>
>;
>

var_dump ( $ano_class_obj_with_arg );
echo «\n» ;

echo $ano_class_obj_with_arg -> prop1 ;
echo «\n» ;

echo $ano_class_obj_with_arg -> prop2 ;
echo «\n» ;

echo $ano_class_obj_with_arg :: SETT ;
echo «\n» ;

echo $ano_class_obj_with_arg -> getValue ();
echo «\n» ;

echo $ano_class_obj_with_arg -> getValueWithArgu ( ‘OOP’ );
echo «\n» ;

Что означает модификатор final в полях классов?

Я заметил, что профессиональные разработчики нередко объявляют поля классов в джаве как final, например:

@Component public class LinkResolver implements GraphQLResolver  < private final UserRepository userRepository; public LinkResolver(UserRepository userRepository) < this.userRepository = userRepository; >public User postedBy(Link link) < if (link.getUserId() == null) < return null; >return userRepository.findById(link.getUserId()); > > 

Для чего это делается? Я видел, как в проекте final объявляются не только поля сервисов и репозиториев, но и поля дата-классов. Расскажите об этом больше, пожалуйста.

Отслеживать
18.6k 4 4 золотых знака 32 32 серебряных знака 45 45 бронзовых знаков
задан 5 мар 2019 в 9:57
2,517 3 3 золотых знака 31 31 серебряный знак 50 50 бронзовых знаков
Чтобы можно было только один раз инициализировать переменную, например только в конструкторе
5 мар 2019 в 10:00

4 ответа 4

Сортировка: Сброс на вариант по умолчанию

  1. Удобно чисто визуально. Если видишь, что переменная final , то точно знаешь, что она не будет меняться.
  2. Без final никак, если ты локальную переменную собираешься использовать в анонимных классах/замыканиях.
  3. final — это подсказка компилятору. Если он видит финальные поля, то может произвести определённые оптимизации кода.

Отслеживать
ответ дан 5 мар 2019 в 10:03
Suvitruf — Andrei Apanasik Suvitruf — Andrei Apanasik
32.3k 15 15 золотых знаков 61 61 серебряный знак 93 93 бронзовых знака
А final, примененное к полям, может иметь какое-то значение при наследовании?
5 мар 2019 в 10:29
@typemoon принципиальной нет. Поля всё также остаются неизменяемыми.
5 мар 2019 в 10:31
в ответе кроется одно коварство, некоторые новички путают final с иммутабельностью.
5 мар 2019 в 10:45
4. final-поля безопасно публикуются в многопоточном окружении
5 мар 2019 в 10:46
@StrangerintheQ ещё про рефлексию можно написать )
5 мар 2019 в 11:20

Многа букав: По-умолчанию ставятся максимальные ограничения. Снимать ограничение (final) с поля имеет смысл только если на это есть причина (поле изменяется).

С помощью final отмечаются поля, которые инициализируются только один раз. Отмечать такое поле как final технически необязательно, но если этого не сделать, то:

  • останется возможность ошибки: разработчик опечатается и переприсвоит значение;
  • при чтении кода возникнут вопросы: «Где это поле изменяется? И как это повлияет на остальной код?». Модификатор final говорит разработчику что лишние сценарии рассматривать не требуется.

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

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

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

На практике поля, как важную часть классов, везде где возможно отмечают как final. На локальных переменных и аргументах методов же обычно «экономят», т.к. срок жизни у них короче и риск побочных эффектов меньше. Для них наоборот используют final только там где это явно необходимо (использование в анонимных классах). Например, в Вашем коде аргументы конструктора ( userRepository ) и метода ( link ) могли бы быть отмечены как final , но не отмечены для простоты кода.

Вот так final…

Вот так final… - 1

В java есть ключевое слово – final . Оно может применяться к классам, методам, переменным (в том числе аргументам методов). Для класса это означает, что класс не сможет иметь подклассов, т.е. запрещено наследование. Это полезно при создании immutable (неизменяемых) объектов, например, класс String объявлен, как final .

 public final class String < >class SubString extends String < //Ошибка компиляции >

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

 public class SuperClass < public final void printReport()< System.out.println("Report"); >> class SubClass extends SuperClass < public void printReport()< //Ошибка компиляции System.out.println("MyReport"); >> 

Java-университет

Для переменных примитивного типа это означает, что однажды присвоенное значение не может быть изменено. Для ссылочных переменных это означает, что после присвоения объекта, нельзя изменить ссылку на данный объект. Это важно! Ссылку изменить нельзя, но состояние объекта изменять можно. С java 8 появилось понятие — effectively final . Применяется оно только к переменным (в том числе аргументам методов). Суть в том, что не смотря на явное отсутствие ключевого слова final , значение переменной не изменяется после инициализации. Другими словами, к такой переменной можно подставить слово final без ошибки компиляции. effectively final переменные могут быть использованы внутри локальных классов ( Local Inner Classes ), анонимных классов ( Anonymous Inner Classes ), стримах (Stream API).

 public void someMethod() < // В примере ниже и a и b - effectively final, тк значения устанавливаютcя однажды: int a = 1; int b; if (a == 2) b = 3; else b = 4; // с НЕ является effectively final, т.к. значение изменяется int c = 10; c++; Stream.of(1, 2).forEach(s->System.out.println(s + a)); //Ок Stream.of(1, 2).forEach(s-> System.out.println(s + c)); //Ошибка компиляции > 
  1. Что можно сказать про массив, когда он объявлен final ?
  2. Известно, что класс String — immutable , класс объявлен final , значение строки хранится в массиве char , который отмечен ключевым словом final .
 public final class String implements java.io.Serializable, Comparable, CharSequence < /** The value is used for character storage. */ private final char value[]; 
  1. Т.к. массив – это объект, то final означает, что после присвоения ссылки на объект, уже нельзя ее изменить, но можно изменять состояние объекта.
 final int[] array = ; array[0] = 9; //ок, т.к. изменяем содержимое массива – array = new int[5]; //ошибка компиляции 
 import java.lang.reflect.Field; class B < public static void main(String[] args) throws Exception < String value = "Old value"; System.out.println(value); //Получаем поле value в классе String Field field = value.getClass().getDeclaredField("value"); //Разрешаем изменять его field.setAccessible(true); //Устанавливаем новое значение field.set(value, "JavaRush".toCharArray()); System.out.println(value); /* Вывод: * Old value * JavaRush */ >> 

Обратите внимание, что если бы мы попытались изменить подобным образом финальную переменную примитивного типа, то ничего бы не вышло. Предлагаю вам самостоятельно в этом убедить: создать Java класс, например, с final int полем и попробовать изменить его значение через Reflection API. Всем удачи!

Малоизвестные особенности Java

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

1. Нестатические блоки инициализации.

Всем, я думаю, известно, что в Java существуют статические блоки инициализации (class initializers), код которых выполняется при первой загрузке класса.

class Foo < static Listabc; static < abc = new LinkedList(); for (char c = 'A'; c > >

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

class Bar < < System.out.println("Bar: новый экземпляр"); >>

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

Map map = new HashMap() >;

Очень даже мощное средство, не находите?

JFrame frame = new JFrame() >); add(new JButton("Торт!") >); >>); >>); >>;

Остальные четыре пункта под катом.

2. Вложенные в интерфейсы классы.

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

interface Colorable < public Color getColor(); public static class Color < private int red, green, blue; Color(int red, int green, int blue) < this.red = red; this.green = green; this.blue = blue; >int getRed() < return red; >int getGreen() < return green; >int getBlue() < return blue; >> > class Triangle implements Colorable < private Color color; // . @Override public Color getColor() < return color; >>

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

Colorable.Color color = new Colorable.Color(0, 0, 0); color = new Triangle.Color(255, 255, 255);

Самым, наверное, известным примером этой идиомы является класс Map.Entry, содержащий пары ключ-значение ассоциативного словаря.

3. Коварианты возвращаемых типов.

Начиная с Java SE 5 типы возвращаемых результатов из методов ковариантны (covariant). Это означает, что мы можем в перекрытом методе (overriden) в качестве типа результата использовать подтип результата перекрываемого метода.

class Covariance implements Cloneable < @Override public Covariance clone() < Object cloned = null; try < cloned = super.clone(); >catch (CloneNotSupportedException exc) < // В данном примере недостижимо. >return (Covariance)cloned; > >

Метод Object.clone() имеет такую сигнатуру:

 protected Object clone()

Заметьте, возвращаемый тип изменён с Object на Covariance. Теперь, к примеру, нет нужды приводить результат работы метода clone() к действительному типу объекта, как это требовалось в ранних версиях JDK. Вместо этого кода:

Covariance foo = new Covariance(); Covariance bar = (Covariance)foo.clone();

Мы можем смело писать код следующий:

Covariance foo = new Covariance(); Covariance bar = foo.clone();

4. Выход из любого блока операторов.

Хотя goto и является зарезервированным ключевым словом Java, использовать его в своих программах нельзя. Пока? На смену ему пришли операторы break и continue, позволяющие прерывать и продолжать (соответственно) не только текущий цикл, но также и любой обрамляющий цикл, обозначенный меткой:

String a = "quadratic", b = "complexity"; boolean hasSame = false; outer: for (int i = 0; i < a.length(); ++i) < for (int j = 0; j < b.length(); ++j) < if (a.charAt(i) == b.charAt(j)) < hasSame = true; break outer; >> > System.out.println(hasSame);

Но многие даже не догадываются, что в Java мы всё же можем при помощи оператора break не только прервать цикл, но и покинуть совершенно любой блок операторов. Чем не оператор goto, правда, односторонний? Как говорится, вперёд и ни шагу назад.

long factorial(int n) < long result = 1; scope: < if (n == 0) < break scope; >result = n * factorial(n - 1); > return result; >

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

5. Модификация данных из внутренних классов.

Хотя в Java и предусмотрено ключевое слово final, однако на деле отсутствует возможность задать неизменяемость самого объекта, а не указывающей на него ссылки (не относится к примитивам). Ну, в принципе, можно спроектировать неизменяемый (immutable) класс, предоставив только геттеры и чистые функции, но нельзя, к примеру, создать неизменяемый массив. Это, как мне кажется, существенное упущение в дизайне языка. Тут бы пригодилось зарезервированное, но запрещённое ключевое слово const. Ждём в следующих версиях?

final int[] array = ; new Object() < void twice() < for (int i = 0; i < array.length; ++i) < array[i] *= 2; >> >.twice();

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

Если вам статья понравилась — продолжение следует.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *