Создание Вложенных Классов В Python
В этом разделе мы в первую очередь сосредоточимся на создании вложенных классов. Для этого давайте рассмотрим пример.
class language: def __init__(self): .specification() def show(self): print("Language:", self.language) class specification: def __init__(self): def display(self): print("type:", self.type) print("Founded:", self.founded) () out.show() .lg ppool.display()
Language: PYTHON type: HIGH-LEVEL Founded: 1991
Здесь выше мы успешно создали вложенный класс . Теперь давайте пройдемся строчка за строчкой и поймем, что мы это сделали. Итак, сначала мы создали класс с именем language. Внутри него мы использовали ключевое слово self. Ключевое слово self-это ключ, через который мы можем получить доступ к атрибутам и методам aнашего созданного класса. Внутри класса language мы создали еще один вложенный класс под названием specification. Там мы точно так же определили спецификации. Наконец, мы получаем желаемый результат.
Общие сведения и определения
Все подпрограммы, которые есть в объектно-ориентированном программировании, тесно связаны между собой. Основное понятие, которое нужно знать на этом этапе – это класс.
Под классом подразумевается тот тип данных, в котором приводится описание структуры объекта. Все программы, написанные с помощью объектно-ориентированных языков, основываются на объектах. В случае с Excel такими служат формы, лист, книги, диаграммы и другие. Класс может объединять в себя целую кучу объектов, каждый из которых является своеобразной его копией с некоторыми доработками.
Выражаясь проще, класс – это шаблон, по которому генерируется объект.
Структура класса:
- Поле. Это такой элемент класса, в котором непосредственно содержатся данные.
- Свойство. Это составная часть класса, в которой также хранятся данные, которые в дальнейшем могут быть обработаны.
- Метод. По сути та же процедура или функция, просто в рамках класса. Методы – одно из главных понятий любого объектно-ориентированного языка программирования.
- Событие. Это ситуация, при которой активируется метод. Это может быть изменение содержимого ячеек или выполнение другого метода.
VBA не является полноценным объектно-ориентированным языком программирования, поскольку в нем есть только два принципа:
- Абстракция. С помощью этого метода можно создать объект, который больше всего напоминает некомпьютерный. Например, можно создать объект «собака», в котором будут такие свойства, как «количество ушей», «длина шерсти» и другие.
- Инкапсуляция. Она дает возможность спрятать класс от других процедур, которые не используют его в своей работе. Но этот принцип допускает использование отдельных свойств класса другими процедурами, если это требуется.
Универсальный тип Java
Соглашение об именовании общих типов Java помогает нам легко понимать код, и наличие соглашения об именовании является одной из лучших практик языка программирования Java. Таким образом, дженерики также имеют свои собственные соглашения об именовании. Обычно имена параметров типа состоят из отдельных прописных букв, чтобы их можно было легко отличить от переменных java. Наиболее часто используемыми именами параметров типа являются:
- E – элемент (широко используется платформой коллекций Java, например ArrayList, Set и т.д.)
- K – клавиша (используется на карте)
- N – Число
- Т – Образный
- V – Значение (используется на карте)
- S,U,V и т.д. – 2-й, 3-й, 4-й типы
Ограничиваем дженерики сверху и снизу
Давайте немного расширим наше представление о мусоре и введём для него дополнительное свойство — массу «типичного представителя», то есть массу одной пластиковой бутылки или листка бумаги, например.
Теперь попробуем использовать эту массу в методе уже знакомого класса Box:
И получим ошибку при компиляции: мы не рассказали компилятору, что T — это какой-то вид мусора. Исправим это с помощью так называемого upper bounding — ограничения сверху:
Теперь метод getItemWeight успешно скомпилируется.
Здесь T extends Garbage означает, что в качестве T можно подставить Garbage или любой класс-наследник Garbage. Из уже известных нам классов это могут быть, например, Paper или Plastic. Так как и у Garbage, и у всех его наследников есть метод getWeight, его можно вызывать в новой версии дженерик-класса Box.
Для одного класса или интерфейса можно добавить сразу несколько ограничений. Вспомним про интерфейс для пункта приёма мусора и введём класс для метода переработки — HandleMethod. Тогда GarbageHandler можно переписать так:
В качестве границы может выступать класс, интерфейс или перечисление (enum), но не примитивный тип и не массив. При этом для интерфейсов тоже используется слово extends, а не implements: <T extends SomeInterface> вместо <T implements SomeInterface>.
Wildcards
До этого мы использовали для параметров типов буквенные имена, но в Java есть и специальный символ для обозначения неизвестного типа — «?». Его принято называть wildcard, дословно — «дикая карта».
Термин wildcard пришёл в программирование из карточной игры. В покере, например, так называют карту, которая может сыграть вместо любой другой. Джокер — известный пример такой «дикой карты».
Wildcard нельзя подставлять везде, где до этого мы писали буквенные обозначения. Не получится, например, объявить класс Box<?> или дженерик-метод, который принимает такой тип:
Wildcards удобно использовать для объявления переменных и параметров методов совместно с классами из Java Collection Framework — здесь собраны инструменты Java для работы с коллекциями. Если вы не очень хорошо знакомы с ними, освежите знания, прочитав эту статью.
В примере ниже мы можем подставить вместо «?» любой тип, в том числе Paper, поэтому строка успешно скомпилируется:
Wildcards можно применять для ограничений типов:
Это уже знакомое нам ограничение сверху, upper bounding, — вместо «?» допустим Garbage или любой его класс-наследник, то есть Paper подходит.
Но можно ограничить тип и снизу. Это называется lower bounding и выглядит так:
Здесь <? super Garbage> означает, что вместо «?» можно подставить Garbage или любой класс-предок Garbage. Все ссылочные классы неявно наследуют класс Object, так что в правой части ещё может быть ArrayList<Object>.
Методы класса
В рамках класса можно создавать как процедуры, так и функции. Все они суммарно называются методами, но лишь при одном условии. Чтобы функция считалась методом, она должна быть открыта для других классов. Если же не требуется таких прав для подпрограмм, то нужно обязательно их делать приватными. Это обязательное правило, которое соответствует принципу инкапсуляции.
В целом, процедура создания методов ничем не отличается от того, как создаются процедуры и функции. В них могут содержаться любые аргументы как те, которые нужно обязательно указывать, так и те, в которых смысла нет. При этом отличается вызов. Внимательнее посмотрите на следующий пример.
8
Итерация
Вы можете выполнить итерацию несколькими способами. Три наиболее распространенных:
- Использование итератора.
- Использование цикла for-each.
- Использование цикла for.
- Использование API Java Stream.
Я объясню каждый из этих методов итерации списка Java в следующих разделах.
Итерация с использованием итератора
Вот пример:
List list = new ArrayList(); list.add("first"); list.add("second"); list.add("third"); Iterator iterator = list.iterator(); while(iterator.hasNext()) { Object next = iterator.next(); }
Вы получаете Iterator, вызывая метод iterator() интерфейса List.
Получив Iterator, вы можете продолжать вызывать его метод hasNext(), пока он не вернет false. Вызов hasNext() выполняется внутри цикла while.
Внутри цикла while вы вызываете метод Iterator next() интерфейса Iterator для получения следующего элемента, на который указывает Iterator.
Если список напечатан с использованием Java Generics, вы можете сохранить некоторые объекты внутри цикла while. Вот пример:
List list = new ArrayList<>(); list.add("first"); list.add("second"); list.add("third"); Iterator iterator = list.iterator(); while(iterator.hasNext()){ String obj = iterator.next(); }
Итерация с использованием цикла For-Each
Второй способ – использовать цикл for, добавленный в Java 5 (также называемый циклом «для каждого»):
List list = new ArrayList(); list.add("first"); list.add("second"); list.add("third"); for(Object element : list) { System.out.println(element); }
Цикл for выполняется один раз для каждого элемента списка. Внутри него каждый элемент, в свою очередь, связан с переменной obj.
Если список напечатан (List), вы можете изменить тип переменной внутри цикла:
List list = new ArrayList(); //add elements to list for(String element : list) { System.out.println(element); }
Итерация с помощью цикла For
Пример:
List list = new ArrayList(); list.add("first"); list.add("second"); list.add("third"); for(int i=0; i < list.size(); i++) { Object element = list.get(i); }
Цикл for создает переменную int и инициализирует ее равной 0. Затем он зацикливается до тех пор, пока int i меньше размера списка. Для каждой итерации переменная увеличивается.
Внутри цикла for пример обращается к элементам List с помощью метода get(), передавая переменную i в качестве параметра.
Опять же, если список набирается с использованием Java Generics, например, для String, то вы можете использовать универсальный тип List в качестве типа для локальной переменной, которая назначается каждому элементу List в ходе итерации:
List list = new ArrayList(); list.add("first"); list.add("second"); list.add("third"); for(int i=0; i < list.size(); i++) { String element = list.get(i); }
С использованием API Java Stream
Для итерации вы должны сначала получить поток из списка. Это выполняется путем вызова метода List stream(). Вот пример получения потока из списка:
List stringList = new ArrayList(); stringList.add("abc"); stringList.add("def"); Stream stream = stringList.stream();
Последняя строка этого примера вызывает метод List stream() для получения потока, представляющего элементы списка.
Как только вы получили поток, можете выполнить итерацию потока, вызвав его метод forEach():
List stringList = new ArrayList(); stringList.add("one"); stringList.add("two"); stringList.add("three"); Stream stream = stringList.stream(); stream .forEach( element -> { System.out.println(element); });
Вызов метода forEach() заставит Stream выполнить внутреннюю итерацию всех элементов потока и вызвать получателя, переданного в качестве параметра методу forEach() для каждого элемента в потоке.
Code Style на практике
Теперь разберём основные правила, которые есть в Code Style, и как их использовать в ваших рабочих проектах.
1. Название файла должно совпадать с именем главного класса в нём
Классы в java располагаются в отдельных файлах расширения.java, и название файла строго должно соответствовать имени класса, включая регистр.
️ Неправильно: файл main.java
Правильно: файл называется как класс — Main.java
2. Названия классов и интерфейсов
Классы и интерфейсы именуются с большой буквы. Если имя состоит из нескольких слов, то каждое слово также начинается с большой буквы. Подчёркивания, тире не используются. Такой стиль нейминга называется UpperCamelCase.
️ Неправильно: phoneBook, full_name, main, result-list
Правильно: PhoneBook, FullName, Main, ResultList
Класс принято называть существительным: Car, Bird, ArrayList, Book. Возможно добавлять уточняющее прилагательное ImmutableList.
Интерфейс, как и класс, может быть существительным: List, Set, Map. А также прилагательным, если указывает на свойство: Readable, Сomparable, Closable.
Отдельно стоить отметить классы тестов, к ним принято добавлять слово Test в окончании: HashTest, HashIntegrationTest
3. Названия методов
Методы именуются со строчной (маленькой) буквы; если имя состоит из нескольких слов, то каждое последующее слово начинается с большой буквы. Подчёркивания, тире не используются. Такой стиль нейминга называется lowerCamelCase.
️ Неправильно: Print (), get-Size (), Main (), is_hidden ()
Правильно: print (), getSize (), main (), isHidden ()
Методы — это действия, поэтому их принято называть глаголами: sendMessage (), stop (), parse (), add ()
Отдельно можно отменить геттеры и сеттеры. Название метода геттера формируется из названия переменной и префикса get. Если переменная length, то геттер: getLength (). Если переменная типа boolean и названа isAlive, то геттер будет также называться isAlive (), являясь ответом на вопрос в названии метода.
Сеттеры формируются по такому же принципу, но с префиксом set. Продолжая примеры выше: для length сеттер будет setLength (). Для переменной типа boolean isAlive, сеттер будет без приставки setAlive ().
4. Названия переменных
Переменные именуются со строчной (маленькой) буквы; если имя состоит из нескольких слов, то каждое последующее слово начинается с большой буквы. Подчёркивания, тире не используются. Такой стиль нейминга называется lowerCamelCase.
Имена переменных в общем случае — это существительные либо составные существительные с добавлением прилагательных.
Данные правила относятся к локальным переменным (внутри методов), параметрам класса, а также к не статическим константам.
5. Названия констант
Константы именуются в стиле SNAKE_CASE, то есть слова разделены нижним подчёркиванием и все буквы прописные (большие)
Константами считаются static final поля, но не все. Поле должно быть примитивом, String или иммутабельным классом. То есть параметры класса константы не должны иметь возможности изменяться, а его методы — иметь побочные эффекты, влияющие на другие классы. Элементы Enum тоже являются константами.
Константы:
️ Не именуются как константы:
Такие разные реализации
Реализаций интерфейсов так много, что при желании можно организовать вполне себе упорядоченный Map и даже отсортированное множество. Пройдёмся кратко по основным классам.
Реализации List
Класс ArrayList подойдёт в большинстве случаев, если вы уже определились, что вам нужен именно список (а не Map, например).
Строится на базе обычного массива. Если при создании не указать размерность, то под значения выделяется 10 ячеек. При попытке добавить элемент, для которого места уже нет, массив автоматически расширяется — программисту об этом специально заботиться не нужно.
Список проиндексирован. При включении нового элемента в его середину все элементы с большим индексом сдвигаются вправо:
При удалении элемента все остальные с бо́льшим индексом сдвигаются влево:
Класс LinkedList реализует одновременно List и Deque. Это список, в котором у каждого элемента есть ссылка на предыдущий и следующий элементы:
Благодаря этому добавление и удаление элементов выполняется быстро — времязатраты не зависят от размера списка, так как элементы при этих операциях не сдвигаются: просто перестраиваются ссылки.
На собеседованиях часто спрашивают, когда выгоднее использовать LinkedList, а когда — ArrayList.
Правильный ответ таков: если добавлять и удалять элементы с произвольными индексами в списке нужно чаще, чем итерироваться по нему, то лучше LinkedList. В остальных случаях — ArrayList.
В целом так и есть, но вы можете блеснуть эрудицией — рассказать, что под капотом. При добавлении элементов в ArrayList (или их удалении) вызывается нативный метод System.arraycopy. В нём используются ассемблерные инструкции для копирования блоков памяти. Так что даже для больших массивов эти операции выполняются за приемлемое время.
Импорт
Если класс A должен использовать класс B, вы должны ссылаться на класс B внутри класса A. Если классы A и B находятся в одном и том же пакете, компилятор будет принимать ссылки между двумя классами:
public class B { public void doIt() { // do something... } }
public class A { public static void main(String[] args){ B theBObj = new B(); b.doIt(); } }
Если классы A и B находятся в одном и том же пакете, проблем с кодом выше нет. Однако, если класс A и B находятся в разных, класс A должен импортировать класс B, чтобы использовать его:
import anotherpackage.B; public class A { public static void main(String[] args){ B theBObj = new B(); b.doIt(); } }
Это первая строка в примере, которая импортирует класс B. В примере предполагается, что класс B находится в пакете с именем anotherpackage.
Если бы класс B находился в подпакете другого пакета, вам пришлось бы перечислить полный путь пакета и подпакета к классу B. Например, если бы класс B находился в пакете anotherpackage.util, то оператор import выглядел бы так:
import anotherpackage.util.B;
Импорт всех классов из другого пакета
Если вам нужно использовать много классов из определенного пакета, их импорт по одному приводит к большому количеству операторов импорта. Можно импортировать все классы, используя символ * вместо имени класса:
import anotherpackage.util.*;
Цикл foreach
Для обхода массива или коллекции можно применять циклы for:
Синтаксис цикла foreach:
С его помощью можно переписать приведённые примеры обхода массива и коллекции. Получится такой код:
Результат выполнения обоих вариантов будет одинаковым, но конструкция сильно упростилась — теперь не нужно следить за счётчиком итераций. Цикл foreach сам поочерёдно берёт значения из массива/коллекции и помещает их в указанную перед двоеточием переменную.
Важно, чтобы тип переменной, указанной перед двоеточием, совпадал с типом массива/коллекции
Внимание: далее материал повышенной сложности (необходимо знание интерфейсов). На самом деле параметром после двоеточия может быть любой класс, который имплементирует интерфейс Iterable (а все коллекции-наследники java.util.Collection его имплементируют) и реализует метод iterator()
Мы можем даже самостоятельно создать класс, который будет передаваться в качестве параметра
На самом деле параметром после двоеточия может быть любой класс, который имплементирует интерфейс Iterable (а все коллекции-наследники java.util.Collection его имплементируют) и реализует метод iterator(). Мы можем даже самостоятельно создать класс, который будет передаваться в качестве параметра.
Пример такого кода:
Мы объявляем класс MyIterable, в нём создаём массив, по которому будем итерироваться. Реализуем метод iterator(), возвращающий объект интерфейса Iterator, а также hasNext и next. Метод hasNext вызывается перед каждой итерацией и проверяет, есть ли следующий элемент. Если не дошли до конца массива, то hasNext вернёт true.
Когда метод hasNext возвращает true, цикл foreach вызывает метод next, который должен вернуть следующий элемент. В нашем случае он, кроме того, увеличивает на 1 текущую позицию элемента массива для последующей итерации.
Под капотом цикл foreach выглядит как простой while, но такой код будет более громоздким:
Знакомимся с дженериками
До появления дженериков программисты могли неявно предполагать, что какой-то класс, интерфейс или метод работает с элементами определённого типа.
Посмотрите на этот фрагмент кода:
Здесь предполагается, что метод printSomething работает со списком строк. Мы можем догадаться об этом, потому что в цикле все элементы приводятся (преобразуются) к классу String, а потом ещё и метод length этого класса вызывается.
Но смотрите, что сделали программисты Саша и Маша, — они поленились заглянуть внутрь метода и положили в список: один — число, а вторая — экземпляр StringBuilder.
Вот только тестировщик назначил баг не кому-то из них, а Паше, который написал метод printSomething, — потому что ошибка произошла именно во время его выполнения.
Как всё работало без дженериков. Схема: Екатерина Степанова / Skillbox
Паша быстро нашёл истинных виновников и попросил их исправить заполнение списка. Но на будущее решил подстраховаться от подобных ситуаций и переписал метод с использованием дженериков. Вот так:
Теперь, если кто-то захочет положить в массив нестроковый элемент, ошибка станет заметной сразу — ещё на этапе компиляции.
Как всё работает с дженериками. Схема: Екатерина Степанова / Skillbox
Область видимости переменных внутри класса
Внутри класса область видимости зависит от типа переменной:
- Статические переменные принадлежат самому классу. Они доступны без создания объекта, область их видимости содержит все методы класса — как статические, так и нет.
- Нестатические переменные класса принадлежат экземпляру класса (объекта). В их область видимости попадают нестатические методы класса.
Пример:
Разберём область видимости каждой из переменных:
- private static int totalFuelConsumption — статическая переменная используется в статическом методе getTotalFuelConsumption () и в нестатическом методе drive (int km). Область видимости здесь — весь класс.
- private int fuel = 86 — переменная нестатическая. А значит, она может использоваться только в конструкторах класса и его нестатических методах.
- private int consumption — переменная нестатическая. Значит, она может использоваться только в конструкторах класса и его нестатических методах.
Ключевое слово this
Если посмотреть на конструктор класса Car, то переменная, которую передают в аргументе конструктора, названа таким же именем, как и параметр класса. В этом случае области видимости переменной consumption класса и аргумента конструктора consumption пересекаются. По умолчанию используется переменная с более узкой областью видимости, то есть аргумент метода. Чтобы использовать переменную класса, необходимо на неё явно указать с помощью ключевого слова this.
Работа с классами на Python
Большая часть времени работы программиста — это работа с классами и их экземплярами. Изменим наш предыдущий класс и добавим дополнительные атрибуты, которые сможем в последующем менять при работе с экземплярами класса.
class Car():
«»»Описание автомобиля»»»
def __init__(self, brand, model, years):
«»»Инициализирует атрибуты»»»
self.brand = brand
self.model = model
self.years = years
self.mileage = 0
def get_full_name(self):
«»»Автомобиль»»»
name = «Автомобиль {self.brand} {self.model} {self.years}»
name.
def read_mileage(self):
«»»Пробег автомобиля»»»
(«Пробег автомобиля {self.mileage} км.»)
В описание автомобиля есть три атрибута(параметра) это brand, model, years. Также мы создали новый атрибут mileage (пробег) и присвоили ему начальное значение 0. Так как пробег у всех автомобилей разный, в последующем мы сможем изменять этот атрибут. Метод get_full_name будет возвращать полное описание автомобиля. Метод read_mileage будет выводить пробег автомобиля.
Создадим экземпляр с классом Car и запустим методы:
car_2 = Car(‘audi’, ‘a4’, 2019)
print(car_2.get_full_name())
car_2.read_mileage()
В результате в начале Python вызывает метот __init__() для создания нового экземпляра. Сохраняет название, модель, год выпуска и создает новый атрибут с пробегом равным 0. В итоге мы получим такой результат:
Автомобиль Audi A4 2019
Пробег автомобиля 0 км.
2.1. Прямое изменение значения атрибута
Для изменения значения атрибута можно обратиться к нему напрямую и присвоить ему новое значение. Изменим пробег автомобиля car_2:
car_2 = Car(‘audi’, ‘a4’, 2019)
print(car_2.get_full_name())
car_2.mileage = 38
car_2.read_mileage()
Мы обратились к нашему экземпляру car_2 и связанным с ним атрибутом пробега(mileage) и присвоили новое значение 38. Затем вызвали метод read_mileage() для проверки. В результате мы получим следующие данные.
Автомобиль Audi A4 2019
Пробег автомобиля 38 км.
2.2. Изменение значения атрибута с использованием метода
В Python удобнее писать методы, которые будут изменять атрибуты за вас. Для этого вы просто передаете новое значение методу, который обновит значения. Добавим в наш класс метод который будет изменять показания пробега.
class Car():
«»»Описание автомобиля»»»
def __init__(self, brand, model, years):
«»»Инициализирует атрибуты»»»
self.brand = brand
self.model = model
self.years = years
self.mileage = 0
def get_full_name(self):
«»»Автомобиль»»»
name = «Автомобиль {self.brand} {self.model} {self.years}»
name.
def read_mileage(self):
«»»Пробег автомобиля»»»
(«Пробег автомобиля {self.mileage} км.»)
def update_mileage(self, new_mileage):
«»»Устанавливает новое значение пробега»»»
self.mileage = new_mileage
car_2 = Car(‘audi’, ‘a4’, 2019)
print(car_2.get_full_name())
car_2.read_mileage()
car_2.update_mileage(17100)
car_2.read_mileage()
Вначале выведем текущие показания пробега ( car_2.read_mileage() ). Затем вызовем метод и передадим ему новое значение пробега ( car_2.update_mileage(17100) ). Этот метод устанавливает пробег 17100. Выведем текущие показания ( car_2.read_mileage() ) и у нас получается:
Автомобиль Audi A4 2019
Пробег автомобиля 0 км.
Пробег автомобиля 17100 км.
2.3. Изменение значения атрибута с приращением
Если вместо того, чтобы присвоить новое значение, требуется изменить с значение с приращением, то в этом случаем мы можем написать еще один метод, который будет просто прибавлять пробег к уже имеющемся показаниям. Для этого добавим метод add_mileage в класс :
def add_mileage(self, km):
«»»Добавляет пробег»»»
self.mileage += km
Новый метод add_mileage() получает пробег в км и добавлет его к self.mileage.
car_2.add_mileage(14687)
car_2.()
Пробег автомобиля 31787 км.
В итоге после вызова метода add_mileage() пробег автомобиля в экземпляре увеличится на 14687 км и станет равным 31787 км. Данный метод мы можем вызывать каждый раз при изменении пробега и передавать новые значение, на которое будет увеличивать основной пробег.
Способы создания экземпляра класса
Итак, под классом подразумевается описание объекта, но не сам объект. Чтобы на основе класса его создать, необходимо воспользоваться одним из доступных способов.
Способ №1
Вот строка кода, которая демонстрирует первый способ создания объекта на основе существующего класса.
Private Sub TestClass()
Dim cl As ExampleClass
Set cl = New ExampleClass End Sub
Способ №2
Dim cl As ExampleClass
Private Sub TestClass()
Set cl = New ExampleClass End Sub
В этом случае объявление класса осуществляется за пределами процедуры, поэтому работа с ним возможна со всех имеющихся внутри модуля процедур. Если же заменить оператор Dim на Public, то объект будет доступен любой процедуре, которая выполняется в рамках проекта при условии, что объявление осуществляется за пределами объектного модуля.
Способ №3
Dim cl WithEvents As ExampleClass
Private Sub TestClass()
Set cl = New ExampleClass End Sub
В данном примере объект создается с событиями, и только в связи с ними они становятся доступными. Этот метод имеет ограниченную сферу использования, его можно применять лишь в рамках объектного модуля (например, листа или книги).
Способ №4
Private Sub TestClass()
Dim cl As New ExampleClass
End Sub
Этот метод не является самым популярным, и многие про него вообще не знают. В этом случае создание объекта осуществляется через переменную cl. Этот метод несколько хуже, потому что сначала лучше выделить память под определенную переменную, и только потом создавать объект, который будет встроен в эту переменную.