Классы. Часть 1 – Введение.
Как уже не раз говорилось в Java все является объектом, за исключением примитивных типов.
Объекты создаются из классов. Поэтому класс является центральным компонентом в Java. Поскольку класс определяет форму и сущность объекта, он является той логической конструкцией, на основе которой построен весь язык. Как таковой, класс образует основу объектно-ориентированного программирования в среде Java. Любая концепция, которую нужно реализовать в Java-программе, должна быть помещена внутрь класса. В связи с тем, что класс имеет такое большое значение для Java мы рассмотрим его очень подробно.
Вероятно, наиболее важное свойство класса то, что он определяет новый тип данных. После того как он определен, этот новый тип можно применять для создания объектов данного типа.
Еще раз напомню, что класс — это шаблон объекта, а объект — это экземпляр класса. Поскольку объект является экземпляром класса, термины объект и экземпляр часто используются попеременно.
Объявление класса
Объявление класса выполняется путем указания данных, которые он содержит, и кода, воздействующего на эти данные. Хотя очень простые классы могут содержать только код или только данные, большинство классов, применяемых в реальных программах, содержит оба эти компонента.
Чтобы все было более понятно, приведу процесс объявления класса в нескольких простых слайдах…
Для объявления класса служит ключевое слово class.
Тут представлена самая простоя форма создания класса. В нем пока отсутствуют данные (поля класса – fields) и код (методы класса – methods). Тело класса, то есть поля и методы должны быть заключены между фигурными скобками.
Имя файла, в котором храниться текст класса, должно в точности совпадать с именем класса учитывая регистр букв. То есть, в нашем примере класс Example должен находится в файле Example.java.
Модификаторы (modifiers) могут отсутствовать при объявлении класса. О модификаторах мы поговорим чуть позже.
Как уже говорилось, класс может содержать данные (поля – fields) и код (методы – methods).
Рассмотрим добавление полей в класс.
Объявление полей класса похоже на объявление переменных, и если вы помните, то при создании экземпляра класса, все его поля инициализируются значениями по умолчанию. Для объектов значением по умолчанию является null.
Полей в классе может быть сколько угодно.
Ну и сразу же рассмотрим пример создания простого класса, где будут только поля данных но не будет методов. Чтобы это продемонстрировать нам понадобятся два класса, один с методом main() и другой который мы создадим сами, например класс Box.
Вот мы и создали наш первый класс Box, весь такой из себя маленький и красивый. Как видим все его поля находятся между фигурными скобками, а имя файла в котором он хранится – Box.java.
Как мы помним для создания объектов используется оператор new, который выделяет в heap’e (куче) память под объект, где так же размещаются все поля (данные) класса. При выделении памяти происходит инициализация полей.
Если нет конструкторов в классе (о которых мы поговорим чуть позже) которые инициализируют эти поля какими-то значениями, то поля инициализируются значениями по умолчанию. Это делает конструктор по умолчанию.
Еще раз акцентирую внимание что поля класса создаются в куче (heap’e), поэтому, даже если вы их не проинициализировали какими-либо значениями, их сразу же можно использовать в коде программы, так как дефолтный конструктор присваивает им значения по умолчанию при создании объекта. Переменные же в методах класса создаются в стеке, поэтому их необходимо инициализировать значениями перед использованием, так как там могут быть неизвестно какие значения.
Как уже было сказано, класс определяет новый тип данных. В данном случае новый тип данных назван Box. Это имя будет использоваться для объявления объектов типа Box. Важно понимать, что объявление class создает только шаблон, но не действительный объект. Таким образом, приведенный код не приводит к появлению никаких объектов типа Box.
Как я уже упоминал, чтобы создать объект типа Box нужно использовать оператор new. В данном примере слева, в строке 7 мы как раз и создаем новый объект Box и размещаем ссылку на него в переменную box1. Далее мы можем использовать этот объект и обратиться к его полям через ссылку на него – box1.
Для обращения к полям объекта используется точка (.) после имени объекта (ссылки на объект), например – box1.widht.
Вывод этой программы вы видите слева. Все поля получили значения по умолчанию.
При объявлении полей класса, их, так же как и обычные переменные, можно сразу инициализировать значениями, как показано на примере слева. Чтобы вывести поле label для объекта box1, добавим следующую строку в код класса Classes001:
println
(«box1.label = » + box1.label);Вывод измененного класса Classes001 представлен справа.
К полям экземпляра, можно не только обращаться для чтения их значений, но и для изменения, хотя обычно так делать не рекомендуют. Поскольку обычно поля лучше менять через методы класса и именно так и рекомендуется делать – это и есть инкапсуляция, ну в самом простом ее понимании. На примере слева, в строке 14 мы изменили значение поля label для объекта box1.
Вывод у программы теперь следующий:
Как вы уже должны были понять, наша программа состоит из двух классов: Classes001 и Box.
Обычно программы состоят не из одного или двух классов, а из сотен или даже тысяч, поэтому классы принято объединять в пакеты. Что такое пакеты мы рассмотрим чуть позже, а пока просто о них упомянули.
Каждый объект содержит собственные копии полей экземпляра. Это означает, что при наличии двух объектов Box каждый из них будет содержать собственные копии полей depth, width, height и label. Важно понимать, что изменения переменных экземпляра одного объекта не влияют на переменные экземпляра другого.
На примере слева, в строке 17 мы создали еще одни экземпляр класса Box с именем box2 и в строке 18 присвоили полю label значение b0x.
Вывод у программы теперь такой:
Поля box2.label и box1.label имеют разные значения.
Чтобы лучше понять чем отличаются классы от объектов и как экземпляры классов (объекты) создаются в памяти, очень рекомендую посмотреть следующее видео:
Создание экземпляра класса (объекта)
Теперь зададим значения высоты, ширины и глубины для box2 и box1 и посчитаем объем каждого из ящиков. Данный пример я привожу для того, чтобы в следующем разделе, где будем рассматривать объявление методов, сразу же стала понята их полезность и назначение.
Теперь вывод у программы следующий:
Как видно из текста класса Classes001, в строка 25 и 30, для вычисления объема ящика, нам каждый раз необходимо обращаться ко всем трем полям экземпляра класса, что не очень то удобно, если таких ящиков будут тысячи.
Поэтому лучше использовать методы в классе, которые будут обрабатывать данные и возвращать результат, или же не возвращать, а сразу его выводить на консоль.
Так же для вывода значений высоты, ширины и глубины, нам опять каждый раз надо обращаться к каждому из полей экземпляра класса Box. А что если создать метод, который будет выводить их все сразу? Ведь это куда удобней!
Так же можно создать метод, который будет задавать одинаковые величины для глубины, ширины и высоты. Хотя это конечно лучше реализовать через конструктор класса, но это уже тема которая пойдет после темы определения методов.
И так! Погнали дальше!
Объявление методов в классе, обычно делают после объявления переменных. Например, объявление метода может быть таким:
Модификаторы так же как для классов и полей могут быть, а могут и не присутствовать.
Кроме того, метод может возвращать какое-либо значение, как в нашем примере возвращает значение типа int через оператор return, а может не возвращать, и тогда тип возвращаемого значения указывается как void.
Вообще тема методов в Java очень обширна, так же как и тема классов. По существу все оставшееся время мы будем говорить только об этом – классах и их полях и методах, так как, еще раз напомню что классы состоят из полей и методов.
Однако чтобы можно было приступить к добавлению методов к своим классам, необходимо ознакомиться с рядом их основных характеристик. Общая форма объявления метода выглядит следующим образом:
Здесь тип указывает тип данных, возвращаемых методом. Он может быть любым допустимым типом, в том числе типом класса, созданным программистом. Если метод не возвращает значение, типом его возвращаемого значения должен быть void. Имя служит для указания имени метода. Оно может быть любым допустимым идентификатором, кроме тех, которые уже используются другими элементами в текущей области определения. Список_параметров — последовательность пар “тип-идентификатор”, разделенных запятыми. По сути, параметры — это переменные, которые принимают значения аргументов, переданных методу во время его вызова. Если метод не имеет параметров, список параметров будет пустым.
Методы, тип возвращаемого значения которых отличается от void, возвращают значение вызывающей процедуре с помощью следующей формы оператора return:
Здесь значение — это возвращаемое значение. Но оператор return может использоваться и без возвращаемого значения если тип метода void, в этом случае оператор return просто прерывает выполнение метода.
Ну и теперь попрактикуемся. Создадим методы в классе Box, что на примере слева.
Метод printVolume() не возвращает ни какого значения, поэтому возвращаемый тип указан как void. Данный метод просто выводит на консоль значение объема.
Метод printSizes() тоже не возвращает ни какого значения, а просто выводит на консоль значения полей.
Метод getVolume() возвращает значение типа double, которое вычисляется перемножением полей размеров и возвращается оператором return.
Метод setSameSize(double size), хотя не возвращает ни какого значения, но напротив устанавливает значения полей равными переданному аргументу в переменной size, имеющей тип double.
В принципе, по аналогии можно было создать и метод, который задает разные значения для каждого из полей размеров класса Box, но вообще такие вещи лучше делать в конструкторах, которые мы рассмотрим чуть позже.
Обращение к методам объекта делается так же, как и обращение к полям объекта, то есть через точку (.), что мы и увидим в классе Classes001.
Стоит взять на заметку, что мы не использовали ни каких модификаторов, ни для полей, ни для класса, ни для методов.
Теперь в классе Classes001 мы можем внести изменения, показанные на примере слева.
В строках 10-12 мы задали размеры для box1 так же как и прежде.
Но в строке 14 для объекта box2 мы уже использовали метод setSameSize() с параметром равным 15, который мы передали в метод.
Метод setSameSize() принял этот параметр и установил значения всех полей размеров равным этому значению (см. код класса Box выше).
Затем в строках 17 и 23 мы использовали метод printSizes() для каждого из объектов, который вывел на консоль их размеры.
В строках 18 и 24 мы использовали метод getVolume() для получения значения объема объектов.
На заметку. С этого момента, то есть с момента начала данного урока, я буду многократно изменять тексты классов. На Bitbucket в исходниках вы будете видеть только последнюю версию изменений. И кстати там же можете посмотреть и всю историю изменений каждого класса.
Вывод у нашей программы теперь такой:
Ну что же, нам пора продвинуться и создать метод, который будет задавать сразу все три размера. На скрине слева я привел только этот метод, без всего остального кода класса Box, так как он остался неизменным. Мы просто добавили этот метод.
Обратите внимание, что в метод передаются три параметра.
Вызов этого метода для объекта box1 осуществляется следующим образом:
box1
. setSizes(10, 20, 30);Вывод программы не изменился, зато код класса Calsses001 сократился на две строки, так как теперь все три значения мы задаем в одной строке с помощью вызова метода setSizes(). На Bitbucket можно посмотреть как теперь выглядят классы Classess001 и Box.
Еще раз прошу обратить внимание на то каким образом метод определен, как заданы параметры передающиеся в него и как называются эти переменные (w, h и d). Это нам скоро понадобится.
А пока переходим к конструкторам класса. Конструкторы, это по существу, те же самые методы, но которые автоматически вызываются при создании объекта, если они были объявлены в классе и используются при создании объекта.
Конструктор инициализирует объект непосредственно во время создания. Его имя совпадает с именем класса, в котором он находится, а синтаксис аналогичен синтаксису метода. Как только он определен, конструктор автоматически вызывается непосредственно после создания объекта, перед завершением выполнения операции new.
Конструкторы выглядят несколько непривычно, поскольку не имеют ни возвращаемого типа, ни даже типа void. Это обусловлено тем, что неявно заданный возвращаемый тип конструктора класса — это тип самого класса. Именно конструктор инициализирует внутреннее состояние объекта так, чтобы код, создающий экземпляр, с самого начала содержал полностью инициализированный, пригодный к использованию объект.
Если не объявлен ни один конструктор, автоматически создается конструктор по умолчанию (без параметров).
Объявление конструктора может выглядеть так:
Модификаторы, как и в предыдущих случаях, могут отсутствовать. И как видите имя конструктора совпадает с именем класса. Так же в примере использовано ключевое слово this, но мы рассмотрим его чуть позже, так как это отдельная тема для большого разговора. А пока рассмотрим более простой пример объявления конструктора.
Добавим в наш класс Box, конструктор, который будет инициализировать все размеры.
Пример, как это может быть сделано, приведен слева. Объявление конструктора подсвечено желтым.
Не находите, что он очень похож на метод setSizes()?
Отличия в том, что не указан тип возвращаемого значения, а так же то что имя совпадает с именем класса.
Инициализация происходит путем присвоения значений переданных аргументов полям класса.
Теперь выполнить создание и инициализацию объекта типа Box можно следующим образом:
Box
box1 = new Box(10, 20, 30);Box box2 = new Box(15, 15, 15);
Вывод данной программы не поменялся, но зато класс Classes001 сократился еще на несколько строк и теперь выглядит так:
Теперь размеры для объектов задаются прямо при их создании.
Чтобы было лучшее понимание происходящего, то лучше посмотреть текущее состояние кода классов Classes001 и Box.
Полный код я тут не привел, чтобы не перегружать статью.
На данный момент пока все достаточно просто и понятно, но все же, в такой реализации конструктора есть подвох, так как мы теперь не можем использовать конструктор по умолчанию, то есть не можем использовать следующий код:
Box
box1 = new Box();Если мы попробуем сделать так, то получим ошибку что конструктор Box() не определен. Поэтому, если мы добавляем свои конструкторы в класс, то, если хотим использовать конструктор по-умолчанию (без аргументов), то должны его тоже объявить.
Вот мы потихоньку и подошли к понятию перегрузки методов и конструкторов, а так же для чего и как это используется. Этот момент мы тоже рассмотрим чуть позже, так как для порядка, нам надо рассмотреть еще одну тему – это деструкторы. Ну как бы если есть конструкторы, то должны быть и деструкторы, а вот их и нет 🙂 в Java.
Поскольку распределение памяти для объектов осуществляется динамически посредством операции new, может возникнуть вопрос, как уничтожаются такие объекты и как их память освобождается для последующего распределения. В некоторых языках, подобных C++, динамически распределенные объекты нужно освобождать вручную. В Java применяется другой подход. Освобождение памяти выполняется автоматически. Используемая для выполнения этой задачи технология называется сборкой мусора. Процесс проходит следующим образом: при отсутствии какихлибо ссылок на объект программа заключает, что этот объект больше не нужен, и занимаемую объектом память можно освободить. В Java не нужно явно уничтожать объекты, как это делается в C++. Во время выполнения программы сборка мусора выполняется только изредка (если вообще выполняется). Она не будет выполняться лишь потому, что один или несколько объектов существуют и больше не используются. Более того, в раз личных реализациях системы времени выполнения Java могут применяться различные подходы к сборке мусора, но в большинстве случаев при написании программ об этом можно не беспокоиться.
Метод finalize()
Иногда при уничтожении объект должен будет выполнять какое-либо действие. Например, если объект содержит какой-то ресурс, отличный от ресурса Java (вроде файлового дескриптора или шрифта), может требоваться гарантия освобождения этих ресурсов перед уничтожением объекта. Для подобных ситуаций Java предоставляет механизм, называемый финализацией. Используя финализацию, можно определить конкретные действия, которые будут выполняться непосредственно перед удалением объекта сборщиком мусора.
Чтобы добавить в класс средство выполнения финализации, достаточно определить метод finalize(). Среда времени выполнения Java вызывает этот метод непосредственно перед удалением объекта данного класса. Внутри метода finalize() нужно указать те действия, которые должны быть выполнены перед уничтожением объекта. Сборщик мусора запускается периодически, проверяя наличие объектов, на которые отсутствуют ссылки как со стороны какого-либо текущего состояния, так и косвенные ссылки через другие ссылочные объекты. Непосредственно перед освобождением ресурсов среда времени выполнения Java вызывает метод finalize() по отношению к объекту.
Важно понимать, что метод finalize() вызывается только непосредственно перед сборкой мусора. Например, он не вызывается при выходе объекта за рамки области определения. Это означает, что неизвестно, когда будет — и, даже будет ли вообще — выполняться метод finalize(). Поэтому программа должна предоставлять другие средства освобождения используемых объектом системных ресурсов и тому подобного. Нормальная работа программы не должна зависеть от метода finalize().
Короче! Хоть метод finalize() и существует, пользоваться им категорически не рекомендуется!
Все! На этом введение в классы закончено! 🙂 Но это только начало 🙂 Самое начало 🙂
Что такое объект, экземпляр класса в Java
Классы и объекты в Java — это основа основ этого языка. Вы, наверное, слышали, что Java — это объектно-ориентированный язык программирования. Такую классификацию он получил не зря, потому что любую программу, написанную на Java, можно разделить на объекты, которые взаимодействуют между собой.
Классы и объекты в Java
Классы и объекты в Java очень тесно связаны между собой. Каждый класс — это шаблон описания какого-либо объекта. А каждый объект — это экземпляр какого-либо класса. Понятно, что пока ничего не понятно, однако можно провести аналогию из жизни.
Когда мы произносим слово «человек», то каждый из нас может его описать какими-то свойствами, например:
- у него есть руки,
- у него есть ноги;
- у него есть туловище, к которому крепятся руки, ноги и голова;
- на голове есть волосы;
- он стоит на двух ногах;
и др. Таких «описаний человека» может быть очень много. По сути, это описание человека может выступать в роли класса «Человек» в Java. Потому что данное описание выступает, как «шаблон человека» без описания какого-то конкретного человека. Однако все мы знаем, что люди бывают разные. Например, у людей может отличаться:
- пол: мужчины и женщины;
- цвет и длина волос;
- цвет кожи;
- цвет и разрез глаз;
- рост;
- вес;
- и много-много других признаков.
Когда мы попытаемся точно описать какого-то человека по его отличительным признакам, то фактически получим объект класса «Человек», который является экземпляром этого класса.
В Java класс «Человек» мог бы выглядеть вот так:
То есть мы в Java создали класс «Human», а внутри «фигурных скобок» можем поместить поля и методы, которые будут описывать общие характеристики нашего класса.
Все характеристики объектов в Java можно разделить на две большие категории:
Те, которые описывают состояние объекта. Например, у нашего класса «Human» это могут быть: рост, вес, возраст, имя, цвет волос, цвет глаз и др. Описание состояния объекта внутри класса несут в себе поля и переменные класса.
Те, которые описывают поведение объекта. Например, у нашего класса «Human» это могут быть следующие действия человека: спит, стоит, кричит, лежит, говорит, читает, пишет, бежит и др. Описание поведения объекта несут в себе методы класса.
Давайте немного усложним наш класс «Human», описывающий человека. В результате в коде мы получим что-то подобное:
Чуть выше мы определили класс «Human», в котором определили:
Немного усложненный класс «Human» у нас есть, теперь давайте на его основе создадим небольшую программу. Например:
Нетрудно заметить, что в коде чуть выше у нас появился еще один класс «PublicPerson», то есть у нас уже есть два класса и только один класс у нас доступен с модификатором «public». Также у нас появилась новая переменная «dormidont», представляющая класс «Human». Однако пока эта переменная пустая и имеет значение «null», потому что она не указывает ни на один объект. Поэтому пока мы ее не можем использовать до тех пор, пока не создадим конкретный объект класса «Human».
Объекты в Java: конструкторы
Вот мы добрались еще до одного важного определения языка программирования — до конструкторов. Конструктор в Java — это специальный метод, который помогает создавать новый объект определенного класса.
Обычно в классе определяют конструктор объекта, если этого не сделать, тогда конструктор создается автоматически, но без параметров.
Например, в нашем примере, класс «Human» не имел ни одного конструктора, поэтому конструктор создается автоматически на основе параметров самого класса. Теперь нам нужно воспользоваться этим конструктором, чтобы создать объект «Human».
Вот что у нас получится в коде:
Для того чтобы создать объект «Human», мы использовали выражение «new Human()», где оператор «new» выделит память для создания нового объекта «Human». После этого происходит вызов конструктора по умолчанию без всяких параметров. В результате этого выделится немного памяти для сохранения информации об объекте «Human», а переменной «dormidont» мы задали ссылку на этот объект.
После того как мы создали объект, есть возможность обратиться к переменным объекта «Human», используя переменную «dormidont», для того чтобы получить или задать значения объекту, например, как это сделали мы:
dormidont.firstName = «Dormidont»;
Заключение
Классы и объекты в Java — это основные инструменты этого языка программирования. Описать все возможные манипуляции с этими инструментами в рамках одной статьи невозможно, потому что информации очень много. По сути, если вы научитесь правильно использовать классы и объекты в Java, тогда вы научитесь программировать на этом языке.
Наша сегодняшняя статья — это начало начал и попытка приоткрыть вам занавес в мир программирования на языке Java.
Что такое класс в java
Классы и объекты в Java
Класс в Java — это шаблон для создания объекта, а объект — это экземпляр класса. Класс определяет структуру и поведение, которые будут совместно использоваться набором объектов. Класс содержит переменные и методы, которые называются элементами класса, членами класса. Он составляет основу инкапсуляции в Java. Каждый объект данного класса содержит структуру и поведение, которые определены классом. Иногда объекты называют экземплярами класса.
Методы используются для описания того, что объект класса умеет делать или что можно с ним сделать. Переменные — для описания свойств или характеристик объекта.
Рассмотрим картинку, приведенную ниже. Объявлен класс Student , у которого есть переменные name и rollNo , а также методы setName() и setRollNo() для установки этих значений. На основе этого класса создано несколько объектов: Jenna, John, Maria, James. У каждого объекта, то есть студента, есть name и rollNo , но они разные.
2. Как создать класс в Java
Рассмотрим как создать класс в языке Java. Упрощенная общая форма определения класса:
После ключевого слова class пишется имя класса. В теле класса объявляются переменные и методы класса. Их может быть сколько угодно.
Опишем класс для объекта Box (коробка). У коробки есть три главные характеристики: ширина, высота и глубина, описанные с помощью переменных:
3. Создание объекта в Java
Объявление класса создает только шаблон, но не конкретный объект. Чтобы создать объект класса Вох в Java, нужно воспользоваться оператором наподобие следующего:
При создании экземпляра класса, создается объект, который содержит собственную копию каждой переменной экземпляра, определенной в данном классе.
Создание объектов класса представляет собой двух этапный процесс:
- Объявление переменной типа класса. Эта переменная не определяет объект. Она является лишь переменной, которая может ссылаться на объект:
- Создание объекта. С помощью оператора new динамически (то есть во время выполнения) резервируется память для объекта и возвращается ссылка на него:
После объявления объекта класса Box , всем переменным класса присваивается значение по умолчанию для заданного типа. Для того чтобы обратиться к переменной класса и изменить ее или получить значение, используется имя переменной объекта:
В следующем примере объявляется два объекта класса Box и каждому устанавливаются свои значения. Изменения в переменных экземпляре одного объекта не влияют на переменные экземпляра другого.
4. Присваивание переменным ссылок на объекты
Возможна ситуация, когда две переменные указывают на один и тот же объект в памяти:
Рассмотрим как это происходит на следующем примере.
При объявлении переменной b1 создается новый объект в памяти. При объявлении переменной b2 , вместо создания нового объекта, переменной присваивается ссылка на объект b1 . Далее объекту, на который указывает переменная b1
Но обе переменные указывают на один и тот же объект, поэтому результат выполнения этой программы будет:
5. Добавляем методы в класс
Кроме переменных класс может содержать методы. В следующем примере в класс Box добавляется два метода: getVolume() — для вычисления объема коробки и setDim() — для установки размера коробки. Обратите внимание, что теперь мы объявляем методы нестатические (без ключевого слова static). В обоих методах мы имеем доступ к переменным класса.
В следующей программе создаются два объекта класса Box и вместо инициализации каждой переменной класса, как мы делали ранее, вызывается метод setDim() , куда передаются необходимые значения для ширины, высоты и глубины. Таким образом программа становится более компактной. Нестатический метод класса всегда вызывается для какого-то объекта. Аналогично, для подсчета объема коробки вызываем метод getVolume() для каждого объекта отдельно:
5.
Java — Классы и объектыJava является объектно-ориентированным языком программирования. Как язык, который имеет функцию объектно-ориентирования, он поддерживает следующие основные понятия:
- полиморфизм;
- наследование;
- инкапсуляция;
- абстракция;
- классы;
- объекты;
- экземпляр;
- метод;
- парсинг.
В этом уроке мы рассмотрим объекты и классы в Java, их концепции.
Класс может быть определен как шаблон (обозначен зеленым цветом), который описывает поведение объекта, который в свою очередь имеет состояние и поведение. Он является экземпляром класса. Например: собака может иметь состояние — цвет, имя, а также и поведение — кивать, лаять, есть.
Содержание
Объекты в Java
Давайте теперь посмотрим вглубь, что является объектами. Если мы рассмотрим реальный мир, то найдём много предметов вокруг нас, автомобили, собаки, люди, и т.д. Все они имеют состояние и образ жизни.
Если учесть, собаку, то ее состояние — имя, порода, цвет, а образ жизни — лай, виляние хвостом, бег.
Если сравнить программный объект в Java с предметов из реального мира, то они имеют очень схожие характеристики, у них также есть состояние и поведение. Программное состояние хранят в полях, а поведение отображается через методы.
Таким образом, в разработке программного обеспечения, методы работают на внутреннем состоянии объекта, а связи с другими, осуществляется с помощью методов.
Классы в Java
Класс, из которого создаются отдельные объекты, обозначен зеленым цветом.
Пример создания класса в Java, приводится ниже:
Класс может содержать любой из следующих видов переменных:
- Локальные переменные, определенные внутри методов, конструкторов или блоков. Они будут объявлены и инициализированы в методе, и будут уничтожены, когда метод завершится.
- Переменные экземпляра являются переменными в пределах класса, но и снаружи любого метода. Они инициализируются при загрузке. Переменные экземпляра могут быть доступны из внутри любого метода, конструктора или блоков этого конкретного класса.
- Переменные класса или статические переменные класса в Java объявляются в классе вне любого метода с помощью статического ключевого слова.
В Java классы могут иметь любое количество методов для доступа к значению различных видов методов. В приведенном выше примере, barking(), hungry() и sleeping() являются методами.
Далее упомянуты некоторые из важных тем, которые должны быть рассмотрены для понимания значения классов и объектов в языке программирования.
Конструктор класса
При обсуждении вопроса класса, одной из наиболее важных подтем в Java является конструктор. Каждый класс имеет конструктор. Если мы не напишем его или, например, забудем, компилятор создаст его по умолчанию для этого класса.
Каждый раз, когда в Java создается новый объект, будет вызываться по меньшей мере один конструктор. Главное правило является то, что они должны иметь то же имя, что и класс, который может иметь более одного конструктора.
Пример конструктора приведен ниже:
Примечание: в следующих разделах мы будем более подробно обсуждать, если у нас будет два разных типа конструкторов.
Создание объекта
Варианты как создать объект в классе следующие:
- Объявление: объявление переменной с именем переменной с типом объекта.
- Инстанцирование: используется «новое» ключевое слово.
- Инициализация: «новое» ключевое слово сопровождается вызовом конструктора. Этот вызов инициализирует новый объект.
Пример приводится ниже:
Если Вы скомпилируете и запустите выше программу, то она выдаст следующий результат:
Доступ к переменным экземпляра и методам в Java
Переменные и методы доступны через созданные объекты. Чтобы получить доступ к переменной экземпляра, полный путь должен выглядеть следующим образом::
Пример
Этот пример объясняет, как получить доступ к переменные экземпляра и методам класса в Java:
Если Вы скомпилируете и запустите выше программу, то она выдаст следующий результат:
Правила объявления классов, операторов импорта и пакетов в исходном файле
В последней части этого раздела давайте рассмотрим правила декларации исходного файла. Эти правила в Java имеют важное значение при объявлении классов, операторов импорта и операторов пакета в исходном файле.
- В исходном файле может быть только один публичный класс (public class).
- Исходный файл может иметь несколько «непубличных» классов.
- Название публичного класса должно совпадать с именем исходного файла, который должен иметь расширение .java в конце. Например: имя класса public class Employee<>, то исходный файл должен быть Employee.java.
- Если класс определен внутри пакета, то оператор пакет должно быть первым оператором в исходном файле.
- Если присутствуют операторы импорта, то они должны быть написаны между операторами пакета и объявлением класса. Если нет никаких операторов пакета, то оператор импорта должен быть первой строкой в исходном файле.
- Операторы импорта и пакета будут одинаково выполняться для всех классов, присутствующих в исходном файле. В Java не представляется возможным объявить различные операторы импорта и/или пакета к различным классам в исходном файле.
Классы имеют несколько уровней доступа и существуют различные типы классов: абстрактные классы (abstract class), конечные классы (final class) и т.д. Обо всем этом обсудим в уроке модификаторы доступа.
Помимо указанных выше типов классов, Java также имеет некоторые специальные классы, называемые внутренние (Inner class) и анонимные классы (Anonymous class).
Java пакет (package)
При разработке приложений сотни классов и интерфейсов будет написано, поэтому категоризации этих классов является обязательным, а также это делает жизнь намного проще.
Операторы импорта (import)
Если задать полное имя, которое включает в себя пакет и имя класса, то компилятор может легко найти исходный код или классы. В Java импорт это способ задать правильное место для компилятора, чтобы найти конкретный класс.
Например, следующая строка будет просить компилятор загрузить все классы, доступные в каталоге «java_installation/java/io»:
Простой пример по выше описанному
Для нашего обучения создадим два класса. Это будут классы Employee и EmployeeTest.
Для начала откройте блокнот и добавьте следующий код. Помните, что этот класс Employee является открытым или публичным классом. Теперь сохраните исходный файл с именем Employee.java.
Класс Employee имеет четыре переменных экземпляра name, age, designation и salary. Он имеет один явно определенный конструктор, который принимает параметр.
Как упоминалось выше, обработка начинается с основного метода. Поэтому для нас, чтобы запустить класс Employee, должен быть главный метод и созданные объекты. Создадим отдельный класс для этих задач.
Ниже приводится класс EmployeeTest, в котором создаются два экземпляра класса Employee и вызывают методы для каждого объекта, для присвоения значений каждой переменной.
Сохраните следующий код в файл «EmployeeTest.java»:
Теперь, скомпилировав оба класса, запустим EmployeeTest и получим следующий результат:
В следующем уроке обсудим основные типы данных, и как они могут быть использованы при разработке java-приложений.
Класс в Java
Класс в Java — это описание или прототип объекта. Класс содержит свойства и методы объекта. В свойствах хранится состояние объекта, а методы описывают поведение объекта.
Возьмем простую строку из программы на Java:
Здесь создается объект animal класса Animal с помощью оператора new.
Давайте рассмотрим подробнее, из чего состоит класс.
Декларация класса
В простейшем случае класс Animal декларируется так:
Двойным слэшем обозначается комментарий. Между фигурными скобками находится тело класса — поля и методы.
Обычно класс декларируется в отдельном файле, и имя файла совпадает с именем класса и имеет расширение java. Например, класс Animal хранится в файле Animal.java.
Скомпилируется и несколько классов в файле, но не любых — два публичных класса (с модификатором public) не могут находиться в одном файле. Также имя публичного класса обязано совпадать с именем файла.
Модификаторы доступа рассмотрим позднее, но в данном случае public означает, что к классу Animal доступ возможен из любого места программы, в том числе из другого пакета.
Бывают еще внутренние классы — класс внутри класса — их рассмотрим позднее.
Также класс может наследоваться от другого класса или реализовывать интерфейс.
Тело класса
Тело класса — это то, что находится между фигурными скобками. Оно состоит из полей и методов. Также в нем может быть конструктор — один или несколько.
В вышеприведенном классе Animal есть поле size, метод getSize() и два конструктора Animal() и Animal (int size).
Конструктор
Конструктор используется для создания объектов. То есть он выполняется при создании объекта с помощью оператора new.
Он отличается от метода тем, что его имя совпадает с именем класса и он не возвращает никакого типа. Тут у нас два конструктора, поэтому мы можем создавать объекты типа Animal двумя способами:
или с аргументом:
Учтите, что если в теле класса отсутствуют любые конструкторы, то компилятор сам подставляет конструктор без аргументов. То есть первый конструктор можно и не писать, если нужен только он. Но если уже написан конструктор с аргументами, и при этом пустой конструктор без аргументов тоже нужен, то его надо прописать, т.к. компилятор не добавит автоматически конструктор без аргументов при наличии хоть какого-то конструктора в классе.
Наследование
Создадим класс Cat, который наследуется от Animal:
Смысл наследования в том, чтобы заново не создавать повторяющиеся поля и методы. Ясно, что кошка — это животное, у нее могут быть такие присущие всем животным свойства, как вес, размер. Допустим, у нас будет еще рыба, и она тоже имеет размер и вес. Чтобы не создавать эти свойства в каждом классе, мы их положим в Animal, а в Cat (и Fish) будем создавать только специфические для Cat (и Fish) свойства.
Animal считается суперклассом Cat. Вообще в Java у любого класса уже есть суперкласс, даже если он явно не прописывается, как для Animal. Это суперкласс Object, Animal под капотом наследуется от Object. Компилятор не требует прописывать это наследование в коде, но тем не менее любой объект уже обладает некоторыми свойствами и методами Object (носящими технический характер — например, методом для сравнения объектов), но об этом поговорим позднее.
Реализация интерфейса
Интерфейс — набор абстрактных методов. Реализуются эти методы в классах. (В Java 8 появились default-методы, которые реализуются прямо в интерфейсах, но обычно они не нужны).
Интерфейс — это контракт, который реализуется каждым классом по-своему. Рассмотрим пример. Создадим интерфейс Movable:
В нем задекларирован метод move(), но не реализован. Предполагается, что любой класс, реализующий интерфейс Movable, по-своему реализует этот метод. Вот так его реализует Cat:
А вот так его реализует Fish:
Как показано выше, implements Movable означает, что класс реализует интерфейс Movable, то есть реализует все абстрактные методы, прописанные в интерфейсе.
Если класс реализует несколько интерфейсов, то они пречисляются через запятую.
Абстрактный класс
Есть еще один вариант задекларировать абстрактный метод move() — можно сделать это не в интерфейсе Movable, а в абстрактном классе — точнее, суперклассе Animal:
Здесь мы помечаем метод move() ключевым словом abstract и не реализуем его. Также мы помечаем словом abstract сам класс Animal — любой класс, который содержит хотя бы один абстрактный метод, должен быть абстрактным.
Итоги
- Декларация класса выглядит так:
В квадратных скобках выше перечислены необязательные элементы декларации.
Некоторые классы и методы Java
Некоторые классы и методы JavaК документам
- Метод — это процедура (также называемая функцией или подпрограмма), которую можно вызвать для выполнения вычисления или другое действие.
- Метод можно вызвать из объекта ( метод экземпляра ) или из класса ( статический метод ).
- Вот некоторые классы в библиотеке классов Java с их часто используемыми методами.
- Окончательная ссылка для классов Документация по библиотеке классов Java 11.
Класс строки
Объект String состоит из последовательности символов Юникода типа char. Список символьных символов включает каждый символ на стандартной клавиатуре, но также включает в себя символы всех широко используемых алфавитов, современных и древних. Поскольку максимальный размер символьной переменной составляет 2 байта, Java не может обрабатывать более новые 4-байтовые символы Unicode. Важное свойство объектов String что их неизменяемый (нельзя изменить).
Конструкторы
- new String( ) Создать объект нулевой строки «».
- new String(existingStr) Создать копию существующей строки.
- new String(char[ ] charArray) Создать строку из массива символов.
- new String(char[ ] charArray, int offset, int count) Создать строку из массива символов, начиная при смещении индекса и подсчете длины.
- new String(StringBuilder sbObj) Создать строку из объекта StringBuilder.
Методы
- str.charAt(pos) Возвращает символ в позиции pos в строке str.
- str.compareTo(otherStr) Возвращает положительное целое число, если str предшествует otherStr в порядке словаря, возвращает отрицательное целое число, если otherStr идет после otherStr, возвращает 0, если str и otherStr состоят из одних и тех же символов. Примечание. Операторы >, < и == нельзя использовать для объектов String.
- str.compareToIgnoreCase(otherStr) То же, что и str.compare to, за исключением того, что регистр букв в строках игнорируется.
- str.endsWith(suffix) Возвращает значение true, если строка заканчивается суффиксом.
- String.format(formatString, arg0, arg1, arg2) Отформатируйте аргументы arg0, arg1 и arg2 в соответствии со строкой формата.
- str.equals(otherStr) Возвращает значение true, если строка содержит те же буквы, что и otherStr.
- str.indexOf(substring) Если в строке найдена подстрока, вернуть начальный индекс самой левой подстроки. Если подстрока не найдена в ул, вернуть -1.
- str.lastIndexOf(substring) Если в строке найдена подстрока, вернуть начальный индекс самой правой подстроки. Если подстрока не найдена в ул, вернуть -1.
- str.length() Возвращает количество символов в строке str.
- ул.заменить(oldChar, newChar) Вернуть строку, в которой каждый экземпляр oldChar заменен на newChar в строке str.
- str.split(delim) Возвращает массив полей String, полученный из str, используя разделитель-разделитель.
- str.startsWith(suffix) Возвращает значение true, если строка начинается с префикса.
- str.toString() Возвращает строку str в виде строки (что ничего не делает, поскольку строка уже является строкой). Для совместимости с другими классами.
- str.toLowerCase() Возвращает строку str, где буквы преобразуются в нижний регистр.
- str.toUpperCase() Возвращает строку str, где буквы преобразуются в верхний регистр.
- str.trim() Возвращает строку str с удаленными начальными и конечными пробелами.
- String.valueOf(obj) Вернуть объект obj в виде строки.
Класс StringBuilder
Поскольку объекты String являются неизменяемыми, в них часто вносятся изменения. может занять много времени, поскольку каждый раз необходимо создавать совершенно новый объект String. время внесения изменений. Более быстрой альтернативой является использование mutable Объекты StringBuilder для управления строками, которые часто меняются.
Конструкторы
- new StringBuilder() Создать объект StringBuilder без символов, но с емкостью 16.
- new StringBuilder(capacity) Создайте объект StringBuilder без символов, но с емкостью 16.
- новый StringBuilder(str) Создайте объект StringBuilder с символами из строки str. Начальная емкость 16 + длина ул.( ).
Методы
- strb.append(obj) Вернуть строковое представление объекта obj в конец символов StringBuilder.
- strb.capacity( ) Возвращает текущую емкость объекта StringBuilder strb.
- strb.ensureCapacity(minCap) Если текущая емкость объекта StringBuilder меньше, чем minCap, установите для текущей емкости значение minCap.
- strb.insert(pos, obj) Вставить строковое представление obj непосредственно перед символом в позиции индекса. Используйте append, чтобы добавить объект в конце.
- strb.setCharAt(pos, chr) Изменить символ в индексе pos на chr.
- Строковые методы charAt, индекс, последний индекс, длина, заменить, substring и toString также работают для Объекты StringBuilder.
Целочисленный класс
Класс Integer — это класс-оболочка, содержащий значения int. Обертка классы также существуют для каждого из других примитивных типов: Byte, Short, Long, Плавающие, двойные, символьные и логические значения.
Конструкторы
- new Integer(int value) Создайте объект Integer, содержащий указанное значение int.
- new Integer(String value) Создайте объект Integer, содержащий заданное строковое значение. Исключение NumberFormatException будет подниматься, если строковое значение не может быть преобразовано в целое число.
Методы
- Integer.bitCount(intValue) Возвращает количество битов в двоичном представлении intValue.
- intObj.byteValue(), intObj.shortValue(), intObj.intValue(), intObj.longValue(), intObj.floatValue(), intObj.doubleValue() Возвращает intObj как указанный тип данных.
- intObj.toBinaryString(), intObj.toHexString(), intObj.toOctalString(), intObj.toString() Возвратите intObj как строку указанного основания. toString возвращает intObj как десятичную строку.
- Integer.parseInt(intStr) Преобразование строки в целое число.
- Integer. MAX_VALUE Максимальное значение типа данных int, что равно 2 31 — 1 = 2 147 483 647.
- Integer.MIN_VALUE Максимальное значение типа данных int, что равно -2 31 = -2 147 483 648.
- Целое.РАЗМЕР Количество битов, необходимых для хранения значения int, равное 32.
Класс объекта
Класс объекта является родительским классом для всех классов Java. Методы объекта могут быть переопределены в производных классах.
Конструктор объекта
Методы объекта
- obj.Clone() Создает и возвращает копию obj.
- obj.equals(otherObj) Возвращает true, если obj равен otherObj, иначе false.
- obj.finalize() Вызывается, когда сборщик мусора убирает объект, ставший мусором.
- obj.getClass() Возвращает объект типа Class, содержащий информацию о типе obj.
- obj.getHashCode() Возвращает хэш-код для объекта obj.
- obj.toString() Возвращает строку, представляющую информацию в obj.
ArrayList Класс
импортировать java.util.ArrayList;Конструкторы
- new ArrayList
( ) Создайте новый объект ArrayList, содержащий объекты типа T. - новый ArrayList
(int size) Создайте новый объект ArrayList с размером емкости.
Методы
- arrayList.add(T obj) Добавить объект типа T в начало объекта arrayList.
- arrayList.add(int index, T obj) Вставляет указанный элемент в указанную позицию индекса в этом списке.
- arrayList.clear() Удалить все элементы из списка.
- arrayList.contains(T obj) Возвращает true списка, содержащего объект obj.
- arrayList.get(int index) Возвращает объект по указанному индексу.
- arrayList.indexOf(T obj) Возвращает индекс первого вхождения объекта obj в список. Возвращает -1, если объект не найден в списке.
- arrayList.isEmpty() Возвращает значение true, если список не содержит объектов.
- arrayList.lastIndexOf(T obj) Возвращает индекс последнего вхождения объекта obj в список. Возвращает -1 объект не найден в списке.
- arrayList.remove(int index) Удаляет объект с указанным индексом из списка.
- arrayList.remove(T obj) Удаляет первое вхождение объекта obj из списка.
- arrayList.set(int index, T obj) Устанавливает элемент указанного индекса в объект obj.
- arrayList.size( ) Возвращает количество объектов в списке.
Класс сканера
импортировать java. util.Scanner;Constructors
- new Scanner(System.in) Создайте новый объект Scanner из объекта BufferedInputStream System.in.
- новый сканер (новый файл (путь к файлу)) Создать объект сканера, который считывает файл с диска. Включающий метод должен вызывать исключение FileNotFoundException.
- new Scanner(inputString) Создать сканер, который считывает входную строку.
Методы
- scan.hasNext() Возвращает значение true, если у сканера есть еще один токен для возврата. Вернуть false, если в конце ввода.
- scan.nextLine() Вернуть следующую строку ввода. Вернуть ноль, если в конце файла.
- scan.next( ) Возвращает следующий символ из ввода.
- scan.close() Закрыть сканер.
Случайный класс
импортировать java. util.Random;Конструкторы
- new Random() Создать новый объект Random.
- новый случайный (начальный) Создайте новый случайный объект.
Методы
- scanter.nextBoolean(int n) Возвращает true или false с вероятностью 0,5 каждый.
- scan.nextDouble() Возвращает случайное значение типа double в диапазоне [0,0, 0,1).
- scanter.nextInt(int n) Возвращает случайное целое число в диапазоне [0, n-1].
Цветовой класс
Класс Color используется для создания цветов для рисования с помощью класса Graphics (описанного ниже).
импортировать java.awt.Color;Constructors
- new Color(r, g, b) Создайте новый объект Color с указанными компонентами RGB.
- new Color(r, g, b, a) Создайте новый объект Color с указанными компонентами rgb и alpha (прозрачность).
Методы
- col.getAlpha() Вернуть альфа-компонент.
- col.getBlue() Вернуть синий компонент.
- col.getGreen() Вернуть зеленый компонент.
- col.getRed() Вернуть красный компонент.
- col.lighter() Возвращает объект Color, представляющий более светлый цвет, чем col.
- col.darker() Возвращает объект Color, представляющий более темный цвет, чем col.
Статические поля
- Имена цветов цветового класса с использованием нижнего обозначения Camel: Color.black Color.blue Color.cyan Color.darkGray Color.grey Color.green Color.lightGray Color.magenta Color.orange Color. pink Color.red Color.white Color.yellow
- Имена цветов класса цветов с использованием записи констант в верхнем регистре: Color.BLACK Color.BLUE Color.CYAN Color.DARK_GRAY Color.GRAY Color.GREEN Color.LIGHT_GRAY Цвет. ПУРПУРНЫЙ Цвет. ОРАНЖЕВЫЙ Цвет. РОЗОВЫЙ Цвет. КРАСНЫЙ Цвет. БЕЛЫЙ Цвет. ЖЕЛТЫЙ
Класс шрифта
Класс Font используется для указания шрифтов для рисования строк с помощью класса Graphics (описанного ниже).
импортировать java.awt.Font;Конструкторы
- new Font(fontName, fontStyle, fontSize) Создать новый объект Font с указанными характеристиками.
Методы
- font.getFamily() Вернуть семейство шрифтов.
- font.getSize() Возвращает размер шрифта.
- font. getStyle( ) Возвращает стиль шрифта.
- font.isBold( ) Возвращает значение true, если стиль шрифта жирный, и значение false в противном случае.
- font.isItalic() Возвращает значение true, если стиль шрифта — курсив, и значение false в противном случае.
- font.isPlain() Возвращает значение true, если стиль шрифта простой, и значение false в противном случае.
Статические поля
- Font.BOLD Указывает простой (ни полужирный, ни курсивный) стиль шрифта. Хранится как растровое изображение 00.
- Font.BOLD Задает жирный шрифт для шрифта. Хранится как растровое изображение 01.
- Font.ITALIC Задает жирный шрифт для шрифта. Хранится как растровое изображение 10.
- Шрифт.ЖИРНЫЙ | Font.ITALIC Определяет полужирный и курсивный начертания для шрифта. Хранится как побитовое или 01 и 10.
Класс графики
Класс Graphic используется для рисования в апплете или компоненте. Обычно Графический конструктор явно не используется; Графический контекст создается в среда апплета или компонента. Начало (0, 0) находится в верхнем левом углу области рисования.
импортировать java.awt.Graphics;Конструкторы
- новая Графика() Создать новый графический объект.
Методы
- g.getColor(c) Получить текущий цвет.
- g.setFont(c) Установить текущий шрифт.
- g.setColor(c) Установить текущий цвет.
- g.setFont(c) Установить текущий шрифт.
- g.drawLine(startX, startY, endX, endY) Нарисовать линию от (startX, startY) до (endX, endY).
- g.drawRect(x, y, width, height) Нарисуйте прямоугольник с (x, y) в качестве верхнего левого угла и заданными шириной и высотой.
- g.FillRect(x, y, width, height) Заполнить прямоугольник (x, y) в качестве верхнего левого угла и заданной ширины и высоты.
- g.drawOval(x, y, width, height) Нарисовать овал (фактически эллипс), используя ограничивающий прямоугольник с (x, y) в качестве верхнего левого угла и заданной шириной и высотой как ограничивающий прямоугольник.
- g.FillOval(x, y, ширина, высота) Заполнить овал (фактически эллипс), используя ограничивающий прямоугольник с (x, y) в качестве верхнего левого угла и заданными шириной и высотой. как ограничивающий прямоугольник.
- g.drawArc(x, y, width, height) Нарисуйте эллиптическую дугу, используя ограничивающий прямоугольник с (x, y) в качестве верхнего левого угла и заданными шириной и высотой. как ограничивающий прямоугольник. Нарисуйте дугу от начала до конца угла.
- g.FillArc(x, y, width, height) Заполнить эллиптическую дугу, используя ограничивающий прямоугольник с (x, y) в качестве верхнего левого угла и заданной шириной и высотой как ограничивающий прямоугольник. Нарисуйте дугу от начала до конца угла.
- g.drawPolygon(xArray, yArray, nPoints) Нарисовать многоугольник, используя координаты x, заданные xArray, и координаты y, заданные yArray. Соединять последняя точка вернуться к первой точке.
- g.FillPolygon(xArray, yArray, nPoints) Заполнить многоугольник, используя координаты x, заданные xArray, и координаты y, заданные yArray. Соединять последняя точка вернуться к первой точке.
- g.drawPolyline(xArray, yArray, nPoints) То же, что и рисование многоугольника, за исключением того, что последняя точка не связана с первой точкой.
- g. drawString(str, xPos, yPos) Нарисуйте строку str так, чтобы левый верхний угол строки находился в точке (xPos, yPos).
Классы и объекты в Java | Примеры программ
В этом руководстве мы познакомимся с основными понятиями классов и объектов в Java с примерами в реальном времени.
Мы знаем, что Java — настоящий объектно-ориентированный язык программирования. В объектно-ориентированном языке программирования мы проектируем и разрабатываем прикладную программу, используя классы и объекты.
Базовая структура всех программ Java состоит из классов. Все, что мы хотим написать в java-программе, должно быть инкапсулировано в классе.
Класс — это модель для создания объектов. Это означает, что мы пишем свойства и действия объектов в классе.
Свойства представлены переменными, а действия представлены методами. Итак, класс состоит из переменных и методов.
В этой главе мы узнаем следующие темы о классах и объектах в Java. Итак, давайте разбираться по одному.
- Объекты в Java
- Примеры объектов в Java в реальном времени
- Классы в Java
- Примеры классов в Java в реальном времени
- Объявление класса в Java
- Компоненты класса в Java
- Поток выполнения программы в Java
- Простая структура программирования классов и объектов Java.
Объекты в Java
Объект — это базовая единица объектно-ориентированного языка программирования. Это любая вещь реального мира, которая имеет свойства и действия.
Другими словами, объект с состоянием и поведением известен как объект в Java . Здесь состояние представляет свойства, а поведение представляет собой действия или функциональные возможности.
Например, книга, ручка, карандаш, телевизор, холодильник, стиральная машина, мобильный телефон и т. д. Объекты в Java состоят из состояний или атрибутов (называемых элементами данных) и поведения (называемых методами).
Объект является экземпляром класса. Каждый экземпляр объекта содержит свои собственные релевантные данные.
Характеристики объекта в Java
Объект в Java имеет три характеристики:
1. Состояние: Состояние представляет свойства или атрибуты объекта. Он представлен переменной экземпляра. Свойства объекта важны, потому что результат функций зависит от свойств.
2. Поведение: Поведение представляет функциональность или действия. Он представлен методами в Java.
3. Идентификатор: Identity представляет собой уникальное имя объекта. Она отличает один объект от другого. Уникальное имя объекта используется для идентификации объекта.
Давайте рассмотрим пример из реальной жизни, чтобы ясно понять все эти моменты.
Примеры объектов Java в реальном времени
Пример 1 в реальном времени:
Мы берем пример «человек». У человека есть три характеристики: Личность (имя), Состояние (свойства) и поведение (действия или функциональные возможности). Посмотрите на рисунок ниже.
На приведенном выше рисунке человек является объектом. Первое состояние человека (объекта) — черные волосы, которые можно представить в Java следующим образом: hairColor = «черный».
Точно так же вторым свойством объекта является цвет глаз, который может быть представлен в Java как eyeColor = «черный» и так далее. Это так называемые атрибуты, определяющие свойства человека.
Давайте рассмотрим поведение или действия человека. Действия человека могут быть «есть, спать, ходить, играть и учиться». Мы представляем эти действия в Java следующим образом: есть(), спать(), ходить(), играть() и учиться(). Их называют методы .
Таким образом, когда свойства и действия любого объекта реального мира объединяются вместе, создается объект в Java.
Примечание: Существительное в английском языке обозначает свойства или состояние объекта. Его можно представить с помощью переменных, тогда как глаголы представляют действие/поведение, которое можно представить с помощью методов.
Пример 2 в реальном времени:
Возьмем еще один интересный пример «Телефон Samsung Galaxy». Если я скажу вам представить этот телефон в java в виде объекта то как вы это сделаете?
Это очень легко сделать. Задайте два вопроса: Что у него есть? (Свойства) и Что он делает? (Действия)
Свойства: ширина = 6,2 или «6,2 дюйма», высота = 13,6 или «13,6 см», цвет = «черный», ОС = «Android», цена = 1000 или «1000 $», бренд = «Samsung». «, а вес = «130 г». Все это существительные, называемые в Java атрибутами.
Действия/Поведение: call(), sendSms(), runApp(), browserInternet(), sharing(). Таким образом, объект телефона состоит из свойств, указывающих на то, что у телефона есть действия, которые выполняет телефон.
Пример в реальном времени 3:
Другой пример, который мы можем взять «Карандаш». Карандаш — это предмет. Его зовут Натрадж.
Состояние: цвет черный.
Поведение: Используется для записи. Итак, письмо — это поведение.
Итак, вы можете взять любой окружающий вас предмет и подумать, каким свойством он обладает? и какое действие он делает?
Классы в Java
Класс в Java — это фундаментальный строительный блок языка объектно-ориентированного программирования (ООП). Другими словами, класс — это базовая единица ООП.
В соответствии с концепцией ООП в Java класс — это план/шаблон объекта. Он содержит схожие типы объектов, имеющих одинаковые состояния (свойства) и поведение.
Другими словами, класс можно также определить как «класс — это группа объектов, общих для всех объектов одного типа».
Давайте разберемся с некоторыми примерами в реальном времени.
Примеры класса в Java в реальном времени
Пример 1 в реальном времени:
Рассмотрим два объекта Samsung Galaxy S4 и iPhone. Предположим, Samsung Galaxy S4 имеет некоторые свойства, такие как ширина = «6,98 см», высота = «13,6 см», ОС = «Android», бренд = «Samsung», цена = «1000 $», а действия — call(), sendMessage(). , браузер(), общий доступ().
Теперь предположим, что у iPhone есть некоторые свойства, такие как ширина = «5,86 см», высота = «12,3 см», ОС = «iOS», бренд = «Apple», цена = «1200 $», а действия — call(), отправить сообщение(), просмотреть(), поделиться().
Оба объекта имеют схожие свойства и действия, но тип один и тот же «Телефон». Это класс. то есть имя класса «Телефон».
Пример 2 в реальном времени:
Рассмотрим два разных объекта: мальчик и девочка. У мальчика есть некоторые свойства, такие как hairColor = «черный», eyeColor = «черный», skinColor = «Светлый», рост = «5,10 дюйма», вес = «65 кг», а действия read(), play(), sleep( ), ходить().
У девушки есть некоторые свойства, такие как hairColor = «brown», eyeColor = «brown», skinColor = «молочно-белый», рост = «5,4 дюйма», вес = «50 кг» и действия read(), play() , спать(), ходить(). Здесь тип и мальчика и девочки одинаков. Тип «Человек». Итак, имя класса — «Человек».
Пример 3 в реальном времени:
Предположим, мы создаем класс «Студент», имеющий вероятные атрибуты — имя студента, адрес, номер списка и другие связанные параметры.
Таким образом, такие ученики, как Джон, Рикки, Дип и т. д., будут объектами класса Учащийся, имеющий таких же членов класса, как имя ученика, адрес, номер списка и т. д.
Короче говоря, мы можем сказать, что класс — это группа объектов, обладающих:
- сходными свойствами
- сходным поведением
В Java существуют разные типы классов, которые существуют в разных пакетах. Некоторые распространенные типы классов в Java:
- Класс-оболочка
- Изменяемый класс
- Абстрактный класс
- Конечный класс
- Анонимный класс
- Класс String
- Класс System 1 02
- Системный класс 1 и т. д. определяемые пользователем типы данных, которые действуют как шаблон для создания объектов идентичного типа. Каждый класс Java содержит атрибуты и методы. Атрибуты представляют состояние класса. Методы (также известные как функции) представляют поведение класса.
Объявление класса в Java
Класс можно объявить с помощью ключевого слова class, за которым следует имя класса. Он также имеет тело в фигурных скобках. Общий синтаксис для объявления класса в Java показан ниже:
Синтаксис: имя_модификатора class имя_класса { // тело класса. }
Компоненты класса в Java
Как правило, класс может иметь следующие компоненты, выступающие в качестве шаблона. Это можно увидеть на рисунке ниже, а краткое объяснение дано ниже.
1. Модификаторы: Класс может быть либо общедоступным, либо модификатором доступа по умолчанию. Но члены класса могут быть общедоступными, частными, стандартными и защищенными. Все это модификаторы доступа.
2. Имя класса: По соглашению имя класса должно начинаться с заглавной буквы, а последующие символы — строчными (например, Студент).
Если имя состоит из нескольких слов, первая буква каждого слова должна быть заглавной (например, CollegerStudent).
Имя класса также может начинаться с символа подчеркивания «_». Допустимым может быть следующее имя класса, например _, _Student. Ключевые слова не могут быть допустимым именем класса. Например, class, true, null и т. д. не принимаются в качестве имени класса.
3. Тело: Тело каждого класса заключено в пару левых и правых фигурных скобок. В теле класс может содержать следующие члены.
класс имя_класса { // Здесь начинается тело класса. // Члены класса. 1. Объявления полей; 2. Объявления конструктора; 3. Объявления методов; 4. Объявления блоков экземпляров; 5. Объявления статических блоков; } Заканчивается здесь.
Если вы пишете простую программу, все должно находиться внутри тела класса. Давайте кратко рассмотрим всех членов класса.
Поля: Поля — это переменные-члены данных класса, в котором хранятся данные или значения. Он определяет состояние или свойства класса и его объекта. Это может быть локальная переменная, переменная экземпляра или статическая переменная.
Конструктор: Конструктор используется для создания объекта. Каждый класс должен быть хотя бы одним конструктором, иначе объект класса не может быть создан.
Если конструктор не определен явно, компилятор автоматически добавит в класс конструктор по умолчанию. Конструктор можно разделить на два типа, например конструктор по умолчанию и пользовательский конструктор.
Метод: Метод определяет действие или поведение класса, которое может выполнять объект класса. У него есть тело в фигурных скобках. В теле мы пишем код, который выполняет действия. Это может быть метод экземпляра или статический метод.
Блок: Блок в основном используется для изменения значений переменных по умолчанию. Это может быть экземплярный блок или статический блок.
Интерфейс: Интерфейс в Java — это механизм, который используется для достижения полной абстракции. По сути, это своего рода класс, но в качестве членов он может иметь только объявления абстрактных методов и константы.
Используется для множественного наследования в Java. Подробнее об интерфейсе вы узнаете из дальнейшего туториала.
Метод main: Класс также имеет метод main, предоставляющий точку входа для запуска выполнения любой программы. Основной метод носит статический характер. Сигнатура основного метода выглядит следующим образом:
public static void main(String[] args) { // Это статический регион. ............. }
JVM выполняет все, что находится между фигурными скобками { } основного метода. Каждая Java-программа имеет по крайней мере один класс и по крайней мере один основной метод.
Поток выполнения программы на Java
1. Из пяти элементов, объявленных внутри тела класса, статическая переменная, статический блок и статический метод выполняются первыми во время загрузки файла класса точек.
2. Переменная экземпляра выполняется при создании объекта.
3. При создании объекта сначала выполняется блок экземпляра перед выполнением конструктора.
4. После выполнения блока экземпляра будет выполнена часть конструктора.
5. После выполнения части конструктора выполняется метод экземпляра.
6. Локальная переменная выполняется внутри метода, конструктора или блока.
Простая структура программирования классов и объектов в Java
Давайте разберемся в простой структуре программирования классов и объектов в Java. Допустим, Student — это имя класса, а его свойствами будут name, rollNo, id, age of student. «public» — это модификатор доступа.
Давайте реализуем эти свойства в программе на Java.
Код программы:
открытый класс Студент { // Объявление состояния/свойств. Имя строки; // Переменная экземпляра. внутренний номер рулона; // Переменная экземпляра. внутренний идентификатор; статический возраст; // Статическая переменная. // Объявление конструктора. Ученик() { // Тело конструктора. } // Декларация действий. void display() // Метод экземпляра. { // тело метода. } // Объявление блока экземпляра. { // тело блока. } ....... ....... public static void main (аргументы String []) { ....... } }
Ключевые пункты:
1. Вы можете создать любое количество объектов класса.
2. Процесс создания объекта определенного класса называется созданием экземпляра объекта .
3. Объект называется экземпляром класса.
Разница между классом и объектом в Java
Существуют следующие различия между классом и объектом в Java. Они следующие:
1. Класс — это определяемый пользователем тип данных, тогда как объект — это экземпляр типа данных класса.
2. Класс порождает объекты, тогда как объект дает жизнь классу.
3. Классы не занимают место в памяти, но объекты занимают место в памяти.
4. Классами нельзя манипулировать из-за того, что они недоступны в ячейке памяти, но объектами можно манипулировать.
Можем ли мы назвать класс как тип данных в Java?
Да, класс также считается определяемым пользователем типом данных. Это потому, что пользователь создает класс.
Ключевые моменты, касающиеся классов и объектов в Java
1. Объекты, классы, абстракция данных, инкапсуляция, наследование, переопределение методов, полиморфизм и передача сообщений являются фундаментальными терминами и понятиями для понимания концепций объектно-ориентированного подхода к программированию.
2. Объекты — это основные объекты среды выполнения в объектно-ориентированных системах.
3. Все объекты в системе занимают отдельное пространство памяти независимо друг от друга.
4. Класс в ООП представляет собой группу подобных объектов.
5. После создания класса можно создать любое количество объектов, принадлежащих этому классу.
6. Объект класса также известен как экземпляр.
7. Класс никогда не представляет объект, а скорее представляет данные и действия, которые будет иметь объект.
8. Члены класса состоят из переменных (членов данных) и методов (функций-членов).
В этом руководстве вы узнали о классах и объектах в Java с примерами в реальном времени и базовой структурой программирования. Надеюсь, вы поняли все основные моменты, связанные с классами и объектами в Java.
В следующем уроке мы узнаем, как создать объект класса в java.
Спасибо за прочтение!!! Далее ⇒ Как создать объект в Java
⇐ Предыдущий Следующий ⇒
Язык программирования Apache Groovy
В этой главе рассматриваются объектно-ориентированные аспекты языка программирования Groovy.
1.1. Примитивные типы
Groovy поддерживает те же примитивные типы, которые определены в спецификации языка Java:
целочисленных типа:
byte
(8 бит),short
(16 бит),int
(32 бит) иlong
(64 бит)типы с плавающей запятой:
float
(32 бита) иdouble
(64 бита)логический тип
true
илиfalse
)тип
char
(16 бит, может использоваться как числовой тип, представляющий код UTF-16)
Как и в Java, Groovy использует соответствующие классы-оболочки, когда объекты, соответствующие любому требуется типов примитивов:
Таблица 1. Оболочки примитивов Примитивный тип Класс обертки логический
Логический
символ
Символ
короткий
Короткий
внутр.
Целое число
длинный
Длинный
поплавок
Поплавок
двойной
Двойной
Автоматическая упаковка и распаковка происходят, например, при вызове метода, требующего класс-оболочку и передавая ему примитивную переменную в качестве параметра, или наоборот. Это похоже на Java, но Groovy развивает эту идею дальше.
В большинстве сценариев вы можете обращаться с примитивом так же, как с полным эквивалентом оболочки объекта. Например, вы можете вызвать
.toString()
или.equals(other)
для примитива. Groovy автоматически выполняет перенос и развертывание между ссылками и примитивами по мере необходимости.Вот пример использования
int
, объявленного как статическое поле в классе (которое обсуждается ниже):class Foo { статический интервал я } утверждать Foo.class.getDeclaredField('i').type == int.class (1) утверждать Foo.i.class != int.class && Foo.i.class == Integer.class (2)
1 Примитивный тип соблюдается в байт-коде 2 Просмотр поля во время выполнения показывает, что оно было автоматически упаковано Теперь вы можете быть обеспокоены тем, что это означает, что каждый раз, когда вы используете математический оператор для ссылки на примитив что вы понесете расходы на распаковку и повторную упаковку примитива. Но это не так, так как Groovy скомпилирует ваши операторы в эквиваленты их методов и вместо этого использует их. Кроме того, Groovy автоматически распаковывает примитив при вызове метода Java, который принимает примитив. параметр и автоматически упаковать примитивный метод, возвращающий значения из Java. Однако имейте в виду, что есть некоторые отличия от разрешения методов Java.
1.2. Ссылочные типы
Помимо примитивов, все остальное является объектом и имеет связанный класс, определяющий его тип. Вскоре мы обсудим классы, а также связанные с классами или похожие на классы вещи, такие как интерфейсы, трейты и записи.
Мы можем объявить две переменные типа String и List следующим образом:
String movie = 'Матрица' Список актеров = ['Киану Ривз', 'Хьюго Уивинг']
1.3. Универсальные шаблоны
В Groovy используются те же концепции универсальных шаблонов, что и в Java. При определении классов и методов можно использовать параметр типа и создавать универсальный класс, интерфейс, метод или конструктор.
Использование универсальных классов и методов независимо от того, определены ли они в Java или Groovy, может включать предоставление аргумента типа.
Мы можем объявить переменную типа «список строк» следующим образом:
List
roles = ['Trinity', 'Morpheus'] Java использует стирание типов для обратной совместимости с более ранними версиями Явы. Dynamic Groovy можно рассматривать как более агрессивно применяющий стирание типов. Как правило, во время компиляции будет проверяться информация о менее универсальных типах. Статическая природа Groovy использует те же проверки, что и Java, в отношении информации о дженериках.
Классы Groovy очень похожи на классы Java и совместимы с классами Java на уровне JVM. У них могут быть методы, поля и свойства (подумайте о свойствах JavaBeans, но с меньшим количеством шаблонов). Классы и члены класса могут иметь те же модификаторы (public, protected, private, static и т. д.), что и в Java. с некоторыми незначительными отличиями на уровне исходного кода, которые кратко объясняются.
Основные отличия классов Groovy от их аналогов в Java:
Классы или методы без модификатора видимости автоматически становятся общедоступными (для обеспечения закрытой видимости пакета можно использовать специальную аннотацию).
Поля без модификатора видимости автоматически превращаются в свойства, что приводит к менее подробному коду, поскольку явные методы получения и установки не нужны. Подробнее об этом аспекте будет рассказано в разделе полей и свойств.
Классы не обязательно должны иметь то же базовое имя, что и определения их исходных файлов, но это настоятельно рекомендуется в большинстве сценариев (см. также следующий пункт о сценариях).
Один исходный файл может содержать один или несколько классов (но если файл содержит какой-либо код, не относящийся к классу, он считается скриптом). Скрипты — это просто классы с некоторыми специальные соглашения и будут иметь то же имя, что и их исходный файл (поэтому не включайте определение класса в сценарий, имеющий то же имя, что и исходный файл сценария).
В следующем коде представлен пример класса.
класс Лицо { (1) Имя строки (2) Целочисленный возраст def увеличение возраста (целое число лет) { (3) this.age += лет } }
1 начало класса, с именем Лицо
2 строковое поле и свойство с именем name
3 определение метода 2.1. Нормальный класс
Нормальные классы относятся к классам высшего уровня и конкретным. Это означает, что они могут быть созданы без ограничений из любых других классов или скриптов. Таким образом, они могут быть только общедоступными (даже несмотря на то, что
public 9ключевое слово 1043 может быть скрыто). Классы создаются путем вызова их конструкторов с использованием ключевого слова
new
, как показано в следующем фрагменте кода.def p = новый человек()
2.2. Внутренний класс
Внутренние классы определяются внутри других классов. Охватывающий класс может использовать внутренний класс, как обычно. С другой стороны, внутренний класс может получить доступ к членам окружающего его класса, даже если они закрыты. Классы, отличные от окружающего класса, не имеют доступа к внутренним классам. Вот пример:
класс Внешний { частная строка privateStr деф callInnerMethod () { новый Внутренний().methodA() (1) } класс Внутренний { (2) определить методA() { println "${privateStr}. " (3) } } }
1 создается экземпляр внутреннего класса и вызывается его метод 2 определение внутреннего класса внутри его внешнего класса 3 даже будучи закрытым, доступ к полю включающего класса осуществляется внутренним классом Есть несколько причин для использования внутренних классов:
Они повышают инкапсуляцию, скрывая внутренний класс от других классов, которым не нужно об этом знать. Это также приводит к более чистым пакетам и рабочим пространствам.
Они обеспечивают хорошую организацию, группируя классы, используемые только одним классом.
Они приводят к более удобному сопровождению кода, поскольку внутренние классы находятся рядом с классами, которые их используют.
Обычно внутренний класс является реализацией некоторого интерфейса, методы которого необходимы внешнему классу. Приведенный ниже код иллюстрирует этот типичный шаблон использования, здесь он используется с потоками.
класс Внешний2 { private String privateStr = 'какая-то строка' определить startThread () { новый поток (новый Inner2()). start() } класс Inner2 реализует Runnable { недействительный запуск () { println "${privateStr}." } } }
Обратите внимание, что класс
Inner2
определен только для реализации методаrun
для классаOuter2
. В этом случае анонимные внутренние классы помогают устранить многословие. Эта тема раскрыта вкратце.Groovy 3+ также поддерживает синтаксис Java для создания нестатических внутренних классов, например:
class Computer { класс ЦП { int coreNumber ЦП(int coreNumber) { this. coreNumber = основной номер } } } assert 4 == new Computer().new Cpu(4).coreNumber
2.2.1. Анонимный внутренний класс
Предыдущий пример внутреннего класса (
Inner2
) можно упростить с помощью анонимного внутреннего класса. Та же функциональность может быть достигнута с помощью следующего кода:класс Внешний3 { private String privateStr = 'какая-то строка' определить startThread () { новый поток (новый Runnable() { (1) недействительный запуск () { println "${privateStr}." } }).start() (2) } }
1 по сравнению с последним примером предыдущего раздела, new Inner2()
был замененновым Runnable()
вместе со всей его реализацией2 метод start
вызывается нормальноТаким образом, не было необходимости определять новый класс для однократного использования.
2.2.2. Абстрактный класс
Абстрактные классы представляют общие концепции, поэтому они не могут быть конкретизированы, поскольку создаются для подклассов. Их члены включают поля/свойства и абстрактные или конкретные методы. Абстрактные методы не имеют реализации и должны реализовываться конкретными подклассами.
абстрактный класс Abstract { (1) Имя строки абстрактный метод abstractMethod() (2) определить конкретный метод () { println 'бетон' } }
1 абстрактных классов должны быть объявлены с ключевым словом abstract
2 абстрактных методов также должны быть объявлены с реферат
ключевое словоАбстрактные классы обычно сравнивают с интерфейсами. Есть как минимум два важных различия в выборе того или иного. Во-первых, в то время как абстрактные классы могут содержать поля/свойства и конкретные методы, интерфейсы могут содержать только абстрактные методы (сигнатуры методов). Более того, один класс может реализовывать несколько интерфейсов, тогда как он может расширять только один класс, абстрактный или нет.
2.3. Наследование
Наследование в Groovy напоминает наследование в Java. Он предоставляет механизм для повторного использования дочерним классом (или подклассом). код или свойства родителя (или суперкласса). Классы, связанные посредством наследования, образуют иерархию наследования. Общее поведение и элементы перемещаются вверх по иерархии, чтобы уменьшить дублирование. Специализации происходят в дочерних классах.
Поддерживаются различные формы наследования:
реализация наследование, где код (методы, поля или свойства) из суперкласса или из один или несколько признаков повторно используются дочерним классом
контракт наследование, когда класс обещает предоставить определенные абстрактные методы, определенные в суперклассе, или определены в одном или нескольких трейтах или интерфейсах.
2.4. Надклассы
Родительские классы имеют общие видимые поля, свойства или методы с дочерними классами. Дочерний класс может иметь не более одного родительского класса. Ключевое слово
extends
используется непосредственно перед указанием типа суперкласса.2.5. Интерфейсы
Интерфейс определяет контракт, которому должен соответствовать класс. Интерфейс определяет только список методов, которые необходимо быть реализованным, но не определяет реализацию метода.
Интерфейс приветствия { (1) недействительное приветствие (имя строки) (2) }
1 интерфейс должен быть объявлен с использованием ключевого слова interface
2 интерфейс определяет только сигнатуры методов Методы интерфейса всегда общедоступный . Использование
защищенных
иличастных методов
в интерфейсах является ошибкой:interface Greeter { защищенное недействительное приветствие (имя строки) (1) }
1 Использование защищенного Класс реализует интерфейс, если он определяет интерфейс в своем списке
реализует
или если какой-либо из его суперклассов делает:класс SystemGreeter реализует Greeter { (1) void приветствие (имя строки) { (2) println "Привет, $имя" } } defgreeter = новый SystemGreeter() утверждать приветствующий экземпляр Greeter (3)
1 SystemGreeter
объявляетGreeter 9Интерфейс 1043 с использованием
реализует ключевое слово
2 Затем реализует требуемый метод приветствия
3 Любой экземпляр SystemGreeter
также является экземпляром интерфейсаGreeter
Интерфейс может расширять другой интерфейс:
интерфейс ExtendedGreeter extends Greeter { (1) void sayBye (имя строки) }
1 интерфейс ExtendedGreeter
расширяет интерфейсGreeter
с помощью ключевого словаextends
Стоит отметить, что для того, чтобы класс был экземпляром интерфейса, он должен быть явным. Например, следующее класс определяет метод
приветствия
, как он объявлен вприветствии
, но не объявляетGreeter
в своем интерфейсы:класс DefaultGreeter { void приветствие (имя строки) { println "Привет" } } приветствие = новый DefaultGreeter() assert !(greeter instanceof Greeter)
Другими словами, Groovy не определяет структурную типизацию. Однако можно сделать экземпляр объекта реализовать интерфейс во время выполнения, используя оператор принуждения
as
:Greeter = new DefaultGreeter() (1) принудительно = приветствующий как приветствующий (2) утверждать принудительный экземпляр Greeter (3)
1 создать экземпляр DefaultGreeter
, который не реализует интерфейс2 принудить экземпляр к Greeter
во время выполнения3 принудительный экземпляр реализует интерфейс Greeter
Вы можете видеть, что есть два разных объекта: один является исходным объектом, экземпляром
DefaultGreeter
, который не реализовать интерфейс. Другой экземплярGreeter
делегирует принудительный объект.Интерфейсы Groovy не поддерживают реализацию по умолчанию, как интерфейсы Java 8. Если вы ищете что-то подобные (но не равные), трейты близки к интерфейсам, но допускают реализацию по умолчанию, а также другие важные функции, описанные в данном руководстве. 3.1. Конструкторы
Конструкторы — это специальные методы, используемые для инициализации объекта с определенным состоянием. Как и в случае с обычными методами, класс может объявить более одного конструктора, если каждый конструктор имеет уникальный тип подписи. Если объект не требует никаких параметров при строительстве, он может использовать без аргументов конструктор. Если конструкторы не указаны, компилятор Groovy предоставит пустой конструктор без аргументов.
Groovy поддерживает два стиля вызова:
3.1.1. Позиционные параметры
Чтобы создать объект с использованием позиционных параметров, соответствующий класс должен объявить один или несколько конструкторы. В случае нескольких конструкторов каждый из них должен иметь уникальную сигнатуру типа. Конструкторы также могут добавлен в класс с помощью аннотации groovy.transform.TupleConstructor.
Как правило, после объявления хотя бы одного конструктора экземпляр класса может быть создан только при наличии одного из его вызвали конструкторы. Стоит отметить, что в этом случае вы не можете нормально создать класс с именованными параметрами. Groovy поддерживает именованные параметры, если класс содержит конструктор без аргументов или предоставляет конструктор, который принимает аргумент
Map
в качестве первого (и, возможно, единственного) аргумента — подробности см. в следующем разделе.Существует три формы использования объявленного конструктора. Первый - это обычный способ Java, с
новое ключевое слово
. Другие полагаются на приведение списков к желаемым типам. В этом случае можно принуждать скак
ключевое слово и статически введя переменную.класс PersonConstructor { Имя строки Целочисленный возраст PersonConstructor(имя, возраст) { (1) это.имя = имя this.age = возраст } } def person1 = новый PersonConstructor('Мария', 1) (2) def person2 = ['Мари', 2] как PersonConstructor (3) PersonConstructor person3 = ['Мария', 3] (4)
1 Объявление конструктора 2 Вызов конструктора, классический способ Java 3 Использование конструктора с использованием принуждения с как ключевое слово
4 Использование конструктора с использованием принуждения в присваивании 3.
1.2. Именованные параметрыЕсли не объявлен конструктор (или конструктор без аргументов), можно создавать объекты, передавая параметры в форме map (пары свойство/значение). Это может быть удобно в тех случаях, когда нужно разрешить несколько комбинаций параметров. В противном случае, используя традиционные позиционные параметры, необходимо было бы объявить все возможные конструкторы. Наличие конструктора, в котором первым (и, возможно, единственным) аргументом является число 9.1042 Map аргумент также поддерживается - такой Конструктор также можно добавить с помощью аннотации groovy.transform.MapConstructor.
класс PersonWOConstructor { (1) Имя строки Целочисленный возраст } def person4 = новый PersonWOConstructor() (2) def person5 = new PersonWOConstructor(имя: 'Мари') (3) def person6 = новый PersonWOConstructor (возраст: 1) (4) def person7 = new PersonWOConstructor(имя: 'Мари', возраст: 2) (5)
1 Конструктор не объявлен 2 В экземпляре не заданы параметры 3 имя
параметр, указанный в экземпляре4 возраст
параметр, указанный в экземпляре5 имя
ивозраст
параметры, указанные в экземпляреОднако важно подчеркнуть, что этот подход дает больше возможностей вызывающему конструктору, возлагая на вызывающую сторону повышенную ответственность за корректность имен и типов значений. Таким образом, если требуется больший контроль, может быть предпочтительным объявление конструкторов с использованием позиционных параметров.
Примечания:
Хотя в приведенном выше примере нет конструктора, вы также можете указать конструктор без аргументов. или конструктор, где первым аргументом является
Map
, чаще всего это единственный аргумент.Если конструктор не объявлен (или конструктор без аргументов), Groovy заменяет вызов именованного конструктора вызовом в конструктор без аргументов, за которым следуют вызовы установщика для каждого предоставленного именованного свойства.
Если первым аргументом является карта, Groovy объединяет все именованные параметры в карту (независимо от порядка) и предоставляет карту в качестве первого параметра. Это может быть хорошим подходом, если ваши свойства объявлены как
final
(поскольку они будут установлены в конструкторе, а не постфактум с сеттерами).Вы можете поддерживать как именованное, так и позиционное построение путем предоставления как позиционных конструкторов, так и конструктора без аргументов или карты.
Вы можете поддерживать гибридную конструкцию, имея конструктор, в котором первый аргумент является Map, но есть и дополнительные позиционные параметры. Используйте этот стиль с осторожностью.
3.2. Методы
Методы Groovy очень похожи на другие языки. Некоторые особенности будут показаны в следующих подразделах.
3.2.1. Определение метода
Метод определяется с типом возвращаемого значения или с 9Ключевое слово 1042 def , чтобы возвращаемый тип был нетипизированным. Метод также может принимать любое количество аргументов, типы которых могут быть не объявлены явно. Модификаторы Java можно использовать как обычно, и если модификатор видимости не указан, метод является общедоступным.
Методы Groovy всегда возвращают некоторое значение. Если оператор
return
не указан, будет возвращено значение, оцененное в последней выполненной строке. Например, обратите внимание, что ни один из следующих методов не использует возврат9.1043 ключевое слово.
def someMethod() { 'вызван метод' } (1) String AnotherMethod() { 'вызван другой метод' } (2) def ThirdMethod(param1) { "$param1 передан" } (3) static String fourMethod(String param1) { "$param1 передан" } (4)
1 Метод без объявленного возвращаемого типа и без параметра 2 Метод с явным типом возврата и без параметра 3 Метод с параметром без определенного типа 4 Статический метод со строковым параметром 3.
2.2. Именованные параметрыКак и конструкторы, обычные методы также могут вызываться с именованными параметрами. Для поддержки этой записи используется соглашение, согласно которому первым аргументом метода является число 9.1042 Карта . В теле метода к значениям параметров можно получить доступ, как и в картах нормалей (
map.key
). Если метод имеет только один аргумент карты, все предоставленные параметры должны быть именованы.def foo (аргументы карты) { "${args.name}: ${args.age}" } foo(name: 'Marie', age: 1)
Смешивание именованных и позиционных параметров
Именованные параметры можно смешивать с позиционными параметрами. То же соглашение применяется в этом случае в дополнение к карте
9.1043 аргумент в качестве первого аргумента, рассматриваемый метод будет иметь дополнительные позиционные аргументы по мере необходимости. Предоставленные позиционные параметры при вызове метода должны быть в порядке. Именованные параметры могут быть в любом положении. Они сгруппированы на карте и поставляются как первый параметр автоматически.
def foo(аргументы карты, целое число) { "${args.name}: ${args.age}, а число равно ${number}" } foo(имя: 'Мари', возраст: 1, 23) (1) foo(23, имя: 'Мари', возраст: 1) (2)
1 Вызов метода с дополнительным номером
аргументом типаInteger
2 Вызов метода с измененным порядком аргументов Если у нас нет карты в качестве первого аргумента, то для этого аргумента должна быть указана карта вместо именованных параметров. Невыполнение этого требования приведет к ошибке
groovy.lang.MissingMethodException 9.1043 :
def foo (целое число, аргументы карты) { "${args. name}: ${args.age}, а число равно ${number}" } foo(имя: 'Мари', возраст: 1, 23) (1)
1 Вызов метода выдает groovy.lang.MissingMethodException: сигнатура метода: foo() не применима для типов аргументов: (LinkedHashMap, Integer) значения: [[name:Marie, age:1], 23]
, потому что именованный аргументПараметр Map
не определен как первый аргументПриведенного выше исключения можно избежать, если мы заменим именованные аргументы явным
Map
аргументом:def foo(Integer number, Map args) { "${args.name}: ${args.age}, и номер ${число}" } foo(23, [имя: 'Мари', возраст: 1]) (1)
1 Явный Аргумент Map
вместо именованных аргументов делает вызов действительнымХотя Groovy позволяет смешивать именованные и позиционные параметры, это может привести к ненужной путанице. С осторожностью смешивайте именованные и позиционные аргументы. 3.2.3. Аргументы по умолчанию
Аргументы по умолчанию делают параметры необязательными. Если аргумент не указан, метод принимает значение по умолчанию.
def foo (String par1, Integer par2 = 1) { [name: par1, age: par2] } утверждать foo('Мари').age == 1
Параметры удаляются справа, однако обязательные параметры никогда не удаляются.
def baz(a = 'a', int b, c = 'c', логическое значение d, e = 'e') { "$a $b $c $d $e" } утверждать baz(42, true) == 'a 42 c true e' assert baz('A', 42, true) == 'A 42 c true e' assert baz('A', 42, 'C', true) == 'A 42 C true e' assert baz('A', 42, 'C', true, 'E') == 'A 42 C true E'
Это же правило применяется как к конструкторам, так и к методам. При использовании
@TupleConstructor
применяются дополнительные параметры конфигурации.3.2.4. Varargs
Groovy поддерживает методы с переменным числом аргументов. Они определены следующим образом:
def foo(p1, …, pn, T… args)
. Здесьfoo
поддерживаетn
аргументов по умолчанию, а также неопределенное количество дополнительных аргументов, превышающееn
.def foo(Object... args) {args.length} утверждать foo() == 0 утверждать foo(1) == 1 утверждать foo(1, 2) == 2
В этом примере определяется метод
foo
, который может принимать любое количество аргументов, включая полное отсутствие аргументов.args.length
вернет количество переданных аргументов. Groovy позволяет использоватьT[]
в качестве альтернативыT…
. Это означает, что любой метод с массивом в качестве последнего параметра рассматривается Groovy как метод, который может принимать переменное количество аргументов.def foo(Object[] args) {args.length} утверждать foo() == 0 утверждать foo(1) == 1 утверждать foo(1, 2) == 2
Если метод с varargs вызывается с
null
в качестве параметра vararg, то аргументом будетnull
, а не массив длины один сnull
в качестве единственного элемента.def foo(Object... args) {args} assert foo(null) == null
Если метод varargs вызывается с массивом в качестве аргумента, то аргументом будет этот массив, а не массив длины один, содержащий данный массив в качестве единственного элемента.
def foo(Object... args) {args} Целое[] ints = [1, 2] утверждать foo(ints) == [1, 2]
Еще одним важным моментом являются varargs в сочетании с перегрузкой методов. В случае перегрузки метода Groovy выберет наиболее конкретный метод. Например, если метод
foo
принимает аргумент varargs типаT
, а другой методfoo
также принимает один аргумент типаT
, второй метод предпочтительнее.def foo(Object... args) { 1 } def foo(Объект x) { 2 } утверждать foo() == 1 утверждать foo(1) == 2 утверждать foo(1, 2) == 1
3.2.5. Алгоритм выбора метода
Dynamic Groovy поддерживает множественную отправку (или мультиметоды). При вызове метода определяется фактически вызываемый метод динамически на основе аргументов методов времени выполнения. Сначала будут рассмотрены имя метода и количество аргументов (включая поправку на varargs), а затем тип каждого аргумента. Рассмотрим следующие определения методов:
def method(Object o1, Object o2) { 'o/o' } метод определения (целое число i, строка s) { 'i/s'} метод def (строка s, целое число i) { 's/i' }
Возможно, как и ожидалось, вызов метода
String
иInteger
, вызывает определение нашего третьего метода.assert method('foo', 42) == 's/i'
Здесь больше интереса, когда типы неизвестны во время компиляции. Возможно, аргументы объявлены типа
Object
(список таких объектов в нашем случае). Java определит, что вариант метода(объект, объект)
будет выбран во всех случаях (если не использовались приведения типов), но, как видно из следующего примера, Groovy использует тип среды выполнения и будет вызывать каждый из наших методов один раз (и обычно приведение не требуется):Пары List
- > = [['foo', 1], [2, 'bar'], [3, 4]]
assert pairs. collect { a, b -> method(a, b) } == ['s/i', 'i/s', 'o/o']
Для каждого из первых двух из наших трех методов вызовов было найдено точное совпадение типов аргументов. Для третьего вызова точное совпадение метода
(целое число, целое число)
не было найдено, но метод(объект, объект)
остается действительным и будет выбран.Выбор метода заключается в том, чтобы найти наиболее подходящих из допустимых методов-кандидатов, которые имеют совместимые типы параметров. Таким образом, метод
(объект, объект)
также действителен для первых двух вызовов, но не так близко соответствует как варианты, где типы точно совпадают. Чтобы определить наиболее близкое соответствие, среда выполнения имеет понятие расстояния фактического аргумента. type отличается от объявленного типа параметра и пытается минимизировать общее расстояние по всем параметрам.В следующей таблице показаны некоторые факторы, влияющие на расчет расстояния.
Аспект Пример Непосредственно реализованные интерфейсы соответствуют более точно, чем интерфейсы, расположенные выше по иерархии наследования.
Учитывая эти определения интерфейса и метода:
интерфейс I1 {} интерфейс I2 расширяет I1 {} интерфейс I3 {} класс Clazz реализует I3, I2 {} метод защиты (I1 i1) { 'I1' } метод защиты (I3 i3) { 'I3' }
Непосредственно реализованный интерфейс будет соответствовать:
метод утверждения (новый Clazz()) == 'I3'
Массив объектов предпочтительнее, чем объект.
метод определения (объект [] аргумент) {'массив'} метод def (аргумент объекта) { 'объект' } метод утверждения ([] как объект []) == 'массив'
Варианты без варарга предпочтительнее вариантов с вараргом.
метод определения (String s, Object... vargs) { 'vararg' } метод def (строка s) { 'не-vararg'} метод утверждения ('foo') == 'не-vararg'
Если применимы два варианта vararg, предпочтительным является тот, который использует минимальное количество аргументов vararg.
метод определения (String s, Object... vargs) { 'two vargs' } метод def (String s, Integer i, Object... vargs) { 'one varg' } метод утверждения('foo', 35, новая дата()) == 'one varg'
Интерфейсы предпочтительнее суперклассов.
интерфейс I {} Базовый класс {} class Child расширяет базовые реализации I {} метод защиты (база b) { 'суперкласс'} метод защиты (I i) { 'интерфейс' } метод утверждения (новый дочерний элемент ()) == 'интерфейс'
Для примитивного типа аргумента предпочтительнее объявленный тип параметра, который является таким же или немного больше.
метод определения (длинный l) { 'длинный' } метод def (Short s) { 'Short' } метод определения (BigInteger bi) { 'BigInteger'} метод утверждения (35) == 'Длинный'
Если два варианта имеют одинаковое расстояние, это считается неоднозначным и вызывает исключение во время выполнения:
метод определения (дата d, объект o) { 'd/o' } метод def (Object o, String s) { 'o/s' } деф ех = долженОшибиться { метод println (новая дата (), 'баз') } assert ex.message.contains('Неоднозначная перегрузка метода')
Приведение может использоваться для выбора нужного метода:
assert method(new Date(), (Object)'baz') == 'd/o' метод assert((Object)new Date(), 'baz') == 'o/s'
3.2.6. Объявление исключения
Groovy автоматически позволяет обрабатывать проверенные исключения как непроверенные исключения. Это означает, что вам не нужно объявлять какие-либо проверенные исключения, которые может генерировать метод. как показано в следующем примере, который может выбросить
FileNotFoundException
, если файл не найден:def badRead() { новый файл ('doesNotExist.txt').текст } долженОшибиться(FileNotFoundException) { плохоЧитать() }
Вам также не потребуется окружать вызов метода
badRead
в предыдущем примере внутри try/catch блокировать - хотя вы можете сделать это, если хотите.Если вы хотите объявить какие-либо исключения, которые может вызвать ваш код (отмеченные или иные), вы можете это сделать. Добавление исключений не изменит использование кода из любого другого кода Groovy, но его можно рассматривать как документацию. для человека, читающего ваш код. Исключения станут частью объявления метода в байт-коде, поэтому, если ваш код может быть вызван из Java, может быть полезно включить их. Использование явного объявления проверенного исключения показано в следующем примере:
def badRead() выдает исключение FileNotFoundException { новый файл ('doesNotExist. txt').текст } долженОшибиться(FileNotFoundException) { плохоЧитать() }
3.3. Поля и свойства
3.3.1. Поля
Поле является членом класса или признака, который имеет:
обязательный модификатор доступа (
общедоступный
,защищенный
иличастный
)один или несколько необязательных модификаторов (
статический
,конечный
,синхронизированный
)дополнительный тип
обязательный имя
класс Данные { частный внутренний идентификатор (1) protected Строковое описание (2) public static final boolean DEBUG = false (3) }
1 частное поле
с именемid
типаint
2 защищенное
поле с именемописание
типаСтрока
3 открытое статическое окончательное
поле с именем DEBUG типалогическое значение
Поле может быть инициализировано непосредственно при объявлении:
class Data { Идентификатор частной строки = IDGenerator. next() (1) // ... }
1 приватное поле id
инициализируется с помощьюIDGenerator.next()
Можно опустить объявление типа поля. Однако это считается плохой практикой, и в целом рекомендуется использовать строгую типизацию для полей:
класс BadPractice { частная картография (1) } класс GoodPractice { частное сопоставление Map
(2) } 1 поле сопоставление
не объявляет тип2 поле сопоставление
имеет сильный типРазница между ними важна, если вы хотите использовать дополнительную проверку типов позже. Это также важно как способ документирования дизайна класса. Однако в некоторых случаях, таких как сценарии или если вы хотите полагаться на утиный ввод, это может быть полезно. опустить тип.
3.3.2. Свойства
Свойство — это внешне видимая функция класса. Вместо того, чтобы просто использовать общедоступное поле для представления такие функции (что обеспечивает более ограниченную абстракцию и ограничивает возможности рефакторинга), типичный подход в Java состоит в том, чтобы следовать соглашениям, изложенным в Спецификация JavaBeans, т. е. представление свойства с помощью комбинация частного резервного поля и геттеров/сеттеров. Groovy следует этим же соглашениям но обеспечивает более простой способ определения свойства. Вы можете определить свойство с помощью:
отсутствует модификатор доступа (нет
public
,protected
илиprivate
)один или несколько необязательных модификаторов (
статический
,конечный
,синхронизированный
)дополнительный тип
обязательный имя
Затем Groovy создаст геттеры/сеттеры соответствующим образом. Например:
класс Человек { Имя строки (1) возраст (2) }
1 создает резервное поле private String name
,getName
иsetName
метод2 создает резервное поле private int age
, значениеgetAge
и метод setAgeЕсли свойство объявлено
final
, установщик не создается:class Person { final Имя строки (1) конечный возраст (2) Человек (имя строки, целочисленный возраст) { this.name = имя (3) this.age = возраст (4) } }
1 определяет доступное только для чтения свойство типа String
2 определяет доступное только для чтения свойство типа int
3 назначает параметр name
полюname
4 присваивает возраст
в полеage
Доступ к свойствам осуществляется по имени и вызовет геттер или сеттер прозрачно, если только код не находится в классе который определяет свойство:
class Person { Имя строки пустое имя (имя строки) { this. name = "Wonder $name" (1) } Название строки () { this.name (2) } } def p = новый человек () p.name = 'Диана' (3) утверждать p.name == 'Диана' (4) p.name('Женщина') (5) assert p.title() == 'Чудо-женщина' (6)
1 this.name
будет напрямую обращаться к полю, поскольку доступ к свойству осуществляется из класса, который его определяет2 аналогичным образом доступ для чтения осуществляется непосредственно к полю имени имени .3 доступ на запись к свойству осуществляется за пределами класса Person
, поэтому он будет неявно вызыватьsetName
4 доступ на чтение к свойству осуществляется за пределами класса Person
, поэтому он будет неявно вызыватьgetName
5 это вызовет метод name
дляPerson
, который выполняет прямой доступ к полю6 это вызовет метод title
дляPerson
, который выполняет прямой доступ для чтения к полюСтоит отметить, что такое поведение прямого доступа к резервному полю сделано для предотвращения стека переполнение при использовании синтаксиса доступа к свойству в классе, который определяет свойство.
Можно перечислить свойства класса благодаря мета-полю
свойств
экземпляра:class Person { Имя строки возраст } def p = новый человек () assert p.properties.keySet().containsAll(['name','age'])
По соглашению Groovy распознает свойства, даже если нет резервного поля если есть геттеры или сеттеры которые следуют спецификации Java Beans. Например:
класс ПсевдоСвойства { // псевдосвойство "имя" недействительным setName (имя строки) {} Строка getName() {} // псевдо-свойство только для чтения "age" интервал getAge () { 42 } // псевдо-свойство только для записи "groovy" void setGroovy (логическое значение Groovy) {} } def p = новые псевдосвойства() p.name = 'Фу' (1) утверждать p.age == 42 (2) p.groovy = true (3)
1 запись p. name
разрешена, так как есть псевдосвойствоname
2 чтение стр.
разрешено, потому что есть свойство псевдо-только для чтенияage
3 запись p.groovy
разрешена, т.к. имеется свойство только для записиgroovy
Этот синтаксический сахар лежит в основе многих DSL, написанных на Groovy.
Соглашения об именах свойств
Обычно рекомендуется, чтобы первые две буквы имени свойства нижний регистр, а для свойств, состоящих из нескольких слов, используется верблюжий регистр. В этих случаях сгенерированные геттеры и сеттеры будут иметь имя, образованное заглавными буквами. имя свойства и добавление
получить
илиустановить префикс
(или опционально «is» для логического геттера). Таким образом,getLength
будет геттером для свойстваlength
, аsetFirstName
— установщиком для свойстваfirstName
.isEmpty
может быть именем метода получения для свойства с именемempty
.Имена свойств, начинающиеся с заглавной буквы, будут иметь геттеры/сеттеры только с добавленным префиксом. Итак, свойство
Foo
разрешен, даже если он не соответствует рекомендуемым соглашениям об именах. Для этого свойства методы доступа будутsetFoo
иgetFoo
. Следствием этого является то, что вам не разрешено иметь свойстваfoo
иFoo
, поскольку у них будут методы доступа с одинаковыми именами.Спецификация JavaBeans делает особый случай для свойств, которые обычно могут быть аббревиатурами. Если первые две буквы имени свойства в верхнем регистре, капитализация не выполняется. (или, что более важно, декапитализация не выполняется при создании имени свойства из имени метода доступа). Итак,
getURL
будет геттером для свойстваURL
.Из-за специальной логики именования свойств «обработка акронимов» в спецификации JavaBeans, преобразование в имя свойства и из него несимметрично. Это приводит к некоторым странным пограничным случаям. Groovy использует соглашение об именах, которое позволяет избежать двусмысленности, которая может показаться немного странной, но был популярен во времена разработки Groovy и остается (пока что) по историческим причинам. Groovy смотрит на вторую букву имени свойства. Если это капитал, имущество считается одно из свойств стиля акронима, и заглавные буквы не используются, в противном случае используется обычная заглавная буква. Хотя мы никогда не рекомендую это , это позволяет вам иметь то, что может показаться «дублирующимся именованным» свойством, например вы можете иметь
aProp
иAProp
илиpNAME
иPNAME
. Получателями будутgetaProp
иgetAProp
, иgetpNAME
иgetPNAME
соответственно.Модификаторы свойства
Мы уже видели, что свойства определяются без модификатора видимости. В общем, любые другие модификаторы, например.
переходный
будет скопирован в поле. Стоит отметить два особых случая:final
, который, как мы видели ранее, предназначен для свойств только для чтения, копируется в резервное поле, но также не приводит к определению установщикаstatic
копируется в резервное поле, но также делает методы доступа статическими
Если вы хотите иметь модификатор типа
final
также перенесены в методы доступа, вы можете написать свои свойства от руки или рассмотреть возможность использования разделенного определения свойства.Аннотации к свойству
Аннотации, в том числе связанные с преобразованиями AST, копируются в резервное поле свойства. Это позволяет преобразовать AST, применимые к полям, для применяться к свойствам, например:
class Animal { интервал нижнегосчета = 0 @Lazy String name = {lower(). toUpperCase() }() Строка ниже () { lowerCount++; 'лень' } } def a = новое животное () утверждать a.lowerCount == 0 (1) утверждать a.name == 'ЛЕНЬ' (2) утверждать a.lowerCount == 1 (3)
1 Подтверждает отсутствие активной инициализации 2 Обычный доступ к собственности 3 Подтверждает инициализацию при доступе к свойству Разделить определение свойства с явным полем поддержки
Синтаксис свойств Groovy является удобным сокращением при разработке вашего класса. следует определенным соглашениям, которые согласуются с общей практикой JavaBean. Если ваш класс не совсем соответствует этим соглашениям, вы, безусловно, можете написать геттер, сеттер и вспомогательное поле длинной рукой, как в Java. Однако Groovy предоставляет возможность разделения определения, которая по-прежнему обеспечивает сокращенный синтаксис с небольшими изменениями в соглашениях. Для определения разделения вы пишете поле и свойство с тем же именем и типом. Только одно из полей или свойств может иметь начальное значение.
Для разделенных свойств аннотации к полю остаются в фоновом поле свойства. Аннотации в части свойства определения копируются в методы получения и установки.
Этот механизм допускает ряд общих вариантов, которые могут пожелать пользователи собственности использовать, если стандартное определение свойства не совсем соответствует их потребностям. Например, если резервное поле должно быть
защищенным
, а незакрытым
:класс HasPropertyWithProtectedField { защищенное имя строки (1) Имя строки (2) }
1 Защищенное резервное поле для свойства name вместо обычного приватного 2 Объявить свойство имени Или тот же пример, но с закрытым для пакета резервным полем:
класс HasPropertyWithPackagePrivateField { Имя строки (1) @PackageScope Имя строки (2) }
1 Объявить свойство имени 2 Закрытое поле пакета для свойства имени вместо обычного закрытого В качестве последнего примера мы можем применить преобразования AST, связанные с методом, или вообще любая аннотация к сеттерам/геттерам, например чтобы аксессоры были синхронизированы:
класс HasPropertyWithSynchronizedAccessorMethods { имя частной строки (1) @Synchronized Имя строки (2) }
1 Поддерживающее поле для свойства имени 2 Объявить свойство имени с аннотацией для установки/получения Явные методы доступа
Автоматическая генерация методов доступа не происходит, если является явным определением геттера или сеттера в классе. Это позволяет вам изменить нормальное поведение такого геттера или сеттера, если это необходимо. Унаследованные методы доступа обычно не рассматриваются, но если унаследованный метод доступа помечен как final, что также не приведет к генерации дополнительный метод доступа для выполнения окончательного требования
4.1. Определение аннотации
Аннотация — это своего рода специальный интерфейс, предназначенный для аннотирования элементов кода. Аннотация — это тип, который superinterface — это интерфейс java.lang.annotation.Annotation. Аннотации объявляются очень аналогично интерфейсам, используя ключевое слово
@interface
:@interface SomeAnnotation {}
Аннотация может определять элементы в форме методов без тел и необязательного значения по умолчанию. возможное типы членов ограничены:
примитивные типы
java.lang. String
java.язык.Класс
и java.lang.Enum
другой java.lang.annotation.Annotation
или любой массив из вышеперечисленных
Например:
@interface SomeAnnotation { Строковое значение() (1) } @interface SomeAnnotation { Строковое значение () по умолчанию «что-то» (2) } @interface SomeAnnotation { интервальный шаг() (3) } @interface SomeAnnotation { Класс применяется к() (4) } @interface SomeAnnotation {} @interface SomeAnnotations { Значение SomeAnnotation[]() (5) } enum DayOfWeek { пн, вт, ср, чт, пт, сб, вс } @интерфейс по расписанию { День недели день недели () (6) }
1 аннотация, определяющая значение
элемент типаString
2 аннотация, определяющая значение Строка
со значением по умолчаниюнечто
3 аннотация, определяющая элемент step
типа примитивного типаint
4 аннотация, определяющая , применяется к члену
типаClass
5 аннотация, определяющая значение
элемент, тип которого является массивом другого типа аннотации6 аннотация, определяющая элемент dayOfWeek
, тип которого является типом перечисленияDayOfWeek
В отличие от языка Java, в Groovy аннотации можно использовать для изменения семантики языка. Это особенно true для преобразований AST, которые будут генерировать код на основе аннотаций.
4.1.1. Размещение аннотации
Аннотацию можно наносить на различные элементы кода:
@SomeAnnotation (1) аннулировать некоторый метод () { // ... } @SomeAnnotation (2) класс SomeClass {} @SomeAnnotation Строка var (3)
1 @SomeAnnotation
применяется к методуsomeMethod
2 @SomeAnnotation
относится кSomeClass
класса3 @SomeAnnotation
применяется к переменнойvar
Чтобы ограничить область применения аннотации, необходимо объявить ее в аннотации определение с использованием аннотации java. lang.annotation.Target. Например, вот как бы вы объявить, что аннотацию можно применить к классу или методу:
импорт java.lang.annotation.ElementType импортировать java.lang.annotation.Target @Target([ElementType.METHOD, ElementType.TYPE]) (1) @interface SomeAnnotation {} (2)
1 аннотация @Target
предназначена для аннотации аннотации с областью действия.2 @SomeAnnotation 9Поэтому 1043 будет разрешен только для
TYPE
илиMETHOD
Список возможных целей доступен в java.lang.annotation.ElementType.
Groovy не поддерживает java.lang.annotation.ElementType#TYPE_PARAMETER и java.lang.annotation.ElementType#TYPE_PARAMETER типы элементов, представленные в Java 8. 4.1.2. Значения элементов аннотации
При использовании аннотации необходимо установить по крайней мере все элементы, не имеющие значения по умолчанию. Например:
Страница @interface { интервальный код состояния () } @Страница (код состояния = 404) недействительным не найдено () { // ... }
Однако можно опустить
value=
в объявлении значения аннотации, если элементvalue
является установлен только один:Страница @интерфейса { Строковое значение() int statusCode() по умолчанию 200 } @Страница(значение='/дом') (1) недействительный дом () { // ... } @Страница('/пользователи') (2) недействительный список пользователей () { // ... } @Страница(значение='ошибка',statusCode=404) (3) недействительным не найдено () { // . .. }
1 мы можем опустить statusCode
, потому что он имеет значение по умолчанию, нозначение
необходимо установить2 , поскольку значение =
3 , если необходимо установить оба значения statusCode
, необходимо использовать значение=
по умолчаниюзначение
член4.1.3. Политика хранения
Видимость аннотации зависит от ее политики хранения. Политика хранения аннотации устанавливается с помощью аннотация java.lang.annotation.Retention:
import java.lang.annotation.Retention импортировать java.lang.annotation.RetentionPolicy @Retention(RetentionPolicy. SOURCE) (1) @interface SomeAnnotation {} (2)
1 аннотация @Retention
аннотирует аннотацию@SomeAnnotation
2 , поэтому @SomeAnnotation
будет иметьSOURCE
сохранениеСписок возможных целей удержания и описание доступны в Перечисление java.lang.annotation.RetentionPolicy. выбор обычно зависит от того, хотите ли вы, чтобы аннотация была видна в время компиляции или время выполнения.
4.1.4. Параметры аннотации замыкания
Интересной особенностью аннотаций в Groovy является то, что замыкание можно использовать в качестве значения аннотации. Следовательно аннотации могут использоваться с широким спектром выражений и при этом иметь поддержку IDE. Например, представьте себе framework, где вы хотите выполнить некоторые методы, основанные на ограничениях среды, таких как версия JDK или ОС. Можно было бы написать следующий код:
class Tasks { Установить результат = [] недействительным всегдаExecuted() { результат << 1 } @OnlyIf({jdk>=6}) пустота supportOnlyInJDK6 () { результат << 'JDK 6' } @OnlyIf({ jdk>=7 && windows }) пустота требуетJDK7AndWindows() { результат << 'JDK 7 Windows' } }
Чтобы аннотация
@OnlyIf
принимала замыкание@Retention(RetentionPolicy.RUNTIME) @интерфейс только если { Значение класса() (1) }
Чтобы завершить пример, давайте напишем пример запуска, который будет использовать эту информацию:
class Runner { статический
T run (Class taskClass) { определение задач = taskClass.newInstance() (1) def params = [jdk: 6, окна: false] (2) tasks. class.declaredMethods.each { m -> (3) if (Modifier.isPublic(m.modifiers) && m.parameterTypes.length == 0) { (4) def onlyIf = m.getAnnotation(OnlyIf) (5) если (только если) { Закрытие cl = onlyIf.value().newInstance(задачи,задачи) (6) кл.делегат = параметры (7) если (кл()) { (8) m.invoke(задачи) (9) } } еще { m.invoke(задачи) (10) } } } задачи (11) } } 1 создать новый экземпляр класса, переданного в качестве аргумента (класс задач) 2 эмулирует среду JDK 6, а не Windows .3 повторить все объявленные методы класса задач 4 , если метод общедоступный и не принимает аргументов 5 попытаться найти аннотацию @OnlyIf
6 , если он найден, получите значение Замыкание
из них7 установить делегата
закрытия в нашу переменную среды8 вызовите замыкание, которое является закрытием аннотации. Он вернет логическое значение .9 если верно
, вызовите метод10 если метод не аннотирован @OnlyIf
, все равно выполнить метод11 после этого вернуть объект задачи Тогда бегун можно использовать следующим образом:
def tasks = Runner. run(Tasks) assert tasks.result == [1, 'JDK 6'] as Set
4.2. Метааннотации
4.2.1. Объявление мета-аннотаций
Мета-аннотации, также известные как псевдонимы аннотаций, — это аннотации, которые заменяются во время компиляции другими аннотациями (одна метааннотация является псевдонимом для одной или нескольких аннотаций). Метааннотации можно использовать для уменьшить размер кода, включающего несколько аннотаций.
Начнем с простого примера. Представьте, что у вас есть
@Service
. и@Transactional
аннотации, и что вы хотите аннотировать курс с обоими:@Service @транзакционный class MyTransactionalService {}
Учитывая количество аннотаций, которые вы можете добавить к одному и тому же классу, метааннотация может помочь, сократив две аннотации до одной, имеющей ту же семантику. Например, вместо этого мы могли бы написать это:
@TransactionalService (1) класс MyTransactionalService {}
1 @TransactionalService
является метааннотациейМета-аннотация объявлена как обычная аннотация, но снабжена аннотацией
@AnnotationCollector
и список аннотаций, которые он собирает. В нашем случае аннотацию@TransactionalService
можно записать:импорт groovy.transform.AnnotationCollector @Service (1) @Транзакционный (2) @AnnotationCollector (3) @interface TransactionalService { }
1 аннотировать мета-аннотацию с помощью @Service
2 аннотировать мета-аннотацию с помощью @Transactional
3 аннотировать мета-аннотацию с помощью @AnnotationCollector
4.2.2. Поведение метааннотаций
Groovy поддерживает как предварительно скомпилированную , так и исходную форму . метааннотации. Это означает, что ваша мета-аннотация может быть предварительно скомпилирован, или вы можете иметь его в том же дереве исходного кода, что и тот, который вы в данный момент компилируются.
ИНФОРМАЦИЯ: Мета-аннотации — это функция только Groovy. Есть у вас нет возможности аннотировать класс Java мета-аннотацией и надеюсь, он будет делать то же самое, что и в Groovy. Точно так же нельзя написать мета-аннотация в Java: использование определения мета-аннотации и должен быть кодом Groovy. Но вы можете с удовольствием собирать аннотации Java и аннотации Groovy в вашей мета-аннотации.
Когда компилятор Groovy встречает класс с аннотацией мета-аннотации, это заменяет на собранные аннотации. Так, в нашем предыдущем примере это будет замените
@TransactionalService
на@Transactional
и@Service
:def annotations = MyTransactionalService.annotations*.annotationType() assert (Сервис в аннотациях) assert (Transactional in annotations)
Преобразование из мета-аннотации в собранные аннотации выполняется во время семантический анализ этап компиляции.
Помимо замены псевдонима собранными аннотациями, метааннотация способна обработка их, включая аргументы.
4.2.3. Параметры мета-аннотаций
Мета-аннотации могут собирать аннотации с параметрами. Чтобы проиллюстрировать это, представим себе две аннотации, каждая из которых принимает один аргумент:
@Timeout(after=3600) @Dangerous(type='explosive')
Предположим, вы хотите создать метааннотацию с именем
@Взрывоопасно
:@Таймаут(после=3600) @Dangerous(type='взрывоопасный') @AnnotationCollector public @interface Explosive {}
По умолчанию при замене аннотаций они получают значения параметра аннотации , как они были определены в псевдониме . Интереснее, мета-аннотация поддерживает переопределение конкретных значений:
@Explosive(after=0) (1) класс Бомба {}
1 значение после
, указанное в качестве параметра@Explosive
, переопределяет значение, определенное в аннотации@Timeout
Если две аннотации определяют одно и то же имя параметра, процессор по умолчанию скопирует значение аннотации во все аннотации, принимающие этот параметр:
@Retention(RetentionPolicy. RUNTIME) публичный @interface Foo { Строковое значение() (1) } @Retention(RetentionPolicy.RUNTIME) общедоступная @интерфейсная панель { Строковое значение() (2) } @Фу @Бар @AnnotationCollector публичный @interface FooBar {} (3) @Фу('а') @Бар('б') класс Боб {} (4) утверждать Bob.getAnnotation(Foo).value() == 'a' (5) println Bob.getAnnotation(Bar).value() == 'b' (6) @FooBar('а') класс Джо {} (7) утверждать Joe.getAnnotation(Foo).value() == 'a' (8) println Joe.getAnnotation(Bar).value() == 'a' (9)
1 аннотация @Foo
определяет значениеСтрока
2 аннотация @Bar
также определяет элементvalue
типаString
3 агрегаты метааннотаций @FooBar
@Foo
и@Bar
4 класс Боб
имеет аннотацию@Foo
и@Bar
5 значение аннотации @Foo
дляBob
равноa
6 , в то время как значение аннотации @Bar
дляBob
равноb
7 класс Джо
аннотирован@FooBar
8 , тогда значение аннотации @Foo
дляJoe
равноa
9 , а значение аннотации .@Bar
дляJoe
также равноa
Во втором случае значение метааннотации было скопировано в обе аннотации
@Foo
и@Bar
.Это ошибка времени компиляции, если собранные аннотации определяют одни и те же элементы. с несовместимыми типами. Например, если в предыдущем примере @Foo
определил значение typeString
, но@Bar
определил значение типаint
.Однако можно настроить поведение метааннотаций и описать, как они собираются аннотации расширены. Вскоре мы рассмотрим, как это сделать, но сначала есть расширенный вариант обработки для покрытия.
4.2.4. Обработка повторяющихся аннотаций в мета-аннотациях
Аннотация
@AnnotationCollector
поддерживает параметр режимаИНФОРМАЦИЯ: Пользовательские процессоры (обсуждаемые далее) могут поддерживать или не поддерживать этот параметр.
Предположим, вы создали метааннотацию, содержащую аннотацию
@ToString
. а затем поместите свою мета-аннотацию в класс, который уже имеет явное@ToString
аннотация. Должно ли это быть ошибкой? Должны ли применяться обе аннотации? Принимает ли приоритет над другими? Нет правильного ответа. В некоторых сценариях это может быть вполне уместно, чтобы любой из этих ответов был правильным. Таким образом, вместо того, чтобы пытаться упредить один правильный способ справиться с проблемой дублирования аннотаций, Groovy позволяет вам напишите свои собственные обработчики мета-аннотаций (см. далее), и давайте напишем любую логику проверки, которая вам нравится в преобразованиях AST, которые часто являются целью для агрегирование. Сказав это, просто установиврежим
, ряд обычно ожидаемые сценарии обрабатываются автоматически для вас в рамках любого дополнительного кодирования. Поведение параметра режимаAnnotationCollectorMode
выбрано значение перечисления, которое сведено в следующую таблицу.Режим
Описание
ДУБЛИКАТ
Аннотации из коллекции аннотаций всегда будут вставляться. После выполнения всех преобразований будет ошибкой наличие нескольких аннотаций (за исключением аннотаций с сохранением SOURCE).
ПРЕДПОЧТИТЕЛЬНЫЙ_СБОРНИК
Аннотации из коллектора будут добавлены, а все существующие аннотации с тем же именем будут удалены.
PREFER_COLLECTOR_MERGED
Аннотации из коллектора будут добавлены, а все существующие аннотации с тем же именем будут удалены, но любые новые параметры, обнаруженные в существующих аннотациях, будут объединены с добавленной аннотацией.
PREFER_EXPLICIT
Аннотации из сборщика будут игнорироваться, если будут найдены какие-либо существующие аннотации с таким же именем.
PREFER_EXPLICIT_MERGED
Аннотации из коллектора будут игнорироваться, если будут найдены какие-либо существующие аннотации с таким же именем, но к существующим аннотациям будут добавлены любые новые параметры в аннотации коллектора.
4.2.5. Пользовательские процессоры мета-аннотаций
Пользовательский процессор аннотаций позволит вам выбрать способ расширения мета-аннотации в собранные аннотации. Поведение мета-аннотации, в этом случае, полностью зависит от вас. Для этого необходимо:
Чтобы проиллюстрировать это, мы рассмотрим, как реализована мета-аннотация
@CompileDynamic
.@CompileDynamic
— метааннотация, которая расширяется на@CompileStatic(TypeCheckingMode. SKIP)
. Проблема в том, что Процессор мета-аннотаций по умолчанию не поддерживает перечисления и значение аннотацииTypeCheckingMode.SKIP
равно единице.Наивная реализация здесь не сработает:
@CompileStatic(TypeCheckingMode.SKIP) @AnnotationCollector public @interface CompileDynamic {}
Вместо этого мы определим его следующим образом:
@AnnotationCollector(processor = "org.codehaus.groovy.transform.CompileDynamicProcessor") public @interface CompileDynamic { }
Первое, что вы можете заметить, это то, что наш интерфейс больше не с аннотацией
@CompileStatic
. Причина этого в том, что мы полагаемся на процессорВот как реализован пользовательский процессор:
CompileDynamicProcessor.groovy
@CompileStatic (1) класс CompileDynamicProcessor расширяет AnnotationCollectorTransform { (2) private static final ClassNode CS_NODE = ClassHelper. make(CompileStatic) (3) private static final ClassNode TC_NODE = ClassHelper.make(TypeCheckingMode) (4) List
визит (сборщик AnnotationNode, (5) AnnotationNode aliasAnnotationUsage, (6) Псевдоним AnnotatedNodeAnnotated, (7) Источник SourceUnit) { (8) узел определения = новый узел аннотации (CS_NODE) (9) def enumRef = новое выражение свойства ( новый ClassExpression(TC_NODE), "SKIP") (10) node.addMember («значение», enumRef) (11) Collections.singletonList(узел) (12) } } 1 наш собственный процессор написан на Groovy, и для лучшей производительности компиляции мы используем статическую компиляцию 2 пользовательский процессор должен расширять org. codehaus.groovy.transform.AnnotationCollectorTransform 3 создать узел класса, представляющий @CompileStatic
тип аннотации4 создать узел класса, представляющий тип перечисления TypeCheckingMode
5 коллектор
— это узел@AnnotationCollector
, найденный в метааннотации. Обычно не используется.6 aliasAnnotationUsage
— расширяемая метааннотация, здесь@CompileDynamic
7 aliasAnnotated
— это узел, аннотируемый мета-аннотацией8 sourceUnit
— это компилируемыйSourceUnit
9 мы создаем новый узел аннотации для @CompileStatic
10 мы создаем выражение эквивалентное TypeCheckingMode. SKIP
11 мы добавляем это выражение в узел аннотации, который теперь равен @CompileStatic(TypeCheckingMode.SKIP)
12 вернуть сгенерированную аннотацию В данном примере метод
посещения
является единственным методом, который необходимо переопределить. Он предназначен для возврата списка узлы аннотации, которые будут добавлены к узлу, аннотированному мета-аннотацией. В этом примере мы возвращаем один, соответствующий@CompileStatic(TypeCheckingMode.SKIP)
.Признаки — это структурная конструкция языка, которая позволяет:
состав поведений
реализация интерфейсов во время выполнения
переопределение поведения
совместимость со статической проверкой/компиляцией типов
Их можно рассматривать как интерфейсов , содержащий обе реализации по умолчанию и , состояние . Признак определяется с помощью
черта
ключевое слово:черта FlyingAbility { (1) String fly() { "Я лечу!" } (2) }
1 объявление признака 2 объявление метода внутри трейта Затем его можно использовать как обычный интерфейс, используя ключевое слово
class Bird реализует FlyingAbility {} (1) def b = новая птица() (2) assert b.fly() == "Я лечу!" (3)
1 Добавляет черту FlyingAbility
кПтица
возможности класса2 создать новую птицу
3 класс Bird
автоматически получает поведение чертыFlyingAbility
Черты предоставляют широкий спектр возможностей, от простой композиции до тестирования, которые подробно описаны в этом разделе.
5.1. Методы
5.1.1. Публичные методы
Объявление метода в свойстве может быть сделано как любой обычный метод в классе:
trait FlyingAbility { (1) String fly() { "Я лечу!" } (2) }
1 объявление признака 2 объявление метода внутри трейта 5.1.2. Абстрактные методы
Кроме того, трейты могут также объявлять абстрактные методы, которые, следовательно, должны быть реализованы в классе, реализующем трейт:
trait Greetable { абстрактное имя строки() (1) Строковое приветствие() { "Здравствуйте, ${name()}!" } (2) }
1 Класс реализациидолжен будет объявить имя
метод2 можно смешивать с бетоном по методу Тогда трейт можно использовать следующим образом:
класс Person реализует Greetable { (1) Имя строки() {'Боб'} (2) } def p = новый человек () assert p. greeting() == 'Привет, Боб!' (3)
1 реализовать трейт Greeable
2 так как имя
было абстрактным, требуется его реализовать3 затем приветствие
можно назвать5.1.3. Частные методы
Черты также могут определять частные методы. Эти методы не будут отображаться в интерфейсе контракта признаков:
черта Приветствующий { private String GreetingMessage () { (1) «Привет от частного метода!» } Строка приветствия () { def m = GreetingMessage() (2) println м м } } класс GreetingMachine реализует Greeter {} (3) def g = новая машина приветствия () assert g. greet() == "Привет от частного метода!" (4) пытаться { утверждать g.greetingMessage() (5) } поймать (MissingMethodException e) { println "greetingMessage является приватным в трейте" }
1 определить закрытый метод GreetingMessage
в признаке2 общедоступное приветствие
вызов сообщенияприветствиеMessage
по умолчанию3 создать класс, реализующий черту 4 приветствовать
можно позвонить5 , но не приветствиеMessage
Трейты поддерживают только public
иprivate
метода. Низащищенные
, нипакетные частные области
не являются поддерживается.5.1.4. Окончательные методы
Если у нас есть класс, реализующий трейт, концептуально реализации из трейт-методов «наследуются» классом. Но на самом деле не существует базового класса, содержащего такие реализации. Скорее, они вплетены непосредственно в класс. Окончательный модификатор метода просто указывает, каким будет модификатор для тканого метода. Хотя, скорее всего, считается плохим стилем наследовать и переопределять или умножать методы наследования с одним и тем же подпись, а сочетание окончательных и не окончательных вариантов, Groovy не запрещает этот сценарий. Применяется обычный выбор метода, и используемый модификатор будет определяться результирующим методом. Вы можете рассмотреть возможность создания базового класса, который реализует желаемые черты, если вы нужны методы реализации трейтов, которые нельзя переопределить.
5.
2. Значение thisthis
представляет собой реализующий экземпляр. Думайте о черте как о суперклассе. Это означает, что когда вы пишете:trait Introspector { Def whoAmI () { это } } класс Foo реализует Introspector {} def foo = new Foo()
, затем вызов:
foo.whoAmI()
вернет тот же экземпляр:
assert foo.whoAmI().is(foo)
5.3. Интерфейсы
Черты могут реализовывать интерфейсы, и в этом случае интерфейсы объявляются с использованием
реализует ключевое слово
: интерфейсИменованный { (1) Имя строки () } черта Приветствуемые инструменты Named { (2) Строковое приветствие() { "Здравствуйте, ${name()}!" } } класс Person реализует Greetable { (3) Имя строки() {'Боб'} (4) } def p = новый человек () assert p. greeting() == 'Привет, Боб!' (5) assert p instanceof Named (6) assert p instanceof Greetable (7)
1 объявление нормального интерфейса 2 добавить Named
в список реализованных интерфейсов3 объявить класс, реализующий черту Greetable
4 реализовать отсутствующее имя
метод5 реализация приветствия 6 убедиться, что человек
реализует интерфейсNamed
7 убедитесь, что человек
реализует чертуGreetable
5.
4. СвойстваЧерта может определять свойства, как в следующем примере:
Черта Named { Имя строки (1) } класс Person реализует Named {} (2) def p = новый человек (имя: «Боб») (3) утверждать p.name == 'Боб' (4) утверждать p.getName() == 'Боб' (5)
1 объявить свойство имя
внутри признака2 объявить класс, реализующий трейт 3 свойство автоматически становится видимым 4 к нему можно получить доступ с помощью обычного средства доступа к свойствам 5 или используя обычный синтаксис геттера 5.
5. Поля5.5.1. Частные поля
Поскольку трейты позволяют использовать частные методы, также может быть интересно использовать закрытые поля для хранения состояния. Черты позволит вам это сделать:
trait Counter { частный счетчик = 0 (1) int count() { count += 1; количество } (2) } класс Foo реализует счетчик {} (3) def f = новый Foo() утверждать f.count() == 1 (4) утверждать f.count() == 2
1 объявить приватное поле количество
внутри признака2 объявить общедоступный метод count
, который увеличивает счетчик и возвращает его3 объявить класс, который реализует черту счетчика
4 метод подсчета может использовать частное поле для сохранения состояния
Это основное отличие от методов виртуального расширения Java 8. В то время как методы виртуального расширения не несут состояния, черты могут. Более того, трейты в Groovy поддерживаются начиная с Java 6, поскольку их реализация не опирается на методы виртуального расширения. Этот означает, что даже если трейт можно увидеть в классе Java как обычный интерфейс, этот интерфейс будет не имеют методы по умолчанию, только абстрактные. 5.5.2. Публичные поля
Публичные поля работают так же, как и частные поля, но во избежание проблемы алмазов, имена полей переназначаются в реализующем классе:
trait Named { общедоступное имя строки (1) } класс Person реализует Named {} (2) def p = новый человек () (3) p.Named__name = 'Боб' (4)
1 объявить публичное поле внутри признака 2 объявить класс, реализующий трейт 3 создать экземпляр этого класса 4 открытое поле доступно, но переименовано Имя поля зависит от полного имени признака. Все точки (
.
) в пакете заменяются символом подчеркивания (_
), а конечное имя включает двойное подчеркивание. Итак, если тип поляString
, имя пакетаmy.package
, имя признакаFoo
и имя полябар
, в реализующем классе открытое поле будет выглядеть так:String my_package_Foo__bar
Хотя трейты поддерживают общедоступные поля, их использование не рекомендуется и считается плохой практикой. 5.6. Состав поведений
Черты можно использовать для контролируемой реализации множественного наследования. Например, у нас могут быть следующие черты:
черта FlyingAbility { (1) String fly() { "Я лечу!" } (2) } черта SpeakingAbility { String speak() { "Я говорю!" } }
И класс, реализующий обе черты:
класс Duck реализует FlyingAbility, SpeakingAbility {} (1) def d = новая утка() (2) assert d. fly() == "Я лечу!" (3) assert d.speak() == "Я говорю!" (4)
1 класс Duck
реализует какFlyingAbility
, так иSpeakingAbility
2 создает новый экземпляр Duck
3 мы можем вызвать метод летать
изFlyingAbility
4 , а также метод говорить
изSpeakingAbility
Черты поощряют повторное использование возможностей объектов и создание новых классов на основе существующего поведения.
5.7. Переопределение методов по умолчанию
Черты предоставляют реализации по умолчанию для методов, но их можно переопределить в реализующем классе. Например, мы можно немного изменить приведенный выше пример, добавив утку, которая крякает:
класс Duck реализует FlyingAbility, SpeakingAbility { Строка кряк() { "Кряк!" } (1) Строка говорить () { шарлатан () } (2) } def d = новая утка() assert d.fly() == "Я лечу!" (3) assert d.quack() == "Кряк!" (4) assert d.speak() == "Кряк!" (5)
1 определить метод, характерный для Duck
, названныйquack
2 переопределяет реализацию по умолчанию говорит
, так что мы используемкрякать
вместо3 утка все еще летит, из реализации по умолчанию 4 шарлатан
происходит отУтка
класса5 говорить
больше не использует реализацию по умолчанию изSpeakingAbility
5.
8. Расширение признаков5.8.1. Простое наследование
Черты могут расширять другие черты, и в этом случае вы должны использовать ключевое слово
extends
:черта Named { Имя строки (1) } черта Вежливое расширение Named { (2) String Introduction() { "Здравствуйте, я $name" } (3) } класс Person реализует вежливость {} def p = новый человек (имя: «Алиса») (4) assert p.introduce() == 'Привет, я Алиса' (5)
1 черта Named
определяет однуимя
собственность2 Вежливый
Черта РасширяетИменованный
Черта3 Вежливый
добавляет новый метод, который имеет доступ кимени
свойству суперпризнака4 свойство name
видно изPerson
класс реализацииВежливый
5 как ввести метод
5.
8.2. Множественное наследованиеВ качестве альтернативы признак может распространяться на несколько признаков. В этом случае все супер-черты должны быть объявлены в инструментах
черта WithId { (1) Длинный идентификатор } черта WithName { (2) Имя строки } Идентифицированная черта реализует WithId, WithName {} (3)
1 Признак WithId
определяет свойствоid
2 Черта WithName
определяет свойствоимя
3 Выявлено
— это черта, которая наследует какWithId
, так иWithName
5.9. Типирование и признаки уток
5.
9.1. Динамический кодЧерты могут вызывать любой динамический код, например обычный класс Groovy. Это означает, что вы можете в теле метода вызвать методы, которые должны существовать в реализующем классе, без необходимости явно объявлять их в интерфейсе. Это означает, что черты полностью совместимы с утиным типированием:
черта SpeakingDuck { Строка говорить() {шарлатанство()} (1) } класс Duck реализует SpeakingDuck { Строковый методMissing (имя строки, аргументы) { "${name.capitalize()}!" (2) } } def d = новая утка() assert d.speak() == 'Кря!' (3)
1 SpeakingDuck
ожидает, что методquack
будет определен2 класс Duck
реализует метод с использованием methodMissing3 вызов метода speak
вызывает вызовquack
, который обрабатывается методомMissing
5.
9.2. Динамические методы в трейтеТакже возможно, что трейт реализует методы MOP, такие как
methodMissing
илиpropertyMissing
, в этом случае реализация классов будет наследовать поведение от типажа, как в этом примере:trait DynamicObject { (1) личные реквизиты карты = [:] def methodMissing (имя строки, аргументы) { имя.toUpperCase() } def propertyMissing (имя строки) { props.get(имя) } void setProperty (имя строки, значение объекта) { props.put(имя, значение) } } класс Dynamic реализует DynamicObject { Строка existsProperty = 'ok' (2) Строка existsMethod() {'ok'} (3) } def d = новый динамический() утверждать d.existingProperty == 'ok' (4) утверждать d.foo == null (5) d.foo = 'бар' (6) утверждать d. foo == 'bar' (7) утверждать d.existingMethod() == 'хорошо' (8) утверждать d.someMethod() == 'НЕКОТОРЫЙ МЕТОД' (9)
1 создать трейт, реализующий несколько методов MOP 2 класс Dynamic
определяет свойство3 класс Dynamic
определяет метод4 вызов существующего свойства приведет к вызову метода из Dynamic
5 вызов несуществующего свойства вызовет метод из трейта 6 вызовет .setProperty
, определенный в свойстве7 вызовет getProperty
определено по признаку8 вызов существующего метода на Dynamic
9 , но вызывает несуществующий метод благодаря трейту methodMissing
5.
10. Конфликты множественного наследования5.10.1. Разрешение конфликтов по умолчанию
Класс может реализовывать несколько признаков. Если какой-то трейт определяет метод с той же сигнатурой, что и метод в другом трейте, у нас конфликт:
признак А { Строка exec() {'A'} (1) } черта Б { Строка exec() {'B'} (2) } класс C реализует A, B {} (3)
1 trait A
определяет метод с именемexec
, возвращающийString
2 черта B
определяет тот же самый метод3 класс C
реализует обе чертыВ этом случае поведение по умолчанию заключается в том, что метод из последней объявленной черты в
реализует предложение
. ЗдесьB
объявлен послеA
, поэтому будет выбран метод изB
:def c = new C() утверждать c.exec() == 'B'
5.10.2. Разрешение конфликтов пользователей
В случае, если это не то поведение, которое вам нужно, вы можете явно выбрать, какой метод вызывать, используя синтаксис
Trait.super.foo
. В приведенном выше примере мы можем обеспечить вызов метода из типажа A, написав следующее:класс C реализует A,B { Строка exec() { A.super.exec() } (1) } защита с = новый C () утверждать c.exec() == 'A' (2)
1 явный вызов exec
из типажаA
2 вызывает версию из A
вместо использования разрешения по умолчанию, которое было бы изB
5.
11. Реализация трейтов во время выполнения5.11.1. Реализация трейта во время выполнения
Groovy также поддерживает динамическую реализацию трейтов во время выполнения. Он позволяет вам «украсить» существующий объект с помощью черта. В качестве примера начнем с этой черты и следующего класса:
признак Дополнительный { String extra() { "Я дополнительный метод" } (1) } класс Нечто { (2) Строка doSomething() {'Что-то'} (3) }
1 признак Extra
определяет методextra
2 класс Что-то
делает , а не реализует чертуExtra
3 Something
определяет только методdoSomething
Тогда, если мы сделаем:
def s = new Something() s. extra()
вызов extra завершится ошибкой, потому что
Что-то
не реализуетExtra
. Это можно сделать во время выполнения с помощью следующий синтаксис:def s = new Something() as Extra (1) с.экстра() (2) s.doSomething() (3)
1 использование ключевого слова as для приведения объекта к признаку во время выполнения 2 затем экстра
можно позвонить на объект3 и doSomething
по-прежнему вызываетсяПри приведении объекта к трейту результат операции не является одним и тем же экземпляром. Это гарантировано что принудительный объект будет реализовывать как черты , так и интерфейсы, которые реализует исходный объект, но результат будет , а не быть экземпляром исходного класса. 5.11.2. Реализация нескольких трейтов одновременно
Если вам нужно реализовать несколько трейтов одновременно, вы можете использовать метод
withTraits
вместо ключевого словаas
:trait A { void methodFromA() {} } черта B { void methodFromB() {} } класс С {} защита с = новый C () c.methodFromA() (1) c.methodFromB() (2) def d = c.withTraits A, B (3) d.methodFromA() (4) d.methodFromB() (5)
1 вызов methodFromA
завершится ошибкой, посколькуC
не реализуетA
2 вызов methodFromB
завершится ошибкой, посколькуC
не реализуетB
3 withTrait
превратитc
во что-то, что реализуетA
иB
4 methodFromA
теперь будет проходить, потому чтоd
реализуетA
5 метод FromB
теперь пройдет, потому чтоd
также реализуетB
При приведении объекта к нескольким признакам результат операции не является одним и тем же экземпляром. Это гарантировано что принудительный объект будет реализовывать как свойства , так и интерфейсы, которые реализует исходный объект, но результатом будет , а не экземпляр исходного класса. 5.12. Поведение цепочки
Groovy поддерживает концепцию стекируемых признаков . Идея состоит в том, чтобы делегировать от одного трейта другому, если текущий трейт не может обработать сообщение. Чтобы проиллюстрировать это, давайте представим такой интерфейс обработчика сообщений:
interface MessageHandler { void on(строковое сообщение, полезная нагрузка карты) }
Затем вы можете составить обработчик сообщений, применив небольшое поведение. Например, давайте определим обработчик по умолчанию в форма признака:
Черта DefaultHandler реализует MessageHandler { void on (строковое сообщение, полезная нагрузка карты) { println "Получено $сообщение с полезной нагрузкой $payload" } }
Тогда любой класс может унаследовать поведение обработчика по умолчанию, реализуя трейт:
class SimpleHandler реализует DefaultHandler {}
А что, если вы хотите регистрировать все сообщения в дополнение к обработчику по умолчанию? Один из вариантов — написать так:
класс SimpleHandlerWithLogging реализует DefaultHandler { void on(строковое сообщение, полезная нагрузка карты) { (1) println "Вижу $message с полезной нагрузкой $payload" (2) DefaultHandler. super.on(сообщение, полезная нагрузка) (3) } }
1 явно реализует метод на
2 выполнить регистрацию 3 продолжить, делегировав DefaultHandler
признакЭто работает, но у этого подхода есть недостатки:
логическая логика привязана к "конкретному" обработчику
у нас есть явная ссылка на
DefaultHandler
в методена
, что означает, что если мы изменим черту, которую реализует наш класс, код будет нарушен
В качестве альтернативы мы можем написать еще один трейт, ответственность которого ограничена логированием: void on (строковое сообщение, полезная нагрузка карты) { println "Вижу $message с полезной нагрузкой $payload" (2) super. on(сообщение, полезная нагрузка) (3) } }
1 обработчик регистрации сам является обработчиком 2 печатает полученное сообщение 3 , затем .super
позволяет делегировать вызов следующему признаку в цепочкеТогда наш класс можно переписать следующим образом:
класс HandlerWithLogger реализует DefaultHandler, LoggingHandler {} def loggingHandler = новый HandlerWithLogger() loggingHandler.on('тестовая регистрация', [:])
, который напечатает:
Просмотр тестового журнала с полезной нагрузкой [:] Получено тестовое протоколирование с полезной нагрузкой [:]
Поскольку правила приоритета подразумевают, что
LoggerHandler
выигрывает, поскольку он объявлен последним, то вызовна
будет использовать реализация изLoggingHandler
. Но у последнего есть вызовсупер
, что означает следующий трейт в цепь. Здесь следующая черта —DefaultHandler
, поэтому и будет называться:Интересность этого подхода станет более очевидной, если мы добавим третий обработчик, отвечающий за обработку сообщений которые начинаются с
, скажем,
:черта SayHandler реализует MessageHandler { void on (строковое сообщение, полезная нагрузка карты) { если (сообщение.startsWith("сказать")) { (1) println "Я говорю ${message - 'say'}!" } еще { super.on(сообщение, полезная нагрузка) (2) } } }
1 предварительное условие обработчика 2 если предварительное условие не выполнено, передать сообщение следующему обработчику в цепочке Тогда наш окончательный обработчик выглядит следующим образом:
класс Handler реализует DefaultHandler, SayHandler, LoggingHandler {} def h = новый обработчик() h. on('foo', [:]) h.on('привет', [:])
Что означает:
сообщения сначала будут проходить через обработчик протоколирования
обработчик ведения журнала вызывает
супер
, который делегирует следующему обработчику, который являетсяSayHandler
, если сообщение начинается с
., скажем,
, то обработчик использует сообщение, если нет,
говорит, что обработчик
делегирует следующему обработчику в цепочке
Этот подход очень эффективен, потому что он позволяет вам писать обработчики, которые не знают друг друга, но при этом позволяют вам комбинируйте их в нужном вам порядке. Например, если мы выполним код, он напечатает:
Видя foo с полезной нагрузкой [:] Получен foo с полезной нагрузкой [:] Увидев sayHello с полезной нагрузкой [:] Я говорю привет!
, но если мы переместим обработчик ведения журнала вторым в цепочке, результат будет другим:
класс AlternateHandler реализует DefaultHandler, LoggingHandler, SayHandler {} h = новый альтернативный обработчик() h. on('foo', [:]) h.on('привет', [:])
отпечатков:
Видение foo с полезной нагрузкой [:] Получен foo с полезной нагрузкой [:] Я говорю привет!
Причина в том, что теперь, поскольку
SayHandler
потребляет сообщение без вызоваsuper
, обработчик регистрации больше не звонил.5.12.1. Семантика super внутри трейта
Если класс реализует несколько трейтов и обнаружен вызов неквалифицированного
super
, тогда:, если класс реализует другой трейт, вызов делегируется следующему трейту в цепочке
, если в цепочке не осталось признаков,
super
относится к суперклассу реализующего класса ( this )
Например, благодаря этому поведению можно декорировать окончательные классы:
trait Filtering { (1) Добавление StringBuilder (String str) { (2) def subst = str. replace('o','') (3) super.append(subst) (4) } Строка toString() { super.toString() } (5) } def sb = new StringBuilder(). withTraits Фильтрация (6) sb.append('Крутой') assert sb.toString() == 'Grvy' (7)
1 определить черту с именем Фильтрация
, которая должна применяться кStringBuilder
во время выполнения2 переопределить добавить метод
3 удалить все o из строки 4 затем делегируйте super
5 в случае 9Вызывается 1042 toString , делегируйте super. toString
6 реализация черты Filtering
во время выполнения в экземпляре StringBuilder7 добавленная строка больше не содержит буквы o
В этом примере, когда встречается
super.append
, в целевом объекте нет другого признака, поэтому метод, который называется, является оригинальнымдобавить метод
, то есть метод изStringBuilder
. Тот же трюк используется дляtoString
, так что строковое представление сгенерированного прокси-объекта делегируетtoString
экземпляраStringBuilder
.5.13. Дополнительные функции
5.13.1. Приведение типа SAM
Если признак определяет один абстрактный метод, он является кандидатом на приведение типа SAM (Single Abstract Method). Например, представьте себе следующую черту:
черта Приветствующий { Приветствие строки () { "Привет $ имя"} (1) абстрактная строка getName() (2) }
1 метод приветствия
не является абстрактным и вызывает абстрактный методgetName
2 getName
— абстрактный методС 9 лет1042 getName is the single abstract method in the
Greeter
trait, you can write:Greeter greeter = { 'Alice' } (1)
1 замыкание «становится» реализацией getName
одного абстрактного методаили даже:
void приветствие(приветствие g) { println g. greet() } (1) приветствовать { 'Алиса' } (2)
1 метод приветствия принимает приветствие типа SAM в качестве параметра 2 мы можем вызвать его напрямую с закрытием 5.13.2. Различия с методами Java 8 по умолчанию
В Java 8 интерфейсы могут иметь реализацию методов по умолчанию. Если класс реализует интерфейс и не предоставляет реализация для метода по умолчанию, то выбирается реализация из интерфейса. Черты ведут себя одинаково, но с большим отличием: реализация от трейта равна всегда используется , если класс объявляет трейт в своем интерфейсе. перечислите и , что он не обеспечивает реализацию даже , если суперкласс это делает.
Эту функцию можно использовать для очень точного составления поведения, если вы хотите переопределить поведение уже реализованный метод.
Чтобы проиллюстрировать концепцию, давайте начнем с этого простого примера:
import groovy.test.GroovyTestCase импорт groovy.transform.CompileStatic импортировать org.codehaus.groovy.control.CompilerConfiguration импортировать org.codehaus.groovy.control.customizers.ASTTransformationCustomizer импортировать org.codehaus.groovy.control.customizers.ImportCustomizer класс SomeTest расширяет GroovyTestCase { защитная конфигурация защитная оболочка недействительная установка () { config = новая конфигурация компилятора () оболочка = новая GroovyShell (конфигурация) } недействительным testSomething () { утверждать shell.evaluate('1+1') == 2 } void otherTest() { /* ... */ } }
В этом примере мы создаем простой тестовый пример, который использует два свойства ( config и shell ) и использует те из них, несколько методов тестирования. Теперь представьте, что вы хотите протестировать то же самое, но с другой конфигурацией компилятора. Одним из вариантов является создание подкласса
SomeTest
:class AnotherTest extends SomeTest { недействительная установка () { config = новая конфигурация компилятора () config.addCompilationCustomizers( ... ) оболочка = новая GroovyShell (конфигурация) } }
Это работает, но что, если у вас на самом деле есть несколько тестовых классов и вы хотите протестировать новую конфигурацию для всех эти тестовые классы? Тогда вам придется создать отдельный подкласс для каждого тестового класса:
class YetAnotherTest extends SomeTest { недействительная установка () { config = новая конфигурация компилятора () config.addCompilationCustomizers( ... ) оболочка = новая GroovyShell (конфигурация) } }
Тогда вы видите, что
установка
метод обоих тестов одинаков. Идея состоит в том, чтобы создать трейт:трейт MyTestSupport { недействительная установка () { config = новая конфигурация компилятора () config. addCompilationCustomizers (новый ASTTransformationCustomizer (CompileStatic)) оболочка = новая GroovyShell (конфигурация) } }
Затем используйте его в подклассах:
класс AnotherTest расширяет SomeTest, реализует MyTestSupport {} класс YetAnotherTest расширяет SomeTest2, реализует MyTestSupport {} ...
Это позволило бы нам значительно сократить шаблонный код и снизить риск того, что вы забудете изменить настройку. код на случай, если мы решим его изменить. Даже если установка
Эта функция особенно полезна, когда у вас нет доступа к исходному коду суперкласса. Его можно использовать для фиктивные методы или форсировать конкретную реализацию метода в подклассе. Это позволяет вам реорганизовать код, чтобы сохранить переопределить логику в одном трейте и наследовать новое поведение, просто реализовав его. Альтернатива, конечно это переопределить метод в через каждые мест, где вы бы использовали новый код.
Стоит отметить, что если вы используете трейты времени выполнения, методы из трейта всегда предпочтительнее, чем методы из проксируемого объект: класс Лицо { Имя строки (1) } черта Боб { Строка getName() { 'Боб' } (2) } def p = новый человек (имя: «Алиса») утверждать p.name == 'Алиса' (3) def p2 = p как Боб (4) утверждать p2.name == 'Боб' (5)
1 класс Person
определяет свойствоname
, результатом которого являетсяgetName
метод2 Bob
— это трейт, определяющийgetName
как возвращающийBob
3 объект по умолчанию вернет Алиса 4 p2
преобразуетp
вBob
во время выполнения5 getName
возвращает Bob , потому чтоgetName
берется из типажаОпять же, не забывайте, что приведение динамического типажа возвращает отдельный объект, который реализует только исходный объект. интерфейсы, а также черты. 5.14. Различия с примесями
Существует несколько концептуальных отличий примесей, поскольку они доступны в Groovy. Обратите внимание, что речь идет о миксины времени выполнения, а не аннотация @Mixin, которая устарела в пользу трейтов.
Прежде всего, методы, определенные в трейте, видны в байт-коде:
внутренне трейт представлен в виде интерфейса (без методов по умолчанию или статических методов) и нескольких вспомогательных классов
это означает, что объект, реализующий трейт, эффективно реализует интерфейс
эти методы видны из Java
они совместимы с проверкой типов и статической компиляцией
Методы, добавленные через миксин, наоборот, видны только во время выполнения:
class A { String methodFromA() { 'A' } } (1) класс B { String methodFromB() { 'B' } } (2) A. metaClass.mixin B (3) защита о = новый A () утверждать o.methodFromA() == 'A' (4) утверждать o.methodFromB() == 'B' (5) утверждение экземпляра A (6) утверждать !(o экземпляр B) (7)
1 class A
определяетmethodFromA
2 класс B
определяетmethodFromB
3 миксин B в A 4 мы можем вызвать methodFromA
5 мы также можем вызвать methodFromB
6 объект является экземпляром A
7 , но это , а не экземпляр B
Последний пункт на самом деле очень важен и иллюстрирует место, где миксины имеют преимущество перед трейтами: экземпляры изменены , а не , поэтому, если вы смешиваете какой-либо класс с другим, третий класс не генерируется, а методы, которые реагируют на А будет продолжать отвечать на А, даже если его смешали.
5.15. Статические методы, свойства и поля
Следующие инструкции требуют осторожности. Поддержка статических элементов находится в стадии разработки и все еще является экспериментальной. приведенная ниже информация действительна только для версии 4.0.0. В трейте можно определить статические методы, но это имеет ряд ограничений:
Трейты со статическими методами нельзя компилировать статически или проверять тип. Все статические методы, Доступ к свойствам и полям осуществляется динамически (это ограничение JVM).
Статические методы не отображаются в сгенерированных интерфейсах для каждого трейта.
Признак интерпретируется как шаблон для класса реализации, что означает, что каждый реализующий класс получит свои собственные статические методы, свойства и поля. Итак, статический член объявленный в свойстве, принадлежит не свойству
Обычно не следует смешивать статические методы и методы экземпляра одной и той же сигнатуры. Нормальный применяются правила применения признаков (включая разрешение конфликтов множественного наследования). Если выбранный метод является статическим, но некоторые реализованные трейты имеют вариант экземпляра, ошибка компиляции произойдет. Если выбранный метод является вариантом экземпляра, статический вариант будет проигнорирован. (поведение аналогично статическим методам в интерфейсах Java для этого случая).
Начнем с простого примера:
trait TestHelper { общественное статическое логическое значение CALLED = false (1) статическая пустота init () { (2) ВЫЗВАН = правда (3) } } класс Foo реализует TestHelper {} Foo.init() (4) утверждать Foo.TestHelper__CALLED (5)
1 статическое поле объявлено в трейте 2 статический метод также объявлен в трейте 3 статическое поле обновлено внутри черта 4 статический метод init доступен для класса реализации 5 статическое поле переназначено , чтобы избежать проблемы с алмазом Как обычно, не рекомендуется использовать публичные поля. В любом случае, если вы хотите этого, вы должны понимать, что следующий код не сработает:
Foo.CALLED = true
, потому что нет статического поля CALLED , определенного в самом признаке. Аналогично, если у вас есть два разных класса реализации, каждый из них получает отдельное статическое поле:
класс Bar реализует TestHelper {} (1) класс Baz реализует TestHelper {} (2) Бар.инит() (3) утверждать Bar.TestHelper__CALLED (4) утверждать !Baz.TestHelper__CALLED (5)
1 класс Бар
реализует черту2 class Baz
также реализует трейт3 init
вызывается только наBar
4 статическое поле CALLED
наБар
обновлено5 но статическое поле CALLED
наБаза
нет, потому что различима5.
16. Наследование состояний подводных камнейМы видели, что трейты имеют состояние. Для признака возможно определить поля или свойства, но когда класс реализует признак, он получает эти поля/свойства на основе для каждого признака. Итак, рассмотрим следующий пример:
trait IntCouple { х = 1 интервал у = 2 целая сумма() {х+у} }
Черта определяет два свойства,
x
иy
, а также метод суммированияclass BaseElem реализует IntCouple { интервал f() { сумма() } } База защиты = новый БазовыйЭлем() assert base.f() == 3
Результатом вызова
f
будет3
, потому чтоf
делегируетсумму
в свойстве, которое имеет состояние. Но что, если мы напишем это вместо этого?класс Elem реализует IntCouple { число х = 3 (1) инт у = 4 (2) int f() { сумма() } (3) } def elem = новый элемент()
1 Переопределить свойство x
2 Переопределить свойство y
3 Вызов сумма
из признакаЕсли вы вызовете
elem. f()
, каков ожидаемый результат? На самом деле это:assert elem.f() == 3
Причина в том, что метод
sum
обращается к полям признака. Таким образом, он использует значенияx
иy
, определенные в черте. Если вы хотите использовать значения из реализующего класса, вам необходимо разыменовать поля с помощью геттеры и сеттеры, как в этом последнем примере:признак IntCouple { х = 1 интервал у = 2 целая сумма() {получитьX()+получитьY()} } класс Elem реализует IntCouple { число х = 3 интервал у = 4 интервал f() { сумма() } } деф элемент = новый элемент () утверждать elem.f() == 7
5.17. Самотипы
5.17.1. Ограничения типа для трейтов
Иногда вам может понадобиться написать трейт, который можно применить только к некоторому типу. Например, вы можете захотеть применить trait в классе, который расширяет другой класс, который находится вне вашего контроля, и по-прежнему иметь возможность вызывать эти методы. Чтобы проиллюстрировать это, давайте начнем с этого примера:
класс CommunicationService { static void sendMessage(String from, String to, String message) { (1) println "$from отправил [$message] в $to" } } класс устройства { идентификатор строки } (2) черта Общение { void sendMessage (устройство для, строковое сообщение) { CommunicationService.sendMessage(id, to.id, сообщение) (3) } } класс MyDevice расширяет устройство, реализующее связь {} (4) def bob = новый MyDevice (id: 'Боб') def alice = новый MyDevice (id: 'Алиса') bob.sendMessage(alice,'secret') (5)
1 Класс Service
, не зависящий от вас (в библиотеке, …), определяетsendMessage
метод2 A Класс устройства
, вне вашего контроля (в библиотеке, …)3 Определяет коммуникационную характеристику для устройств, которые могут вызывать службу 4 Определяет MyDevice
как устройство связи5 Вызывается метод из типажа, и id
разрешаетсяЗдесь ясно, что черта
Общение
может применяться только кУстройству
. Однако нет явного контракт, чтобы указать это, потому что черты не могут расширять классы. Тем не менее, код компилируется и работает отлично хорошо, потому чтоid
в методе типажа будет разрешаться динамически. Проблема в том, что нет ничего, что предотвращает применение черты к любому классу, который равен не аУстройство
. Любой класс с идентификаторомid
будет работать, в то время как любой класс, не имеющий свойстваid
, вызовет ошибку времени выполнения.Проблема еще более усложняется, если вы хотите включить проверку типов или применить
@CompileStatic
к свойству: потому что трейт ничего не знает о себе, будучи устройствомid
.Одна из возможностей — явно добавить метод
getId
в трейт, но это не решит всех проблем. Что, если метод требуетэто
в качестве параметра, и на самом деле требует, чтобы это былоУстройство
?класс SecurityService { static void check(Device d) { if (d. id==null) throw new SecurityException() } }
Если вы хотите иметь возможность вызвать
это
в трейте, то вам нужно будет явно привестиэто
в Устройствок этому
везде.5.17.2. Аннотация @SelfType
Чтобы сделать этот контракт явным и чтобы программа проверки типов знала о своем типе , Groovy предоставляет аннотацию
@SelfType
, которая будет:Итак, в нашем предыдущем примере мы можем исправить черту, используя аннотацию
@groovy.transform.SelfType
:@SelfType(Устройство) @CompileStatic черта Общение { void sendMessage (устройство для, строковое сообщение) { SecurityService.check(это) CommunicationService.sendMessage(id, to.id, сообщение) } }
Теперь, если вы попытаетесь реализовать эту черту в классе, который является , а не устройством, произойдет ошибка времени компиляции:
класс MyDevice реализует связь {} // забыли расширить устройство
Ошибка будет :
класс «MyDevice» реализует трейт «Communicating», но не расширяет класс self-type «Device»
В заключение, self-типы — это мощный способ объявления ограничений на трейты без объявления контракта непосредственно в трейте или везде использовать приведения типов, сохраняя разделение задач настолько жестким, насколько это должно быть. 2 двойная высота = 1d двойная площадь = Math.PI * 0,5d**2 } утвердить новый UnitCube().volume == 1d утвердить новый UnitCylinder().volume == 0,78539816333d
1 Все варианты использования признака HasVolume
должны реализовывать или расширять какHasHeight
, так иHasArea
2 Только UnitCube
илиUnitCylinder
могут использовать признакДля вырожденного случая, когда один класс реализует трейт, например:
final class Foo реализует FooTrait {}
Затем либо:
@SelfType(Foo) черта FooTrait {}
или:
@Sealed(permittedSubclasses='Foo') (1) черта FooTrait {}
1 Или просто @Sealed
, еслиFoo
иFooTrait
находятся в одном исходном файлеможет выразить это ограничение. Как правило, предпочтение отдается первому из них.
5.18. Ограничения
5.18.1. Совместимость с преобразованиями AST
Признаки официально не совместимы с преобразованиями AST. Некоторые из них, такие как @CompileStatic
, будут применены на самом трейте (не на реализующих классах), в то время как другие будут применяться как к реализующему классу, так и к трейту. Нет абсолютно никакой гарантии, что трансформация AST будет работать с трейтом так же, как с обычным классом, поэтому используйте ее. на свой страх и риск!5.18.2. Префиксные и постфиксные операции
Внутри трейтов префиксные и постфиксные операции не разрешены, если они обновляют поле трейта:
трейт Подсчет { целое число х недействительным вкл () { х++ (1) } недействительным dec () { --x (2) } } класс Counter реализует подсчет {} def c = новый счетчик() c. inc()
1 x
определено в свойстве, инкремент постфикса не разрешен2 x
определяется в свойстве, декремент префикса не разрешенВ качестве обходного пути можно использовать оператор
+=
.Классы записей, или записи для краткости, являются особым видом класса. полезно для моделирования простых агрегатов данных. Они обеспечивают компактный синтаксис с меньшими церемониями, чем обычные классы. В Groovy уже есть преобразования AST, такие как
@Immutable
и@Canonical
которые уже резко сокращают церемонию, но записи были представленные в Java, и классы записи в Groovy предназначены для согласования с классами записи Java.Например, предположим, что мы хотим создать запись
Сообщение
представляющий сообщение электронной почты. Для целей этого примера давайте упростим такое сообщение, чтобы оно содержало только адрес электронной почты из , адрес электронной почты с по и сообщение кузов . Мы можем определить такие запись следующим образом:Record Message(String from, String to, String body) { }
Мы будем использовать класс записи так же, как обычный класс, как показано ниже:
def msg = новое сообщение ('[email protected]', '[email protected]', 'Привет!') assert msg.toString() == 'Message[[email protected], [email protected], body=Hello!]'
Сокращенная церемония избавляет нас от определения явных полей, геттеров и
toString
,равно
иметодам hashCode
. На самом деле это сокращение для следующего приблизительного эквивалента:final class Message extends Record { частная окончательная строка из частная окончательная строка для частное конечное тело строки частный статический окончательный длинный serialVersionUID = 0 /* конструктор(ы) */ окончательная строка toString() { /*. ..*/ } final boolean equals(Object other) { /*...*/ } окончательный int hashCode() { /*...*/ } Строка из () { из } // другие геттеры ... }
Обратите внимание на специальное соглашение об именах для средств получения записей. Они имеют то же имя, что и поле (вместо часто распространенного соглашения JavaBean о написании с заглавной буквы с префиксом «получить»). Вместо того, чтобы ссылаться на поля или свойства записи, термин компонент обычно используется для записей. Таким образом, наша запись
Message
содержитиз
,по
итела
компонентов.Как и в Java, вы можете переопределить обычно неявно предоставляемые методы написав свое:
запись Point3D(int x, int y, int z) { Строка toString() { "Point3D[координаты=$x,$y,$z]" } } assert new Point3D(10, 20, 30).toString() == 'Point3D[coords=10,20,30]'
Вы также можете использовать дженерики с записями обычным способом. Например, рассмотрим следующее определение записи
Coord
:запись Coord
(T v1, T v2){ double distFromOrigin() { Math.sqrt(v1()**2 + v2()**2 как двойной) } } Можно использовать следующим образом:
def r1 = new Coord
(3, 4) утверждать r1.distFromOrigin() == 5 def r2 = новый Coord (6d, 2.5d) утверждать r2.distFromOrigin() == 6.5d 6.1. Специальные функции записи
6.1.1. Компактный конструктор
Записи имеют неявный конструктор. Это можно переопределить обычным способом предоставив свой собственный конструктор - вам нужно убедиться, что вы установили все поля если вы сделаете это. Однако для краткости можно использовать компактный синтаксис конструктора, где часть объявления параметра обычного конструктора опущена. Для этого особого случая по-прежнему предоставляется обычный неявный конструктор. но дополняется предоставленными операторами в определении компактного конструктора:
общедоступная запись Предупреждение (строковое сообщение) { общественное предупреждение { Objects. requireNonNull(сообщение) сообщение = сообщение.toUpperCase() } } def w = новое предупреждение («Помощь») утверждать w.message() == 'ПОМОЩЬ'
6.1.2. Сериализуемость
Groovy собственные записи следуют за специальные соглашения для сериализуемости, которые применяются к записям Java. Классы Groovy , подобные записи (обсуждаемые ниже), следуют обычным соглашениям о сериализации классов Java.
6.2. Усовершенствования Groovy
6.2.1. Аргументы по умолчанию
Groovy поддерживает значения по умолчанию для аргументов конструктора. Эта возможность также доступна для записей, как показано в следующем определении записи. который имеет значения по умолчанию для
y
иcolor
:Record ColoredPoint(int x, int y = 0, String color = 'white') {}
Аргументы, если их не использовать (удаление одного или нескольких аргументов справа ) заменены со значениями по умолчанию, как показано в следующем примере:
утвердить новый ColoredPoint(5, 5, 'черный'). toString() == 'ColoredPoint[x=5, y=5, color=black]' утвердить новый ColoredPoint(5, 5).toString() == 'ColoredPoint[x=5, y=5, color=white]' assert new ColoredPoint(5).toString() == 'ColoredPoint[x=5, y=0, color=white]'
Эта обработка следует обычным соглашениям Groovy для аргументов по умолчанию для конструкторов, по существу автоматически предоставляя конструкторам следующие подписи:
ColoredPoint(int, int, String) ColoredPoint(целое, целое) КолоредПойнт (целое число)
Также могут использоваться именованные аргументы (здесь также применяются значения по умолчанию):
утверждать новую ColoredPoint(x: 5).toString() == 'ColoredPoint[x=5, y=0, color=white]' утверждать новый ColoredPoint(x: 0, y: 5).toString() == 'ColoredPoint[x=0, y=5, color=white]'
Вы можете отключить обработку аргументов по умолчанию, как показано здесь:
@TupleConstructor (режим по умолчанию = режим по умолчанию. ВЫКЛ) запись ColoredPoint2 (int x, int y, цвет строки) {} утвердить новый ColoredPoint2(4, 5, 'красный').toString() == 'ColoredPoint2[x=4, y=5, цвет=красный]'
Будет создан один конструктор по умолчанию в Java. Будет ошибкой, если вы отбросите аргументы в этом сценарии.
Вы можете принудительно установить для всех свойств значения по умолчанию, как показано здесь:
@TupleConstructor(defaultsMode=DefaultsMode.ON) запись ColoredPoint3 (int x, int y = 0, цвет строки = «белый») {} assert new ColoredPoint3(y: 5).toString() == 'ColoredPoint3[x=0, y=5, color=white]'
Любому свойству/полю без явного начального значения будет присвоено значение по умолчанию для аргумента тип (null или ноль/false для примитивов).
6.2.2. Декларативное
toString
настройкаКак и в Java, вы можете настроить метод
toString
записи, написав свой собственный. Если вы предпочитаете более декларативный стиль, вы можете использовать преобразование Groovy@ToString
. чтобы заменить запись по умолчаниюна строку
. Например, вы можете записать трехмерную точку следующим образом:package threed импорт groovy.transform.ToString @ToString(ignoreNulls=true, cache=true, includeNames=true, leftDelimiter='[', rightDelimiter=']', nameValueSeparator='=') точка записи (целое число x, целое число y, целое число z = null) { } утверждать новую точку (10, 20).toString() == 'threed.Point[x=10, y=20]'
Мы настраиваем
toString
, включая имя пакета (исключено по умолчанию для записей) и путем кэширования значенияtoString
, поскольку оно не изменится для этой неизменяемой записи. Мы также игнорируем нулевые значения (значение по умолчанию дляz
в нашем определении).У нас может быть аналогичное определение для двумерной точки:
package twod импорт groovy.transform.ToString @ToString(ignoreNulls=true, cache=true, includeNames=true, leftDelimiter='[', rightDelimiter=']', nameValueSeparator='=') точка записи (целое число x, целое число y) { } утверждать новую точку (10, 20). toString() == 'twod.Point[x=10, y=20]'
Здесь мы видим, что без имени пакета он будет иметь тот же toString, что и в предыдущем примере.
6.2.3. Получение списка значений компонентов записи
Мы можем получить значения компонентов из записи в виде списка следующим образом:
запись Point(int x, int y, String color) { } def p = новая точка (100, 200, 'зеленый') защита (x, y, c) = p.toList() утверждать х == 100 утверждать у == 200 assert c == 'green'
Вы можете использовать
@RecordOptions(toList=false)
, чтобы отключить эту функцию.6.2.4. Получение карты значений компонентов записи
Мы можем получить значения компонентов из записи в виде карты следующим образом:
запись Point(int x, int y, String color) { } def p = новая точка (100, 200, 'зеленый') assert p.toMap() == [x: 100, y: 200, color: 'green']
Вы можете использовать
@RecordOptions(toMap=false)
, чтобы отключить эту функцию.6.2.5. Получение количества компонентов в записи
Мы можем получить количество компонентов в записи следующим образом:
запись Point(int x, int y, String color) { } def p = новая точка (100, 200, 'зеленый') assert p.size() == 3
Вы можете использовать
@RecordOptions(size=false)
, чтобы отключить эту функцию.6.2.6. Получение компонента n
th из записиМы можем использовать нормальное позиционное индексирование Groovy для получения определенного компонента в записи следующим образом:
Record Point(int x, int y, String color) { } def p = новая точка (100, 200, 'зеленый') утверждать p[1] == 200
Вы можете использовать
@RecordOptions(getAt=false)
, чтобы отключить эту функцию.6.3. Дополнительные функции Groovy
6.3.1. Копирование
Может быть полезно сделать копию записи с некоторыми измененными компонентами. Это можно сделать с помощью необязательного метода
copyWith
, который принимает именованные аргументы. Компоненты записи задаются из предоставленных аргументов. Для не упомянутых компонентов используется (поверхностная) копия исходного компонента записи. Вот как вы можете использоватьcopyWith
для записиFruit
:@RecordOptions(copyWith=true) запись фруктов (имя строки, двойная цена) {} def apple = новый Fruit('Apple', 11.6) утверждать 'Apple' == apple.name() утверждать 11.6 == apple.price() def оранжевый = apple.copyWith(имя: 'Оранжевый') assert Orange.toString() == 'Fruit[name=Orange, price=11.6]'
Функцию
copyWith
можно отключить, установивRecordOptions#copyWith
атрибут аннотации кfalse
.6.3.2. Глубокая неизменность
Как и в Java, записи по умолчанию обеспечивают неглубокую неизменность. Преобразование Groovy
@Immutable
выполняет защитное копирование для диапазона изменяемых переменных. типы данных. Записи могут использовать это защитное копирование для обеспечения глубокой неизменности следующим образом:@ImmutableProperties запись Покупки (элементы списка) {} def items = ['хлеб', 'молоко'] def shop = новые покупки (предметы) предметы << 'шоколад' assert shop.items() == ['хлеб', 'молоко']
Эти примеры иллюстрируют принцип Функция записи Groovy предлагает три уровня удобства:
Использование ключевого слова
записи
для максимальной краткостиПоддержка простой настройки с использованием декларативных аннотаций
Разрешение обычных реализаций методов, когда требуется полный контроль
6.3.3. Получение компонентов записи в виде типизированного кортежа
Компоненты записи можно получить в виде типизированного кортежа:
import groovy.transform.* @RecordOptions (компоненты = истина) запись Point (int x, int y, цвет строки) { } @CompileStatic метод защиты () { def p1 = новая точка (100, 200, «зеленый») def (целое x1, целое y1, строка c1) = p1. components() утверждать x1 == 100 утверждать y1 == 200 утверждать c1 == 'зеленый' def p2 = новая точка (10, 20, 'синий') защита (x2, y2, c2) = p2.components() утверждать x2 * 10 == 100 утверждать у2 ** 2 == 400 утверждать c2.toUpperCase() == 'СИНИЙ' def p3 = новая точка (1, 2, 'красный') утверждать p3.components() instanceof Tuple3 } метод()
Groovy имеет ограниченное количество классов
TupleN
. Если в вашей записи много компонентов, вы не сможете использовать эту функцию.6.4. Другие отличия от Java
Groovy поддерживает создание классов , подобных записям , а также собственных записей. Подобные записи классы не расширяют класс Java
Record
и подобные классы. не будут восприниматься Java как записи, но в остальном будут иметь аналогичные свойства.@RecordOptions аннотация
(часть@RecordType
) поддерживает атрибут аннотации режимаAUTO
по умолчанию):- NATIVE
Создает класс, аналогичный тому, что сделала бы Java. Выдает ошибку при компиляции на JDK более ранних версий, чем JDK16.
- ЭМУЛЯЦИЯ
Создает класс записи для всех версий JDK.
- АВТО
Создает собственную запись для JDK16+ и эмулирует запись в противном случае.
Используете ли вы ключевое слово записи
@RecordType
не зависит от режима.Запечатанные классы, интерфейсы и трейты ограничивают, какие подклассы могут расширять/реализовывать их. До запечатанных классов у проектировщиков иерархии классов было два основных варианта:
Запечатанные классы представляют собой нечто среднее по сравнению с этими вариантами «все или ничего».
Запечатанные классы также более гибкие, чем другие приемы, использовавшиеся ранее попытаться достичь золотой середины. Например, для иерархии классов модификаторы доступа, такие как protected и package-private, дают некоторую возможность ограничить наследование иерархии, но часто за счет гибкого использования этих иерархий.
Запечатанные иерархии обеспечивают полное наследование в пределах известной иерархии классов, интерфейсов и трейты, но отключают или обеспечивают только контролируемое наследование вне иерархии.
В качестве примера предположим, что мы хотим создать иерархию форм, содержащую только круги и квадраты. Мы также хотим, чтобы интерфейс формы иметь возможность ссылаться на экземпляры в нашей иерархии. Мы можем создать иерархию следующим образом:
запечатанный интерфейс ShapeI разрешает Circle,Square { } конечный класс Circle реализует ShapeI {} final class Square реализует ShapeI { }
Groovy также поддерживает альтернативный синтаксис аннотаций. Мы думаем, что стиль ключевых слов лучше, но вы можете выбрать стиль аннотаций, если ваш редактор еще не поддерживает Groovy 4.
@Sealed(permittedSubclasses=[Круг,Квадрат]) интерфейс ShapeI { } конечный класс Circle реализует ShapeI {} final class Square реализует ShapeI { }
У нас может быть ссылка типа
ShapeI
, которая, благодаря предложениюразрешает
, может указывать либо наCircle
, либо наSquare
и, поскольку наши классыfinal
, мы знаем, что в будущем в нашу иерархию не будут добавлены дополнительные классы. По крайней мере, без измененияразрешает пункт
и перекомпиляцию.В общем, мы могли бы захотеть, чтобы некоторые части нашей иерархии классов немедленно заблокированы, как у нас здесь, где мы отметили подклассы как
окончательный
, но в других случаях мы могли бы разрешить дальнейшее контролируемое наследование.Герметичный класс Форма разрешает Круг, Многоугольник, Прямоугольник { } окончательный класс Circle extends Shape {} класс Polygon расширяет форму {} незапечатанный класс RegularPolygon расширяет Polygon { } конечный класс Hexagon extends Polygon {} запечатанный класс Rectangle extends Shape разрешает Square{ } окончательный класс Square extends Rectangle { }
<Щелкните, чтобы увидеть альтернативный синтаксис аннотаций>@Sealed(permittedSubclasses=[Circle,Polygon,Rectangle]) class Shape { } окончательный класс Circle extends Shape {} класс Polygon расширяет форму {} Класс @NonSealed RegularPolygon расширяет Polygon { } конечный класс Hexagon extends Polygon {} Класс @Sealed(permittedSubclasses=Square) Rectangle extends Shape { } окончательный класс Square extends Rectangle {}
В этом примере наши разрешенные подклассы дляФигура
— этоКруг
,Многоугольник
иПрямоугольник
.Круг
являетсяокончательным
и, следовательно, эта часть иерархии не может быть расширена.Polygon
неявно не является запечатанным, аRegularPolygon
явно помечен какнезапечатанный
. Это означает, что наша иерархия открыта для любого дальнейшего расширения путем создания подклассов. как видно сPolygon → RegularPolygon
иRegularPolygon → Hexagon
.Прямоугольник
сам запечатан, что означает, что часть иерархии может быть расширена но только контролируемым образом (разрешен толькоКвадрат
).Запечатанные классы полезны для создания связанных классов, подобных перечислениям. которые должны содержать конкретные данные экземпляра. Например, у нас может быть следующее перечисление:
перечисление Погода {Дождливо, Облачно, Солнечно} прогноз прогноза = [Погода.Дождь, Погода.Солнечно, Погода.Облачно] утверждать прогноз. toString() == '[Дождь, Солнечно, Облачно]'
, но теперь мы хотим также добавить данные конкретного экземпляра погоды в прогнозы погоды. Мы можем изменить нашу абстракцию следующим образом:
запечатанный абстрактный класс Погода { } @Immutable(includeNames=true) класс Rainy расширяет Weather { Integer ожидаемый дождь } @Immutable(includeNames=true) class Sunny extends Weather {Integer ожидаемая температура} Класс @Immutable(includeNames=true) Облачность расширяет прогноз погоды { Ожидаемое целое числоUV } прогноз прогноза = [новый дождливый(12), новый солнечный(35), новый облачный(6)] утверждать прогноз.toString() == '[Дождь (ожидаемый дождь: 12), Солнечно (ожидаемая температура: 35), Облачно (ожидаемый UV: 6)]'
Запечатанные иерархии также полезны при указании алгебраических или абстрактных типов данных (ADT), как показано в следующем примере:
import groovy.transform.* запечатанный интерфейс Tree
{} @Singleton final class Empty реализует Tree { Строка toString() {'Пусто'} } @Canonical final class Node реализует Tree { значение Т Дерево слева, справа } Tree tree = new Node<>(42, new Node<>(0, Empty. instance, Empty.instance), Empty.instance) assert tree.toString() == 'Узел (42, Узел (0, Пусто, Пусто), Пусто)' Запечатанные иерархии хорошо работают с записями, как показано в следующем примере:
запечатанный интерфейс Expr {} запись ConstExpr(int i) реализует Expr {} запись PlusExpr(Expr e1, Expr e2) реализует Expr {} запись MinusExpr(Expr e1, Expr e2) реализует Expr {} запись NegExpr(Expr e) реализует Expr {} def threePlusNegOne = новое ПлюсВыражение(новое КонстантноеВыражение(3), новое ОтрицательноеВыражение(новое КонстантноеВыражение(1))) assert threePlusNegOne.toString() == 'PlusExpr[e1=ConstExpr[i=3], e2=NegExpr[e=ConstExpr[i=1]]]'
7.1. Отличия от Java
Java не предоставляет модификатор по умолчанию для подклассов запечатанных классов и требует, чтобы был указан один из
окончательный
,запечатанный
илине запечатанный
. Groovy по умолчанию использует незапечатанный , но вы все равно можете использоватьнезапечатанный/@NonSealed
, если хотите. Мы ожидаем, что инструмент проверки стиля CodeNarc в конечном итоге будет иметь правило, которое ищет наличиене запаянных
, поэтому разработчики хотят, чтобы это было строже style смогут использовать CodeNarc и это правило, если захотят.В настоящее время Groovy не проверяет, разрешены ли все классы, упомянутые в
. Подклассы
. доступны во время компиляции и компилируются вместе с базовым запечатанным классом. Это может измениться в будущей версии Groovy.
Groovy поддерживает аннотирование классов как запечатанных, так и "собственных" запечатанных классов.
Аннотация
@SealedOptions
поддерживает атрибут аннотации режимаAUTO
по умолчанию):- NATIVE
Создает класс, аналогичный тому, что сделала бы Java. Выдает ошибку при компиляции на JDK более ранних версий, чем JDK17.
- ЭМУЛЯЦИЯ
Указывает, что класс запечатан с помощью аннотации
@Sealed
. Этот механизм работает с компилятором Groovy для JDK8+, но не распознается компилятором Java.- АВТО
Создает собственную запись для JDK17+ и эмулирует запись в противном случае.
Используете ли вы ключевое слово
seal
или аннотацию@Sealed
не зависит от режима.Вызов функций из классов Java
Информация в этом разделе относится только к настольным приложениям. Вы можете вызывать подпрограммы, находящиеся в любом классе Java, из ваших скриптов. В следующих разделах содержится подробная информация об этой функции:
Требования
Вызов подпрограмм Java через объект JavaClasses
Вызов подпрограмм Java через объект JavaRuntime
Обработка исключений, возникших в приложениях Java
Ограничения
Требования
Лицензия на модуль TestComplete Desktop.
Плагин Java Classes Support включен в TestComplete. Плагин устанавливается и включается автоматически.
Чтобы проверить, доступен ли подключаемый модуль, выберите Файл | Установите расширения из главного меню TestComplete и найдите плагин в следующем диалоговом окне. Если плагина нет, переустановите TestComplete.
Вызов подпрограмм Java через объект JavaClasses
Вы можете использовать объект JavaClasses
для доступа к классам Java, их методам и свойствам. Настройка следующая:
Укажите модуль виртуальной машины Java.
Чтобы обработчик сценариев мог вызывать функции классов Java, необходимо указать модуль виртуальной машины Java, который будет использоваться для размещения классов. Для этого используйте диалоговое окно Java Bridge Options. Чтобы вызвать диалоговое окно, выберите Инструменты | Options в главном меню, а затем выберите Engines | Java Bridge из дерева, отображаемого в левой части результирующего диалогового окна «Параметры». Диалог включает в себя Расположение модуля виртуальной машины Java Опция, позволяющая указать путь к нужному файлу модуля виртуальной машины Java.
Сделать класс доступным для обработчика сценариев.
Для этого добавьте нужный класс в список в группе настроек Java Bridge проекта. Эта группа содержит список классов Java, функции которых будут доступны вашим скриптам через объект
JavaClasses
и пути к этим классам:Выберите Инструменты | Текущие свойства проекта из главного меню TestComplete.
Выберите категорию Java Bridge.
Щелкните изображение, чтобы увеличить его.
Чтобы добавить новое имя класса в список Классы Java , нажмите кнопку Добавить справа от списка, а затем укажите нужное имя класса.
Используйте полное имя класса, включая имя пакета. Чтобы указать путь к указанным классам, нажмите кнопку Добавить каталог или Добавить файлы JAR справа от списка Пути к классам и выберите нужную папку или файлы JAR в следующем диалоговом окне.
Список Class Path содержит каталоги и файлы JAR. Классы Java указаны в приведенном выше списке классов Java . Если вы хотите добавить новый класс Java в настройки проекта, нажмите кнопку Добавить справа от списка Классы Java, и указанный класс будет автоматически добавлен в этот список. (Необязательно.) Чтобы изменить или перестроить файл JAR, добавленный в настройки проекта TestComplete, щелкните Выгрузить классы справа от списка Классы Java. Эта кнопка очистит список Java Classes . Дело в том, что TestComplete блокирует доступ к JAR-файлам, указанным в настройках проекта Java Bridge, до тех пор, пока пакет проектов, содержащий соответствующий проект, не будет закрыт. Таким образом, если вы попытаетесь удалить или перестроить этот файл, пока соответствующий проект открыт в TestComplete, вы получите сообщение «Отказано в доступе». быстрый. Чтобы получить доступ к указанному файлу JAR, вы можете либо щелкнуть «Выгрузить классы», либо закрыть набор проектов.
Классы, добавленные к параметрам Java Bridge, становятся доступными в сценариях как дочерние объекты объекта JavaClasses
. Типы, определенные в конкретном классе, в свою очередь являются дочерними объектами узла класса. Объект, соответствующий типу, обеспечивает доступ к подтипам, статическим членам и конструкторам, определенным в этом типе. Все доступные классы, типы и члены типов отображаются в коде.
Окно завершения:
Щелкните изображение, чтобы увеличить его.
Для вызова определенного метода из сценариев используйте следующий синтаксис:
Классы Java. пакет . класс . подкласс . метод ( параметры )
Простые типы данных (например, строки, целые числа, логические значения), передаваемые в качестве параметров, автоматически преобразуются в экземпляры соответствующих классов Java. Параметры типа Object могут быть переданы в Java Bridge только в том случае, если они являются потомками java. lang.Object , в противном случае возникает ошибка несоответствия типов. |
Как видите, имя пакета и класса (а также имя подкласса) должны быть разделены точками. Также обратите внимание, что точки, используемые в имени пакета, должны быть заменены символом подчеркивания.
Приведенный выше синтаксис позволяет вызывать только статические члены классов Java. Чтобы использовать нестатические методы и свойства в сценариях, вы должны сначала создать экземпляр нужного класса, вызвав конструктор класса или статический член, который создает новый экземпляр класса.
Примечание: | Обычно конструктор Java имеет то же имя, что и имя класса, к которому он принадлежит. Класс может иметь любое количество конструкторов с одинаковыми именами. Чтобы избежать конфликтов имен, TestComplete меняет имена этих методов на newInstance . Таким образом, конструкторы классов будут иметь следующие имена: newInstance, newInstance_2, newInstance_3 и т. д. |
С объектами, возвращаемыми методами и свойствами классов Java, можно работать так же, как и с другими объектами. Некоторые экземпляры типов значений, например числовые и логические значения, совместимы с OLE и могут напрямую использоваться в скриптах. Чтобы сделать String
, Decimal
, DateTime
Объекты, значения перечисления и массивы OLE-совместимые, TestComplete добавляет к ним специальное свойство OleValue
. Для работы со структурами и ссылочными типами (кроме строк и массивов) используйте их внутренние свойства и члены.
Вызов подпрограмм Java через объект JavaRuntime
Все объекты процесса
, соответствующие приложениям Java, имеют метод JavaRuntime
, обеспечивающий доступ к виртуальной машине Java, существующей в процессе Windows. Объект, возвращенный 9Метод 1042 JavaRuntime является экземпляром класса java.lang.Runtime
желаемой виртуальной машины Java. Этот объект содержит те же свойства и методы, что и java.lang.Runtime
, а также определенные свойства, предоставляемые TestComplete. Свойство объектов JavaClasses
обеспечивает доступ к пакетам и классам, определенным в приложении Java.
Свойство JavaRuntime.JavaClasses
аналогично свойству JavaClasses
, а синтаксис, который используется для вызова члена класса через JavaRuntime
, аналогичен синтаксису, который вы используете с объектом JavaClasses
:
Sys.Process( "MyJavaApp" ).JavaRuntime.JavaClasses. пакет . класс . подкласс . метод ( параметры )
Точно так же для вызова нестатического метода или свойства необходимо сначала создать экземпляр класса с помощью конструктора класса или специального статического члена.
Преимущество свойства JavaRuntime.JavaClasses
по сравнению с объектом JavaClasses
заключается в том, что оно позволяет создавать экземпляры классов приложения без необходимости добавлять классы в параметры Java Bridge проекта.
Обработка исключений, возникших в приложениях Java
TestComplete позволяет перехватывать и обрабатывать исключения, возникающие в коде приложений Java. Вы можете перехватывать эти исключения, используя стандартные операторы скрипта, такие как попробовать… поймать и другие. Дополнительные сведения о перехвате исключений из сценариев TestComplete см. в разделе Обработка исключений в сценариях.
TestComplete автоматически перехватывает исключение, возникающее при тестировании приложения Java, и возвращает его описание в следующем формате:
Тип исключения: Причина исключения
Например, следующее исключение возникает, когда тестируемое приложение Java обнаруживает деление на ноль - java.lang.ArithmeticException: / на ноль
.
TestComplete не может получить доступ к внутренним методам и свойствам объектов Java, если тестируемое приложение было запущено с аргументами командной строки -verbose или -verbose:class .