Java передача параметров в функцию

Зачастую нам надо повторять одно и то же действие во многих частях программы.

Например, необходимо красиво вывести сообщение при приветствии посетителя, при выходе посетителя с сайта, ещё где-нибудь.

Чтобы не повторять один и тот же код во многих местах, придуманы функции. Функции являются основными «строительными блоками» программы.

Примеры встроенных функций вы уже видели – это alert(message) , prompt(message, default) и confirm(question) . Но можно создавать и свои.

Объявление функции

Для создания функций мы можем использовать объявление функции.

Пример объявления функции:

Вначале идёт ключевое слово function , после него имя функции, затем список параметров в круглых скобках через запятую (в вышеприведённом примере он пустой) и, наконец, код функции, также называемый «телом функции», внутри фигурных скобок.

Наша новая функция может быть вызвана по её имени: showMessage() .

Вызов showMessage() выполняет код функции. Здесь мы увидим сообщение дважды.

Этот пример явно демонстрирует одно из главных предназначений функций: избавление от дублирования кода.

Если понадобится поменять сообщение или способ его вывода – достаточно изменить его в одном месте: в функции, которая его выводит.

Локальные переменные

Переменные, объявленные внутри функции, видны только внутри этой функции.

Внешние переменные

У функции есть доступ к внешним переменным, например:

Функция обладает полным доступом к внешним переменным и может изменять их значение.

Внешняя переменная используется, только если внутри функции нет такой локальной.

Если одноимённая переменная объявляется внутри функции, тогда она перекрывает внешнюю. Например, в коде ниже функция использует локальную переменную userName . Внешняя будет проигнорирована:

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

Глобальные переменные видимы для любой функции (если только их не перекрывают одноимённые локальные переменные).

Желательно сводить использование глобальных переменных к минимуму. В современном коде обычно мало или совсем нет глобальных переменных. Хотя они иногда полезны для хранения важнейших «общепроектовых» данных.

Параметры

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

В нижеприведённом примере функции передаются два параметра: from и text .

Когда функция вызывается в строках (*) и (**) , переданные значения копируются в локальные переменные from и text . Затем они используются в теле функции.

Вот ещё один пример: у нас есть переменная from , и мы передаём её функции. Обратите внимание: функция изменяет значение from , но это изменение не видно снаружи. Функция всегда получает только копию значения:

Параметры по умолчанию

Если параметр не указан, то его значением становится undefined .

Например, вышеупомянутая функция showMessage(from, text) может быть вызвана с одним аргументом:

Это не приведёт к ошибке. Такой вызов выведет "Аня: undefined" . В вызове не указан параметр text , поэтому предполагается, что text === undefined .

Если мы хотим задать параметру text значение по умолчанию, мы должны указать его после = :

Теперь, если параметр text не указан, его значением будет "текст не добавлен"

В данном случае "текст не добавлен" это строка, но на её месте могло бы быть и более сложное выражение, которое бы вычислялось и присваивалось при отсутствии параметра. Например:

В JavaScript параметры по умолчанию вычисляются каждый раз, когда функция вызывается без соответствующего параметра.

В примере выше anotherFunction() будет вызываться каждый раз, когда showMessage() вызывается без параметра text .

Ранние версии JavaScript не поддерживали параметры по умолчанию. Поэтому существуют альтернативные способы, которые могут встречаться в старых скриптах.

Например, явная проверка на undefined :

…Или с помощью оператора || :

Возврат значения

Функция может вернуть результат, который будет передан в вызвавший её код.

Простейшим примером может служить функция сложения двух чисел:

Директива return может находиться в любом месте тела функции. Как только выполнение доходит до этого места, функция останавливается, и значение возвращается в вызвавший её код (присваивается переменной result выше).

Вызовов return может быть несколько, например:

Возможно использовать return и без значения. Это приведёт к немедленному выходу из функции.

В коде выше, если checkAge(age) вернёт false , showMovie не выполнит alert .

Если функция не возвращает значения, это всё равно, как если бы она возвращала undefined :

Пустой return аналогичен return undefined :

Для длинного выражения в return может быть заманчиво разместить его на нескольких отдельных строках, например так:

Код не выполнится, потому что интерпретатор JavaScript подставит точку с запятой после return . Для него это будет выглядеть так:

Таким образом, это фактически стало пустым return .

Если мы хотим, чтобы возвращаемое выражение занимало несколько строк, нужно начать его на той же строке, что и return . Или, хотя бы, поставить там открывающую скобку, вот так:

И тогда всё сработает, как задумано.

Выбор имени функции

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

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

Например, функции, начинающиеся с "show" обычно что-то показывают.

Функции, начинающиеся с…

  • "get…" – возвращают значение,
  • "calc…" – что-то вычисляют,
  • "create…" – что-то создают,
  • "check…" – что-то проверяют и возвращают логическое значение, и т.д.

Примеры таких имён:

Благодаря префиксам, при первом взгляде на имя функции становится понятным что делает её код, и какое значение она может возвращать.

Функция должна делать только то, что явно подразумевается её названием. И это должно быть одним действием.

Читайте также:  Wifi адаптер для компьютера днс

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

Несколько примеров, которые нарушают это правило:

  • getAge – будет плохим выбором, если функция будет выводить alert с возрастом (должна только возвращать его).
  • createForm – будет плохим выбором, если функция будет изменять документ, добавляя форму в него (должна только создавать форму и возвращать её).
  • checkPermission – будет плохим выбором, если функция будет отображать сообщение с текстом доступ разрешён/запрещён (должна только выполнять проверку и возвращать её результат).

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

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

Например, во фреймворке jQuery есть функция с именем $ . В библиотеке Lodash основная функция представлена именем _ .

Это исключения. В основном имена функций должны быть в меру краткими и описательными.

Функции == Комментарии

Функции должны быть короткими и делать только что-то одно. Если это что-то большое, имеет смысл разбить функцию на несколько меньших. Иногда следовать этому правилу непросто, но это определённо хорошее правило.

Небольшие функции не только облегчают тестирование и отладку – само существование таких функций выполняет роль хороших комментариев!

Например, сравним ниже две функции showPrimes(n) . Каждая из них выводит простое число до n .

Первый вариант использует метку nextPrime :

Второй вариант использует дополнительную функцию isPrime(n) для проверки на простое:

Второй вариант легче для понимания, не правда ли? Вместо куска кода мы видим название действия ( isPrime ). Иногда разработчики называют такой код самодокументируемым.

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

Итого

Объявление функции имеет вид:

  • Передаваемые значения копируются в параметры функции и становятся локальными переменными.
  • Функции имеют доступ к внешним переменным. Но это работает только изнутри наружу. Код вне функции не имеет доступа к её локальным переменным.
  • Функция может возвращать значение. Если этого не происходит, тогда результат равен undefined .

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

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

  • Имя функции должно понятно и чётко отражать, что она делает. Увидев её вызов в коде, вы должны тут же понимать, что она делает, и что возвращает.
  • Функция – это действие, поэтому её имя обычно является глаголом.
  • Есть много общепринятых префиксов, таких как: create… , show… , get… , check… и т.д. Пользуйтесь ими как подсказками, поясняющими, что делает функция.

Функции являются основными строительными блоками скриптов. Мы рассмотрели лишь основы функций в JavaScript, но уже сейчас можем создавать и использовать их. Это только начало пути. Мы будем неоднократно возвращаться к функциям и изучать их всё более и более глубоко.

Задачи

Обязателен ли "else"?

Следующая функция возвращает true , если параметр age больше 18 .

В ином случае она запрашивает подтверждение через confirm и возвращает его результат:

Будет ли эта функция работать как-то иначе, если убрать else ?

Есть ли хоть одно отличие в поведении этого варианта?

Оба варианта функций работают одинаково, отличий нет.

Перепишите функцию, используя оператор ‘?’ или ‘||’

Следующая функция возвращает true , если параметр age больше 18 .

В ином случае она задаёт вопрос confirm и возвращает его результат.

Перепишите функцию, чтобы она делала то же самое, но без if , в одну строку.

Сделайте два варианта функции checkAge :

  1. Используя оператор ?
  2. Используя оператор ||

решение

Используя оператор || (самый короткий вариант):

Обратите внимание, что круглые скобки вокруг age > 18 не обязательны. Они здесь для лучшей читаемости кода.

Функция min(a, b)

Напишите функцию min(a,b) , которая возвращает меньшее из чисел a и b .

Вариант решения с использованием if :

Вариант решения с оператором ? :

P.S. В случае равенства a == b не имеет значения, что возвращать.

Функция pow(x,n)

Напишите функцию pow(x,n) , которая возвращает x в степени n . Иначе говоря, умножает x на себя n раз и возвращает результат.

Создайте страницу, которая запрашивает x и n , а затем выводит результат pow(x,n) .

Возможно я что – то не так понимаю. Но по моей логике переменная переданная в метод и дальнейшие модификации с ней должны остаться в это методе, и исчезнуть вместе с методом. Но в итоге я вижу в main измененный массив mas.

Почему так происходит. Потому что в таком случае пропадает надобность в return. В Java давно, а тут такое. Видать что – то серьезное я забыл.

2 ответа 2

@AlexChermenin замудрено объяснили. Объекты передаются по ссылке, примитивы по значению. Массивы примитивов – это объекты, поэтому передаются также по ссылке.

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

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

Представьте себе ссылку, как "пульт управления переменной". Вы передаете в метод только "пульт управления" при передаче по ссылке. При выходе из метода уничтожается только "пульт управления", то есть ссылка. При передаче примитивного типа, происходит передача значения. То есть копируется значение переменной. А при выходе из метода исчезает эта копия. Исчезает значение переменной – копии.

Читайте также:  Бесплатное телевидение для компьютера

Здесь приведен перевод статьи Scott Stanchfield "Java is Pass-by-Value, Dammit!" [1], раскрывающей смысл передачи параметров в методы на Java.

Термины семантики "pass-by-value" и семантики "pass-by-reference" относятся к параметрам функции, и имеют весьма точные определения. Но они иногда почему-то сильно искажаются, когда люди говорят о Java. Мне хотелось бы исправить это.

Pass-by-value (передача параметра по значению). Действительный параметр (или выражение в аргументе) полностью вычисляется, и значение результата копируется в отдельную ячейку памяти, предназначенную для хранения значения этого параметра во время выполнения функции. То есть функция имеет дело с копией переменной, которую передали в функцию в качестве параметра. Место в памяти под параметр – обычно кусок runtime-стека в приложении (обрабатываемый Java), но другие языки могут выбрать хранение параметра в другом месте.

Pass-by-reference (передача параметра по ссылке). Формальный параметр действует просто как псевдоним (alias) реального параметра. Функция или метод, которая использует формальный параметр (для чтения или записи), в действительности использует актуальный параметр, существующий где-то вне функции.

В Java жестко задано использовать вариант передачи параметров по значению (pass-by-value), точно так же, как это делается на языке C (однако на C можно также передавать параметр и по-другому, pass-by-reference). Подробности прочитайте в Java Language Specification (JLS), там все корректно разъяснено. В разделе "JLS 8.4.1. Formal Parameters" написано:

"Когда запускается метод или конструктор, значение актуального аргумента инициализируют новые создаваемые переменные параметра, каждая с объявленным типом. Это все делается до запуска кода из тела метода или конструктора. Идентификатор, который появляется в DeclaratorId, может использоваться как простое имя в теле метода или конструктора для ссылки на формальный параметр."

Если коротко, то это можно выразить так: в Java есть указатели, и передача переменных происходит только по значению (pass-by-value). Никаких скрытых правил, все просто, тупо и однозначно (все также ясно и четко, как в дьявольском синтаксисе C++ ;).

Примечание: в конце этой статьи см. замечание по поводу семантики вызова remote-метода (RMI, remote method invocation). То, что обычно называют "pass by reference" (передача параметра по ссылке) для remote-объектов представлено чрезвычайно плохо организованной семантикой.

[Лакмусовый тест]

Здесь приведен простой тест для языка, поддерживающего семантику передачи параметра по ссылке (pass-by-reference): можете Вы написать обычную функцию/метод swap(a,b) на этом языке?

Традиционный метод или функция swap принимает два аргумента и переставляет их значения прямо в тех переменных, которые определены вне функции, и переданы в неё через параметры по ссылке. Базовая структура такой функции выглядит примерно так:

Если Вы можете на тестируемом языке написать такой метод или функцию, и вызвать его примерно так:

И после этого вызова значения в переменных var1 и var2 поменяются местами, то тогда язык поддерживает семантику передачи параметров по ссылке (pass-by-reference). Например, на Pascal можно написать:

Или можно то же самое проделать на С++:

Но на Java этого сделать нельзя!

[Теперь подробнее. ]

Здесь мы столкнулись со следующей проблемой: на Java объекты передаются по ссылке, а примитивные типы передаются по значению. Причем это корректно только наполовину. Любой может легко согласиться, что примитивы передаются по значению; в Java вообще нет такого понятия, как указатель/ссылка на примитив.

Однако объекты не передаются (явно) по ссылке. Корректным оператором была бы ссылка на объект, которая передается по значению в функцию/метод. В последующих примерах Java будет показано, в чем тут дело.

Переданная переменная (aDog) не была модифицирована! После вызова foo, aDog все также указывает на собаку "Max"!

Многие люди ошибочно думают, что получится состояние наподобие следующего:

Здесь показано, что Java фактически передает объекты по ссылке. Ошибочно трактуют определение:

Когда Вы задаете такое определение, то определяете указатель на объект Dog, не сам объект Dog.

[Указатели в сравнении с ссылками]

Проблема здесь в том, что разработчики Sun допустили ошибку в назначении понятий. В терминологии разработки языка программирования термин "pointer" (указатель) это переменная, которая неявно отслеживает место размещения некой порции данных. Значение указателя часто адрес интересующих нас данных. Некоторые языки позволяют манипулировать этим адресом, другие не позволяют этого делать.

Термин "reference" (ссылка) означает псевдоним (alias) другой переменной. Любая манипуляция с reference-переменной напрямую изменяет оригинальную переменную. Прочитайте следующее высказывание в документации "JLS 4.3. Reference Types and Values":

"Переменные-ссылки (reference values, часто просто references) являются указателями на эти объекты, и есть специальная ссылка null, которая говорит, что не указан никакой объект."

Они подчеркивают "указатели" (pointers) в своем описании. Интересненько.

Когда первоначально создавался Java, у них был все-таки "указатель" (Вы можете в реальности видеть атавизмы от этого в виде появления ошибок типа NullPointerException). Sun хотел продвигать Java в качестве безопасного языка, и одним из достоинств Java преподносился запрет на использовании арифметики указателей, которая имеется в C++.

Разработчики пошли по пути использования другого имени для концепции указателей, формально называя их "ссылками" (references). Мне кажется это большой ошибкой, которая создает еще больше путаницы в процессе программирования.

Хорошее объяснения понятия ссылочных переменных дано в статье [2] (оно дано в специфике C++, однако здесь сказаны правильные вещи про всю концепцию reference-переменной). Слово "reference" в разработке языка программирования изначально произошло от способа, каким Вы передаете данные подпрограммам/функциям/процедурам/методам. Ссылочный параметр (reference parameter) является псевдонимом (alias) переменной, переданной в качестве параметра.

В результате Sun сделала ошибку и подменила понятия, что вызвало беспорядок. Все-таки в Java есть указатели (несмотря на то, что арифметика указателей запрещена), и если Вы признаете это, то в поведении Java найдете намного больше смысла.

Читайте также:  Outlook не сохраняет отправленные письма

[Вызовы методов]

Вызов метода foo в этом примере передает значение d; здесь вовсе не передается объект, на который указывает d!

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

Однако не имеет никакого значения, как указатели реализованы внутри Java. Вы программируете с ними на Java точно так же, как это делали бы на C или C++. Синтаксис незначительно поменялся: еще один плохой выбор в дизайне Java; здесь надо было для разыменования (de-referencing) использовать тот же синтаксис, как в C++. Например, на Java объявление:

означает абсолютно то же самое, что и на C++ объявление:

Используются указатели при этом тоже одинаково:

Итак, можно сделать следующий вывод: Java имеет указатели, и в случае использования объектов в виде параметров функции в функцию передается значение указателя. Нет никакого способа передать сам объект в качестве параметра. Вы можете передать только указатель на объект. Имейте в виду, что когда делается вызов:

то Вы не передаете объект. Вы передаете указатель на объект. Немного другой взгляд (но все-таки корректный) на эту проблему Вы также можете найти в отличной книге Peter Haggar "Practical Java".

[Remote Method Invocation (RMI)]

Когда передаются параметры в remote-методы, все становится несколько сложнее. Во-первых, мы (обычно) имеем дело с передачей данных между двумя независимыми виртуальными машинами, которые могут находиться также на разных физических машинах. Передача значения указателя не принесла бы пользы, потому что целевая виртуальная машина не имеет доступа к куче виртуальной машины, вызвавшей метод.

Вы будете часто встречаться с понятиями "pass by value" и "pass by reference", используемыми в контексте RMI. Причем они имеют некое "логическое" значение, и в реальности некорректны для надлежащего использования.

Вот что обычно подразумевается под этими фразами в контексте RMI. Обратите внимание, что здесь теряется традиционное значение терминов семантики "pass by value" и "pass by reference":

RMI Pass-by-value: действительный параметр сериализируется и передается по сетевому протоколу на целевой отдаленный объект. Сериализация по-существу "сжимает" данные от исходных данных объекта/примитива. На принимающем конце данные используются для построения "клона" оригинального объекта или примитива. Имейте в виду, что этот процесс может быть довольно дорогим по ресурсам, если фактические параметры указывают на большие объекты (или большие графы объектов). Это не вполне правильное использования термина "pass-by-value"; возможно, это надо было бы назвать "pass-by-memento".

RMI Pass-by-reference: действительный параметр, который сам по себе remote-объект, предоставлен через прокси. Этот прокси отслеживает место, где живет фактический параметр, и в любое время, когда целевой метод использует формальный параметр, происходит вызов другого remote-метода, чтобы передать обратно действительный параметр. Это может быть полезным, если актуальный параметр указывает на большой объект (или граф объектов), и здесь используется несколько обратных вызовов. Здесь использование термина "pass-by-reference" также не совсем корректно (Вы не можете поменять действительный параметр сам по себе). Лучше бы подошло название типа "pass-by-proxy".

[Обсуждение на stackoverflow.com]

Спецификация Java утверждает, что всегда передача параметров происходит по принципу pass-by-value. В Java нет такого понятия, как "pass-by-reference". Ключевым понятием для понимания является следующее – здесь на Java переменная myDog не является собакой, это указатель на собаку:

То есть, если Вы написали следующее:

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

Предположим, что объект Dog находится в памяти по адресу 42. Это означает, что методу foo будет передано 42. Пусть метод foo определен так:

Посмотрите, что произойдет. Параметр someDog установлен в значение 42.

На строке "AAA": someDog следует за объектом Dog по указателю (который находится по адресу 42), так что для этого Dog (находящегося по адресу 42) будет запрошено изменение имени на Max.

На строке "BBB": будет создан новый объект Dog. Новый объект получит новый адрес в памяти. Предположим, что этот адрес 74, так что указателю someDog будет присвоено значение 74.

На строке "CCC": someDog следует за объектом Dog по указателю (который находится по адресу 74), так что для этого Dog (находящегося по адресу 74) будет запрошено изменение имени на Rowlf. После этого выполнение метода foo заканчивается.

Давайте теперь подумаем, что же произойдет вне метода foo: изменится ли myDog?

Это ключевой момент. Если иметь в виду, что myDog является указателем, а не просто объектом Dog, то ответ будет НЕТ. myDog все еще имеет значение 42, как это и было до вызова функции; он все еще указывает на оригинальный объект Dog. Совершенно допустимо следовать по адресу myDog для изменения содержимого объекта, адрес myDog при этом остается однако неизменным.

Java работает абсолютно так же, как это делает C. Вы можете назначить указатель, передать указатель методу, следовать по указателю и изменять данные, на которые указатель указывает. Однако Вы не можете поменять место расположения объекта, на который этот указатель указывает.

На C++, Ada, Pascal и других языках, которые поддерживают pass-by-reference, Вы можете в действительности поменять переменную, которая была передана.

Если бы у Java была семантика pass-by-reference, то вышеопределенный нами метод изменился бы, и myDog указывал бы на другой объект, который был присвоен someDog на строке BBB.

Думайте о reference-параметрах как о псевдонимах для переданной переменной. Когда присвоено значение псевдониму, то это была переданная в метод переменная.

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

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

Adblock detector