Блок 1. Раздел 4. Тема 1

Функции в Java -
разбиваем код на блоки

Часто какой-то код в программе нужно повторять много раз в разных местах. Тогда этот код можно поместить в функцию. Функция - это блок кода, у которого есть имя, параметры и возвращаемое значение. Функции можно вызывать по имени в любых местах, в main или в других функциях. Это гораздо удобнее, чем без конца копировать кучу кода, когда вам нужно выполнить его несколько раз.
Функцией называют блок кода, у которого есть имя, параметры и возвращаемое значение. По имени можно вызывать этот блок в любом месте программы.

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

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

Посмотрим код функции для возведения числа в квадрат. Сначала пишется заголовок функции, где указывается слева направо тип возвращаемого значения, имя функции и в скобках её параметры. Функцию возведения в квадрат часто называют sqr от английского слова square - "квадрат". Поскольку её нужно число, возможно дробное, и возвращает она тоже число, то тип параметра будет double и тип возвращаемого значения тоже будет double .
// Наш заголовок читается так:
// статическая, возвращает double, называется sqr, принимает параметром один double
static double sqr ( double number) {
// Фигурная скобка открывает тело функции - её блок кода.

// Чтобы посчитать квадрат числа, нужно его умножить само на себя.

// Результат перемножения нужно вернуть из функции словом return вот так:
    return number*number;
}
// фигурная скобка закрывает блок функции.
Слово static в заголовке функции пока что будем писать всегда, а разберём подробнее, когда будем делать свои классы.

Фигурные скобки {...} обрамляют блок функции. Если закрывающую не поставить, то компилятор решит, что у функции нет конца и так и скажет "missing }", то есть "отсутствует }". А если наоборот закрывающих слишком много, то скажет "unbalanced brackets", то есть "скобки не сбалансированы" как про круглые (), так и фигурные {}

Теперь посмотрим на вызов нашей функции, например, в main. Чтобы вызвать функцию нужно написать её имя и в скобках указать значения параметров. Результат работы функции имеет смысл сохранить в какой-нибудь переменной, чтобы потом ей пользоваться, к примеру, чтобы распечатать:
public static void main (String[] args) {
// Объявим x равный 10
    double x = 10;

// Вызвать sqr с параметром x:
    double result = sqr (x); // Возвращаемое значение sqr подставится прямо сюда
// получится double result = 100
    System.out.println ("10 в квадрате будет " + result);
}
При вызове функции программа выполняет весь её код, вместо параметров подставляя значения, указанные в скобках. Сейчас мы сделали вызов sqr (x) . Значит в параметр подставится число x, а оно сейчас равно 10. В то самое место кода, где мы написали вызов sqr (x)

Тип переменной для хранения результата функции должен соответствовать типу самого результата, поэтому в нашем случае мы сделали double result, а не, например, int, ведь sqr возвращает double.

Теперь на основе функции sqr сделаем в main простой калькулятор - он будет спрашивать у пользователя числа и возводить их в квадрат. Заодно посмотрим, как ещё можно использовать функции.
public static void main (String[] args) {

    System.out.println ("Это программа для расчета квадратов чисел");
    System.out.println ("Введите число:");

// Считаем в x дробное число с клавиатуры

    Scanner scan = new Scanner (System.in); // Создадим сканер клавиш клавиатуры    

// Кстати тут мы тоже вызываем функцию - nextDouble, только она уже есть в Java
// Мы указываем scan, чтобы подчеркнуть, что вызов именно 
// для нашего конкретного сканера scan, созданного строчкой выше
    double x = scan.nextDouble ();

// Вызвать sqr с параметром x:
    double result = sqr (x); // Возвращаемое значение sqr подставится прямо сюда
    System.out.println (x + " в квадрате будет " + result);

    System.out.println ("Введите новое число:");

// Мы используем уже введенную раньше переменную x,
// поэтому указываем просто x, а не double x:
    x = scan.nextDouble ();

// Снова вызвать sqr с параметром x:
    result = sqr (x); // Возвращаемое значение sqr подставится прямо сюда
    System.out.println (x + " в квадрате будет " + result);

// Теперь посмотрим другой вариант для той же задачи:
    System.out.println ("Введите новое число:");
    x = scan.nextDouble ();

// sqr(x) в программе будет уже просто число, и его можно подставлять куда угодно:
    System.out.println (x + " в квадрате будет " + sqr (x) ); 

// И самый короткий вариант:
    System.out.println ("Введите новое число:");
    System.out.println ("В квадрате это будет " + sqr ( scan.nextDouble () ) ); 
}
В последней строчке мы вызвали сразу nextDouble, sqr и println:
System.out.println ("В квадрате это будет " + sqr ( scan.nextDouble () ) );
Компилятор идёт слева направо и видит, что здесь стоит распечатка println. Он пробует её выполнить, но обнаруживает, что строчку для распечатки он получит, только выполнив функцию sqr, а значит, он выполняет сначала её. Но тут снова, чтобы получить число для возведения в квадрат, нужно выполнить scan.nextDouble .

Поэтому прежде всего заработает scan.nextDouble и число будет введено с клавиатуры. Оно будет передано на вход функции sqr и будет возведено в квадрат. Тогда уже оно попадёт в строчку и будет распечатано на экране в функции println.

Получается, что возвращаемое значение одной функции можно сразу дать в качестве входного параметра для другой функции. Это нормально, ведь возвращаем мы число, и на вход нам тоже нужно число. Но при этом, поскольку мы сейчас не заводили никаких переменных, мы уже не сможем использовать результаты работы этой функции где бы то ни было ещё в коде программы. Итак, каскад вызовов делают для краткости кода, но если вы хотите использовать результат где-то ещё в программе, то нужно писать несколько строк и сохранять значения в переменных.

Сейчас в нашем небольшом калькуляторе мы 4 раза подряд совершили один и тот же набор действий - считать число, возвести в квадрат, вывести на экран. Имеет смысл для этих действий завести свою функцию, и таким образом сократить объём кода:
// Для полноты картины приводим нашу функцию sqr
static double sqr (double num) {
    return num*num;
}
// Назовём функцию ask_and_calc, то есть спросить и посчитать квадрат.
// Она ничего не возвращает, поэтому вместо типа возвращаемого значения
// пишется void, по-английски пустота.
// Данные функция сама возьмёт с клавиатуры и параметры ей не нужны.
// Поэтому в круглых скобках ничего нет - это будет функция без параметров.
static void ask_and_calc () {
    System.out.println ("Введите число:");  
    Scanner scan = new Scanner (System.in);
    double x = scan.nextDouble ();
    double result = sqr (x); 
    System.out.println (x + " в квадрате будет " + result);  
}

// Кстати main тоже ничего не возвращает, поэтому он void .
// А параметром идёт String[] args, что означает "набор String".
// Эти строки можно указывать при запуске приложения из командной строки,
// но мы их пока не будем использовать.
public static void main (String[] args) {
    System.out.println ("Это программа для расчета квадратов чисел");

// Больше не копируем один и тот же код 4 раза, а просто вызываем функцию
    ask_and_calc(); // название читается по-английски, и сразу понятен смысл
    ask_and_calc();
    ask_and_calc();
    ask_and_calc();
}
Итак, функция ask_and_calc ничего не возвращает, а значит вместо типа возвращаемого значения пишется void - "пустота". Параметров у этой функции тоже нет, так что пишутся пустые скобки () .

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

О термине "процедура". Строго говоря, функцией называется блок кода, который что-то возвращает. Когда используется слово void, и ничего не возвращается, то говорят, что это процедура. Но разница так тонка, что в обиходе слова «функция» и «процедура» используются практически как синонимы.

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

О термине "аргумент". Также параметры ещё называют аргументами. Так что можно сказать "параметр функции", а можно "аргумент функции" и это будет одно и то же.

Наш main можно было бы сократить ещё больше, если бы мы вызывали ask_and_calc в цикле вот так:
public static void main (String[] args) {
    System.out.println ("Это программа для расчета квадратов чисел");

// спросить число и возвести его в квадрат 4 раза
    for (int i = 0; i < 4; i ++)
        ask_and_calc(); 
}
Когда мы делаем функцию и затем вызываем её в коде много раз или пишем цикл, который вызывает один и тот же код много раз, мы экономим объём программы, и при этом делаем её понятной для чтения человеку. Это называется принцип повторного использования кода. Один из краеугольных камней современного программирования.

Заметьте, что в нашем примере мы запускаем функцию sqr внутри функции ask_and_calc, а её внутри main. Так что как видите, запуск одной функции внутри другой - это нормально и удобно.

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

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

Это правило даёт нам возможность в разных функциях называть переменные одинаковыми именами. Это бывает удобно, например, любой ответ пользователя на вопрос можно поместить в переменную answer, по-английски "ответ". В одной функции answer будет означать ответ на один вопрос, а в другой функции уже совсем на другой.

То же самое касается параметров функций. В разных функциях могут быть параметры с одинаковым названием, но это их никак не связывает, каждый выполняет свою задачу для своей функции.
Вы дочитали тему до конца - это успех :) Пришло время решать задачи. Ответьте на несколько вопросов, чтобы лучше усвоился новый материал:
Вопрос 1. В чем состоит принцип повторного использования кода?
Он состоит в том, что повторяющиеся куски кода можно использовать повторно. Так устроен цикл - один и тот же код в нём прокручивается много раз. Так устроена функция - мы запускаем её много раз, а пишем её код всего один раз. В будущем мы пройдём классы, внутри которых можно создать группу функций. Классы также помогают использовать код повторно ещё более эффективным образом.
Вопрос 2. Как вернуть из функции две переменных с помощью return? (в материалах этого вопроса не было)
На уровне данного раздела - никак. Можно возвращать только по одной переменной. В дальнейшем мы пройдём классы, внутри которых можно заводить группу переменных, и тогда сможем возвращать объект класса, а в нём все необходимые переменные.

Ещё в следующем разделе мы пройдём массивы чисел, и можно будет возвращая массив, передавать наборы чисел одного и того же типа.
Задача 1. Сколько раз в этом коде будет считано число с клавиатуры? В этой теме мы просили пользователя ввести число и выводили на экран квадрат этого числа. Будет ли данный краткий код делать то же самое?
package javaapplication1;
import java.util.Scanner;

public class JavaApplication1 {
 static double sqr (double num) { 
     return num*num;
 }

 public static void main(String[] args) {
     Scanner scan = new Scanner (System.in);
     System.out.println ("Введите число");
     System.out.println (scan.nextDouble () + "в квадрате будет " + sqr ( scan.nextDouble () ) );
 }   
}
Ответ
Будет введено два разных числа - одно для первого запуска scan.nextDouble () и одно для второго запуска. Дело в том, что в строке

System.out.println (scan.nextDouble () + "в квадрате будет " + sqr(scan.nextDouble () ) ); функция nextDouble вызвана дважды. Поэтому программа сработает совсем иначе, чем ожидается. Мы хотели считать число и вывести на экран квадрат этого числа. А здесь получится, мы сначала считали одно число - оно будет выведено на экран вместо первого запуска nextDouble. Затем считали второе число, и уже его квадрат будет выведен на экран.

То есть считываем лишнее число, а первое вообще непонятно зачем нужно. Имейте это в виду, имея дело в такими функциями как nextDouble. Чтобы считать одно число и всё время работать с ним, его имеет смысл сохранить в переменную:
double num = scan.nextDouble ();
System.out.println (num + "в квадрате будет " + sqr(num ) );
Теперь nextDouble вызвана всего один раз, и поэтому в распечатке в начале и в конце будет использоваться одно и то же число, как мы и хотели.

Решайте больше задач по этому разделу здесь.
В следующей теме на основе функций сделаю программу "Угадай пропущенную букву"