Руководство по Java Core. Методы.
Метод – это набор выражений, которые объединены вместе для выполнения определённой операции.
Например, когда мы вызываем метод System.out.println(), начинается выполнение целого ряда операций для того, чтобы в итоге отобразить вывести сообщение в консоль.
В этом уроке мы более подробно изучим, как создавать собственные методы. Рассмотрим методы, возвращающие определённое значение, и которые – нет. Также, мы поговорим о том, что такое перезагрузка метода.
В общем виде, методы выглядит следующим образом:
Метод состоит из следующих сущностей:
- Модификатор
Модификатор метода говорит компилятору как вызывать данный метод и определяет уровень доступа данного метода. Модификатор не является обязательной частью объявления метода. - Возвращаемый тип
Метод возвращать значение. Возвращаемый тип – это тип данных, который будет возвращён данным методом. Если нам не нужно, чтобы метод возвращал значение, то мы должным использовать ключевое слово void в качестве возвращаемого типа. - Имя метода
Непосредственно, имя метода, которе является его идентификатором. - Параметры
Если нам необходимо, выполнить определённые вычисления, например получить сумму двух чисел, то мы должны передать методу эти два числа. Передать эти числа мы можем с помощью параметров метода. Мы должны указать тип данных этих параметров. Метод также может не иметь параметров. - Тело метода
Тело метода включает в себя набор выражений, которые определяют поведение метода. В случае со сложением двух чисел, метод выполнит операцию сложения.
Для понимания того, как это работает на практике, рассмотрим пример простого приложения.
В результате работы программы мы получим следующий результат:
Как мы видим, первый метод принимает два параметра и возвращает значение (сумму этих чисел). Второй метода, также принимает два параметра, но не возвращает никакого значения. Вместо этого он определяет максимальное число и выводит текстовое сообщение.
Перегрузка методов
Иногда нам необходимо выполнять одни и те же операции для разных типов данных. Например, нам необходимо определять сумму не только для целых чисел, но и для чисел с плавающей точкой. Перегруженный метод должен иметь такое же название, как и первый, но список параметров должен отличаться (либо типы данных, либо количество параметров, либо и то, и другое). Рассмотрим пример простого приложения.
В результате работы программы мы получим следующий результат:
Конструкторы
Конструктор по своей сути тоже метод, имеющий такое же название, как и класс, в котором он создан. Его задача создавать экземпляры данного классы (объекты).
Все классы имеют конструктор. Даже если он не объявлен явно, то используется конструктор по умолчанию (без аргументов – всем переменным присваиваются значения по умолчанию, согласно спецификации).
Рассмотрим пример простого приложения с использованием конструктора.
В результате работы программы мы получим следующий результат:
В этом уроке мы более подробно изучили, что такое методы, какие виды методов существуют и рассмотрели примеры с их использованием.
Удобоваримый вызов Java методов из нативного кода
Существует довольно много приложений под Android, которые совмещают C++ и Java код. Где Java выступает оберткой/прослойкой, а C++ выполняет всю грязную работу. Пожалуй, ярким примером могут служить игры. В связи с этим часто приходится вызывать Java код из нативного для доступа к системным свойствам и плюшкам, которые предоставляет система (переключится на другую активность, послать или скачать что-либо из интернета). Причин много, а проблема одна: каждый раз приходится писать в лучшем случае 5 строчек кода и помнить, какую сигнатуру функции нужно запихнуть в параметр. Потом еще нужно перевести эти параметры в нужный тип. Стандартный пример из туториалов:
Строка-сигнатура для данного метода будет (ILjava/lang/String;F)J.
Вам удобно это все запоминать? А переводить С-строки в jstring? Мне — нет. Мне хочется писать:
Постановка задачи
Для начала поймем, что нам нужно. В сущности, это четыре вещи:
- Вызвать метод;
- Из параметров нужно вытянуть строку сигнатуры. Да, да, вот эту (ILjava/lang/String;F)J;
- Сконвертировать параметры в нужный тип;
- Возвратить тип данных, который хочет видеть пользователь нашего класса.
Вызов метода
Теперь стоит отметить, как мы будем вызывать нашу функцию-оболочку. Так как параметров может разное количество (от нуля и больше), то нужна функция вроде print`а в стандартной библиотеке, но с тем, чтобы было удобно вытягивать тип параметра и сам параметр. В С++11 появились вариадические шаблоны. Ими и воспользуемся.
Составляем сигнатуру
- Используем typeid и цепочку if … else. Должно получится что-то вроде:
Вызов всего этого непотребства:
Рекурсия — это хорошо в воспитательно-образовательных целях, но предпочитаю ее обходить при возможности. Тут такая возможность есть. Так как аргументы идут последовательно и мы можем узнать количество аргументов можно использовать удобство предоставленное стандартом С++11. Код преобразуется в:
Кода вроде бы и больше, но работает оно быстрее. Хотя бы за счет того, что не вызываем функций больше, чем нам это нужно.
Конвертация типа данных
После пыток попыток и ухищрений было решено использовать CallStaticMethodA(JNIEnv*, jclass, jmethodID, jvalue*). Теперь только нужно привести все параметры к jvalue. Сам jvalue это union, в котором нужно установить нужное поле в зависимости от типа данных, которые вам передали любимые пользователи. Мудрить не будем и создаем структуру (или класс; дело вкуса) JniHolder с конструкторами нужных типов.
Где JObjectHolder — обертка для удержания и удаления jobject`а.
Создается объект JniHolder, куда передаются JNIEnv* и значение. В конструкторе мы знаем какое поле нужно выставить в jvalue. Чтобы не было соблазна у компилятора приводить типы незаметно, все конструкторы делаем explicit. Вся цепочка занимает одну строчку:
Но есть одно но. Когда преобразования происходит мы возвращаем jvalue, но у нас удаляется jObject и val.l указывает на невалидный адрес. Поэтому приходится сохранять холдеры во время вызова функции java.
В случае передачи нескольких параметров используем список инициализации:
Возвращение нужного типа данных
Хотелось бы написать какой-то один метод, который разруливал ситуацию и выглядел:
Но есть неприятная особенность JNI: для каждого возвращаемого типа есть свой конкретный метод. То есть, для int вам нужен CallStaticIntMethod, для float – CallStaticFloatMethod и так далее. Пришел к частичным типизациям шаблонов. Сначала объявляем нужный нам интерфейс:
Потом для каждого типа пишем реализацию. Для целых чисел (int) будет выглядеть:
Если у нас ноль параметров, то нужно вызывать CallStaticMethod, а не CallStaticMetodA. Ну и если пытаться создать массив размерностью ноль, компилятор сообщит вам все, что думает по этому поводу.
Финал
Сам метод вызова выглядит:
Теперь вызов метода из java:
Где-то в нативном коде:
Выводы
Вызовы методов превратились в удобную вещь и не надобно запоминать сигнатуры и конвертировать значения и удалять ссылки. Достаточно передавать название класса, метода и аргументы.
Также получилась интересная задачка, благодаря которой немного поразбирался с новыми плюшками языка (которые мне ну очень понравились) и вспомнил шаблоны.
Благодарю за прочтение. Ну или за внимание, если вы не все прочитали. С радостью прочитаю предложения по улучшению и критику работы. А также отвечу на вопросы.
Изучаем Java
Метод — это именованный обособленный блок кода.
С методом можно выполнить три операции. Объявить — описать, что такой метод есть, однако не расписывать его содержимое, т.е. тело; определить — описать метод с его структурой, т.е. с его телом; вызвать, т.е. запустить этот метод на выполнение.
Объявление метода состоит из двух частей: заголовка метода и его тела.
В заголовке метода указываются его модификаторы, имя метода и в скобках его параметры (аргументы). Тело метода располагается между двумя фигурными скобками и является блоком кода.
Рассмотрим следующие модификаторы.
• Модификаторы доступа — они уже подробно разбирались.
• Модификаторы strictpf, static — они также уже рассматривались.
• Abstract. Он предусматривает только объявление метода. Метод должен быть определен в классах-наследниках (подробнее об этом — в разделе "Наследование").
• Final. Методы не могут переопределяться в классах-наследниках (об этом подробнее см. в том же разделе).
• Native. Если у метода есть такой модификатор, то данный метод написан на другом языке программирования (например, на языке С++).
• Sinchronized— этот модификатор означает, что данный метод защищен от разрушения данных при попытке использования этих данных несколькими методами сразу.
Создадим теперь простой метод и вызовем его (листинг 4.5).
Листинг 4.5.
Создание и вызов простого метода
У каждого метода в скобках можно помещать аргументы, или параметры. С помощью аргументов можно передать какие-либо значения методу, над которыми он будет работать. Если передавать методу ссылку (на объект, переменную и т.д.), то значение каких- либо свойств объекта могут измениться. Приведем пример использования методов с аргументами (листинг 4.6).
Листинг 4.6.
Пример использования методов с аргументами
Заметим, что при задании ссылки в качестве аргумента значение свойств объекта меняется. Почему так? Дело в том, что передается ссылке, которая находится в аргументе метода, не ссылка как таковая, а сам объект и, следовательно, ссылка, являющаяся аргументом, выступает полноправной ссылкой на объект. И поэтому в методе можно изменять свойства того или иного объекта.
Ранее мы использовали параметр, являющийся массивом объектов String с именем args, который указывается в методе main. Если запускать программу из командной строки, то можно передать в программу несколько значений, которые последовательно будут записываться в элементы этого массива. Если это необходимо, можно получить к ним доступ, указывая соответствующие элементы массива.
Возможно также наличие следующего модификатора типа возвращаемого значения (мы уже указывали модификатор void, означающий то, что метод ничего не возвращает). Метод может возвращать какое-то одно значение. Чтобы метод возвращал значение, нужно указать модификатор типа значения, которое вы собираетесь возвращать, а в конце выполнения метода добавить return с переменной (или значением).
В листинге 4.7 представлен пример использования метода, имеющего модификатор типа возвращаемого значения.
Листинг 4.7.
Пример использования метода, имеющего модификатор типа j возвращаемого значения
Перегруженные методы
Если два и более методов класса имеют одно имя, но их параметры не совпадают, то такие методы называют перегруженными. Примером перегруженных методов являются конструкторы. Пример в листинге 4.8.