Блок 1. Раздел 4. Тема 3
Работа с функциями.
Циклы внутри функции. Перегрузка имени функции по параметрам. Передача String и Scanner параметром.
Рекурсия.
Внутри функций можно писать любой код. В том числе вычисления сумм и вообще любых больших выражений часто бывает удобно делать внутри функций. Это здорово сокращает код и помогает не запутаться в нем.
Циклы внутри функции
Допустим нам нужно рассчитывать суммы вида:

50+52+54+56+58+…+600

Троеточие означает – «и так далее, аналогично»:

160+164+168+172+176+…+800

Вместо троеточия идут такие же слагаемые, каждый раз увеличиваемые на 2. Такая задача очень часто возникает как основа самых разных алгоритмов. Например, вы делаете программу для расчета времени переезда из одной точки на карте в другую. Пусть машина в начале разгоняется и едет с постоянно увеличивающейся скоростью. Нужно посчитать, какой путь она пройдет, разгоняясь, – вам придется складывать постоянно возрастающие числа. На самом деле такие или похожие суммы встречаются повсеместно, когда вам нужно учесть суммарный эффект каких-то небольших, но часто случающихся событий. Также они хороши для обучения, т.к. в них используются все основные инструменты языка. Итак заметим, что вместо того чтобы писать отдельный цикл на каждую сумму такого вида, можно всего один раз написать функцию:
/* sumFrom возвращает сумму слагаемых, начинающихся от begin, увеличивающихся с шагом step (от англ. «шаг») и завершающихся по достижении значения end */
static double sumFrom (double begin, double end, double step) {
	double sum = 0;
	double slog = begin;
	while (slog <= end) {
		sum = sum + slog;
		slog += step;
	}
	return sum;
}
/*Например, в main мы теперь можем легко рассчитывать любую сумму такого вида, в том числе как было указано в начале:
50+52+54+56+58+…+600     
160+164+168+172+176+…+800   
*/
static void main () {
	double res1 = sumFrom (50, 600, 2);
	double res2 = sumFrom (160, 800, 4);
	System.out.println (res1);
	System.out.println (res2);
/*мы теперь можем любую такую сумму рассчитать в нужном месте программы, а не просто одну единственную, как мы делали раньше, не зная функций. Теперь к примеру, для программы, считающей путь разгоняющегося автомобиля, можно постоянно в одну строку и без лишнего труда рассчитывать этот путь для сотен разных машин с разными двигателями. В этом вся польза функций – вы решили задачу один раз и пользуетесь решением сотни и тысячи раз как готовым блоком. Внутри этого блока могут быть несколько строчек, а может быть и сложный код с циклами и расчетами, размер функций не ограничен. */
}
Замечание. Циклы можно вызывать внутри функций, и наоборот функции можно вызывать внутри циклов. Проще сказать, внутри функций можно писать любой Java код, и сами функции можно вызывать в остальном Java коде.
Перегрузка имени функции по параметрам
У одного и того же имени могут быть несколько смыслов. Такое имя называют перегруженным. Например, функции можно перегружать по параметрам, когда есть одно и то же имя функции с разными параметрами. Рассмотрим пример функции максимум, перегруженной по своим аргументам:
static double max (double a, double b) {
	if (a >= b) 
		return a;
	else 
		return b;
}
static double max (double a, double b, double c) {
	if (a >= b && a >= c)
		return a;
	else if (b >= a && b >= c)
		return b;
	else
		return c;
}
Здесь две совершенно разные функции обладают одним и тем же именем max. Как же их различать между собой, и как понять, какую именно вызывает программист в том или ином случае? Они отличаются количеством аргументов – если указано два аргумента, то вызовется первая функция, а если три аргумента, то вторая. Рассмотрим их вызов в main:
static void main () {
	double res1 = max (10, 20); //вызвать max, у которой 2 параметра
	double res2 = max (0.5, -10, 60); // вызвать другую  max
}
Такая практика встречается очень часто. Например, в нашем примере с максимумами это очень удобно, ведь иначе пришлось бы ввести много имен, по сути обозначающих одно и то же – вычисление максимума.
Делаем магазин в виде функций и передаем String и Scanner параметром
Одна функция постоянно вызывает другие, и это нормально. Так, в main мы все время вызывали наши функции, например, println и sqrt. Поэтому возьмите на заметку, что вызов одной функции в другой – это абсолютно нормально. Обычно программы так и проектируют – разбивают на базовые функции, решающие простые отдельные задачки, и несколько главных функций, которые вызывают много простых. Давайте напишем магазин, который будет предлагать клиенту купить какие-то товары по определенной цене. Спрашивать про очередной товар будем по очереди, то есть должно получиться так:
Сколько вы бы хотели купить шоколадок?
Одна штука стоит 50 руб.
Ответ клиента – 3
Сколько вы бы хотели купить абрикосов?
Одна  штука стоит 15 руб
Ответ клиента – 5
Итого, ваш заказ: 50*3+15*5=225руб
Базовой функцией здесь будет один вопрос об одном товаре. Главная функция будет много раз вызывать базовую для разных товаров с разными ценами.
import java.util.Scanner;

/*Базовая функция спрашивает один вопрос и возвращает сумму за один вид товара: */
static double buy (String question, double price, Scanner scan) {
	System.out.println (question);
	System.out.println (“Одна штука стоит” + price);
	int amount = scan.nextInt ();
	return price*amount;
}

static void main () {
	double sum = 0;
	Scanner myscan = new Scanner (System.in);
	sum += buy (“Сколько вы бы хотели купить шоколадок?”, 50, myscan);
	sum += buy (“Сколько вы бы хотели купить абрикосов?”, 15, myscan);
	sum += buy (“Сколько вы бы хотели купить помидоров?”, 15, myscan);
	sum += buy (“Сколько вы бы хотели купить огурцов?”, 20, myscan);
	System.out.println (“Суммарный чек ” + sum);
}
Только представьте, сколько кода пришлось бы написать, чтобы сделать такой магазин без использования функций. Когда же мы сделали базовую функцию один раз, то в данном случае почти за бесплатно, лишь указывая новые параметры, мы сделали магазин на 4 товара, а могли бы без всякого труда и на 20 товаров. Обратите внимание – внутри функции buy мы не могли узнать вообще ничего об ответах на другие вопросы, кроме одного текущего. Значит в ней мы никак не смогли бы выставить итоговый чек – в одном вызове она знает только текущие данные, и никак не может узнать картину в целом. Значит, переменную sum, аккумулирующую данные по всем вопросам, имеет смысл завести в главной функции. Сейчас это функция main, но могла бы быть и любая другая.

Каждый отдельный вызов buy возвращает ответ на один вопрос – сколько денег следует выставить в счет за один товар. Но если сложить эти единичные чеки вместе, то получится суммарный итоговый чек. Так и сделано в этом коде. Каждое возвращаемое значение прибавляется к общей сумме sum += buy (…), и так мы можем собрать данные о покупках в целом по разным товарам.

Мы можем передавать параметры любых типов, в том числе String и Scanner. String – это по сути такой же тип как int или double, но в нем хранится целая строка – набор символов. С его помощью удобно передавать строки. При вызове функции следует взять конкретную строчку в двойные кавычки. Например, "Один томат стоит " – это строчка и это значение может принимать String.

Мы можем передавать объекты любых классов, например, Scanner. Сравните запись double price и Scanner scan. Они по своей сути идентичны, но double price – это одно дробное по названием price, а Scanner scan – это один сканер под названием scan. Внутри этого scan хранятся все данные, нужные для работы сканера. При вызове мы должны передать какой-то сканер, в данном случае в main передается myscan. Когда мы его передали, им можно свободно пользоваться, например, для считывания чисел с клавиатуры.
Рекурсия
Рекурсивными называют функции, которые вызывают сами себя. Обычно при этом меняется значение параметра. Простой пример с распечаткой:
static void somePrint (int n) {
	System.out.println (n);
	if (n > 0)
		somePrint (n-1);
}
	В результате для вызова somePrint (5) будет
5			//распечатали 5 и вызвали somePrint (4)
4			//распечатали 4 и вызвали somePrint (3)
3			//распечатали 3 и вызвали somePrint (2)
2			//распечатали 2 и вызвали somePrint (1)
1			//распечатали 1 и вызвали somePrint (0)
0			//распечатали 0 и вернулись обратно
Есть алгоритмы, которые очень удобно писать с помощью рекурсии. В том числе такие как алгоритм перебора всех слов, возможных в алфавите из заданного количества букв, отрисовка деревьев и листьев в играх, а также вся работа с деревьями как структурами данных. Но об этих алгоритмах речь будет в следующих темах.


Пока что разберем простой пример вычисления факториала заданного числа. Факториалом для числа n называется произведение 1*2*3*4*…*n. Обозначается восклицательным знаком: n!


Факториал бывает нужен для подсчета всевозможных комбинаций различных событий. Не будем углубляться в теорию вероятности, но укажем, что если вам нужно подсчитать вероятность какого-то события, то без факториала вам не обойтись.*
static int fact (int n) {
	if (n > 0) {
// используем fact, как будто функция уже готова для n-1:
		return n * fact (n-1);
	}
	else if (n == 1)   //базовый случай и конец рекурсии
		return 1;  
//когда n мало, то задачу решить легче
//а более сложные случаи строятся на основе малого n
	else 
		return -1; // отбросим отрицательные числа
}
Итак, в рекурсивных алгоритмах мы выражаем задачу через саму себя, но уже для более простого случая. Проходя по вложенным вызовам функции, мы дойдем до базового случая, на котором следует решить задачу в самой простой её формулировке и полностью остановить функцию, вернув результат.
Самое главное мы уже освоили. Теперь время решать задачи. Ответьте на несколько вопросов, чтобы лучше усвоился новый материал:
Вопрос 1. Что значит объявить переменную? (ответы по кнопке "плюс")
Это значит назвать число заданного типа (целые, дробные или коды букв) каким-то своим именем. Под это число будет выделено место в оперативной памяти. Это число в дальнейшем можно менять и использовать в любых расчетах.
Вопрос 2. Что будет, если объявить две переменных с одним и тем же именем внутри одного блока фигурных скобок { } ?
Компилятор выдаст ошибку, потому что с его точки зрения, вы говорите, что одно и то же имя теперь может отвечать двум разным переменным. Он не может разобраться, какой же из двух переменных это имя отвечает, и сообщит, что это имя двусмысленное и его нужно изменить.

То есть в таком коде есть ошибка:
int x = 100;
int x = 200; // ошибка, x уже был объявлен
А в таком коде все нормально:
int x = 100;
x = 200; //поменять значение x на 200
//именно отсутствие int в начале строчки говорит о том, что x просто меняет свое значение, и мы не пытаемся завести новую переменную.
Задача 1. Ниже дан код с двумя переменными. Что будет распечатано на экране?
package javaapplication1;
public class JavaApplication1 {
public static void main(String[] args) {
  int x = 100;
  x = x*10/2 + x;
  System.out.println (x);

  double y = x - 5.5;

  System.out.println (y);
 }   
}
Ответ
600
594.5
Решение
Посчитаем выражения для введенных в коде переменных x и y.
Сначала x = 100;
Затем в строчке
x = x*10/2 + x;
компилятор сначала рассчитывает числовое выражение справа от равно
на основе старого значения x
100*10/2 + 100, то есть 600.
Поэтому строчка читается как "икс присвоить 600". Это значение и будет распечатано на экране.

На его основе будет вычислено значение y = x - 5.5, то есть 594.5.
Переменная y обладает типом double, то есть дробное, поэтому в ней можно сохранить дробную часть 0.5 . Если бы y было целым, то в такой переменной невозможно было бы сохранить дробную часть, и компилятор подчеркнул бы эту строчку красным и выдал ошибку. В ошибке было бы сказано, что идет отбрасывание дробной части, и это может привести к потере точности.
Решайте больше задач по этому разделу здесь.
В следующей теме расскажем подробнее
о типах данных в Java