Методи – деклариране, имплементация и извикване на методи. Видове методи.

 

Методи.

Методите в Java са познатите от други езици функции. Тенденциите в съвременното програмиране се базират строго на стратегията, позната от древността „Разделяй и владей“. Тоест, разделянето на задачата на множество по-малки подзадачи би помогнало за по-лесно осмисляне, по-добра четимост, по- високо ниво на контрол, по-лесно откриване на грешки и тн. Естествено причината за това е намаленият обем на анализираните подзадачи. От друга гледна точка, добра практика е писането на код, който може да бъде използвам многократно или т.нар. code reusing. Естествено, всичко, което може да напишем като код в една програма, би могло да бъде написано и в една единствена главна функция. Не само че е лоша практика, но е почти невъзможно за четене, анализ и разбиране. А един код, обикновено се чете в последствие от много програмисти, които правят промени по него или го доразвиват.

Определение::

Метод наричаме обособена част на една програма, който реализира решение на даден проблем.

Методите, от гледна точка на обектноориентираното програмиране, са начинът един обект да покаже и развие своята функционалност. Тоест, ако даден обект може да прави нещо, то това „нещо“ се оформя и декларира като негов метод. В последствие, ако желаем да накараме обектът да извърши това „нещо“(разбирайте свирене, пеене, рисуване, печатане на конзола, изчисляване разстоянието между вас и Слънцето и тн.), то просто извикваме метода на конкретния обект, който сме създали. Счита се за добра практика код, който се използва повече от веднъж, да се обособи в отделен метод.

Деклариране на метод::

Методи се декларират навсякъде между отваряща и затваряща скоба на дадения клас, но не и в рамките на друг метод или конструктор. Например main методът, който вече познавате:

public class Example{
public static void main(String[] args){
//some operators here….
}
}

Нека дадем обобщен модел на декларация на метод:
[public] [static] [return_type] [method_name]([param_list])

 

[public] – модификатор за достъп – определя обхвата на видимост на дадения метод.

[static] – незадължителна ключова дума е и определя метода като статичен. Повече за това след малко в раздел „Статични методи“.
тип на връщаната стойност – Може да бъде всеки тип данни, с който Java разполага.
– име на метода – Името е уникално и трябва да спазва определени правила за сформиране, катонапример: трябва да съдържа глагол, да започва с малка буква и да няма разделители. С името на метода по-късно се вика самият метод.
[] – списък с параметри– Записва се в скоби, като се изброяват всички декларации на параметри, от които методът има нужда, разделени със запетая. Всеки тип данни е следван от формално име на параметъра, като не може да има два с едно и също име, не може да има един тип данни и две имена на параметри след него , както е възможно ако просто декларираме променливи от един и същи тип. Методът може да приема и променлив брой параметри.
Например:

private void printInfo(String inf){
System.out.println(“The information is:”);
System.out.println(inf);
}

Методът може да приема и final параметри. Това са такива параметри, които веднъж придадени на метода, не могат да бъдат модифицирани в рамките му. Например:

 private String getFullName(final String firstName, final String lastName){
String fullName = firstName + “ ” +lastName;
return fullName;
}
            • Сигнатура на метод: Всеки метод има съвкупност от атрибути, които го идентифицират еднозначно – това е т.нар. сигнатура или спецификация на метода. Тя се състои от двойката име и списък с параметри
              Сигнатура = f(name, parameter_list);
              Важно уточнение е, че към сигнатурата не спада типа на връщан резултат. Причината за това е, че по време на извикване на метода не може да се идентифицира кой метод да се извика, в случай че има повече от 1 метод с еднакви имена и параметри, но с различен тип на връщана стойност.
            • Имплементация – Представлява тялото на метода, заключено между отваряща и затваряща къдрава скоба след декларацията на метода. Логиката и функционалността на метода се намират именно в тялото на метода. Метод може да се остави и като неимплементиран, стига да е деклариран като абстрактен. Ще разберем повече за абстрактните методи малко по-късно.
            • Локални променливи – Локални променливи са всички променливи, които са декларирани в рамките на даден метод. Те са достъпни само и единствено в метода. Тоест областта и на съществуване е заключена между реда на декларацията и и затварящата скоба на метода.
            • Блок – наричаме програмен фрагмент заключен между две къдрави скоби. Ако декларираме променлива в блок, то тя отново е локална и важи горното правило за областта и на съществуване. Тялото на метода представлява програмен блок. След логически оператори if, else, switch, case и тн. обикновено, ако кодът е повече от 1 ред, то той се поставя в програмен блок. Същото важи и за циклите for и while- ако логиката им се състои от повече от 1 ред код, то той се огражда в блок и важат отново правилата за декларираните в рамките на блока локални променливи.

Извикване на метод:
Извикването на метода става чрез написване на името му, задължително следвано от скоби с или без параметри в тях, в зависимост от сигнатурата на метода.

Къде може да се извиква метод?
Навсякъде в програмни блокове, в други методи, в конструктори. Така че, обикновено методите викат един друг. Метод може да се извика и в себе си- позната тактика като рекурсия.

Разлика между параметри и аргументи:
Разликата е на ниво деклариране и на ниво извикване на метод. При декларирането на метод, ние описваме сигнатурата му, като освен името, посочваме и списък с параметри. Тоест вътре в списъка просто декларираме какви и колко на брой параметри ще приема метода, за да си свърши, образно казано, работата. Затова параметрите на ниво декларация на метод са известни още като формални параметри – просто и формално деклариране на това какво е разрешено и какво трябва да се подаде на метода при извикване. Когато обаче извикваме метода, ние трябва да му подадем истински параметри – тоест такива, които имат стойност. Затова те се наричат фактически параметри. Те задължително трябва по тип да са като формалните, а каква стойност ще имат, няма значение – въпрос на логика в имплементацията на метода е да отсее аргументите с логически достоверни стойности. Така че, вторите се наричат още аргументи на метода и трябва задължително да са инициализирани.
Контрол над изпълнението на програмата:
За разлика от декларативното програмиране, при което програмата представлява последователност от команди, които се изпълняват в строго дефиниран ред, обектноориентираната програма има по-сложен вид. Тя разчита на взаимодействието между обектите. А обектите в голяма степен притежават методи, като един вика друг, той, за да си свърши работата вика трети метод, който може да е на същия обект, или пък на друг и тн. Тоест картинката при ооп програмата става малко по-„цветна“ от гледна точка на разклонения на логиката и взаимодействия между обекти. Нека дадем пример:

public class Example{
public void callSum(int a, int b) {
(2.1) int theSum = add(a, b);
(2.2) System.out.print(theSum);
}

public int add(int value1, int value2) {
(3.1) return value1 + value2;
}
public static void main(String[] args){
(1.1) System.out.println(“We will call callSum() method to calculate 2+3:”);
(1.2) callSum(2,3);
(1.3) System.out.println(“The method callSum() finished and the main begins.”);
}
}

Какво се случва в горната програма?
При стартиране, програмата стартира с изпълнение на main метода. Изпълнява си ред (1.1), с което принтира съобщението. След това на ред (1.2) се вика метод callSum(0), следователно управлението на програмата се пренасочва към кода в този метод. Изпълнява се ред (2.1), но в него се вика метод add(), следователно управлението на програмата се предава към него. Изпълнява се ред (3.1), с което метод add() приключва работата си( с return) и се връща отново там, от където е извикан – метод callSum() на ред (2.1). На този ред няма повече оператори и не се викат повече методи, така че преминава към ред (2.2), който принтира сумата. От там метода приключва своето изпълнение и се връща там, от където е бил извикан – метод main, ред (1.2). На този ред няма повече оператори и извиквания на методи, следователно минава на последния ред (1.3), принтира си съобщението, стига до последната затваряща скоба на main и с това изпълнението на програмата приключва.

Подаване на аргументи от примитивен и от референтен тип:

При подаване на аргументи към метод, трябва да се има предвид едно основно правило – че в Java аргументите към метод се предават по стойност. Философията на предаването на аргументи на метод е, че когато се подава аргумент, винаги стойността му се копира в параметъра от декларацията, независимо от какъв тип е.
o Подаване на аргументи от примитивен тип:
Когато предадем аргумент от примитивен тип към метод, то стойността му веднага се копира в параметъра от декларацията на метода. В последствие този параметър с придобита стойност се използва в тялото на метода. Ако аргумента е от примитивен тип, както е в случая, който разглеждаме, то ако методът се опитва да го модифицира, той всъщност се опитва да модифицира копието на този аргумент. Така че, ако придадем примитивен аргумент към метод и той го променя в тялото си, то след излизане от метода, аргумента ще има същата стойност, каквато е имал преди да се подаде на метода. Например:

public static void main (String[] args){
int a = 5;
System.out.println(“Before invoking modifyMethod() a = ”+a);
modifyMethod(a);
System.out.println(“After invoking modifyMethod() a = ”+a);

}
public static void modifyMethod(int number){
number = 1000;
System.out.println(“ModifyMethod modifying a = ”+number);
}

Ако изпълним горния main метод, ще видим следния изход:
Before invoking modifyMethod() a =5
ModifyMethod modifying a = 1000
After invoking modifyMethod() a = 5

Това е съвсем нормално, защото методът modifyMethod() се опитва да модифицира предадения му аргумент, но всъщност не модифицира „а“, a копието на „а“, записало се в параметъра „number“.

o Предаване на аргумент от референтен тип:
Когато се предава аргумент от референтен тип, отново се спазва гореспоменатата философия, че стойността му се копира в параметъра от декларацията. Нека да помислим каква е стойността на една променлива от референтен тип? Стойността на референтната променлива е адрес на област в Heap-а, където стоят реалните стойности на референтния тип. Тост самият обект не се копира, се копира единствено стойността на референцията(адресът), сочещ към него.
picture13

Когато променливата е от референтен тип, независимо дали е масив, или друг тип, то винаги оперираме с името на променливата, която както виждате е адрес към място в Heap-a.
Нека дадем пример:

public class Main{
public static void main(String[] args){
Foo f = new Foo("f");
changeReference(f); // It won't change the reference!
modifyReference(f); // It will modify the object that the reference variable "f" refers to!
}
public static void changeReference(Foo a){
Foo b = new Foo("b");
a = b;
}
public static void modifyReference(Foo c){
c.setAttribute("c");
}
}

1. Декларираме обект от тип Foo с име f и го инициализираме с конструктора на класа Foo с атрибут “f”. Ето какво се случва в паметта:
Foo f = new Foo(“f”);
picture14

2. В метода changeReference() е деклариран обект от тип Foo в списъка с параметри, който се казва „а“ и докато не му се предаде реален обект, неговата стойност е null:
public static void changeReference(Foo a)
Тоест състоянието на паметта е следното:
picture15

3. В момента, в който извикаме метод changeReference() и му предадем като аргумент новосъздадения обект „f“, то в „а“ ще се копира стойността на „f“.
changeReference(f);
Ето какво се случва в паметта:
picture16

В „а“ се е копирала стойността на „f“, тоест адресът, към който сочи „f“. Тоест сега и двете променливи сочат към един и същи адрес.
4. Декларираме обект от тип Foo, който се казва „b“ и го инициализираме с нов обект от същия тип, чрез конструктора му, с атрибут „b“:
Foo b = new Foo(“b”);
В паметта се случва следното:
picture17

5 . Реинициализираме „a“ с нова стойност – „b“ :
a = b;
Ето какво се случва в паметта:
picture18

Обектът „а“ вече не сочи към „f“, а към обект „b“. Така че методът по никакъв начин не модифицира обектa „f“.
6. Когато извикаме метод modifyReference (Foo c), обект „с“ се създава и в променливата „с“ се копира стойността на предадения аргумент „f“:
modifyReference(f);
Ето какво се случва в паметта:
picture19

7. Извикването на c.setAttribute(“c”); върху обекта „с“ ще модифицира оригиналния обект, към който сочи и „f“.
Ето какво се случва в паметта:
picture20

Така работи предаването на аргументи в Java.
Методи с променлив брой аргументи(var-arg):
Методите, които разгледахме до момента, съдържат в декларацията си списък с параметри, които са декларирани по тип и са конкретен брой. За да предадем аргументи с предварително недефиниран брой към метод, можем да подходим чрез старата техника, използваща масив от конкретния тип като параметър на метода. В този случай можем да подадем масив с различен брой аргументи и вътре в метода да ги използваме както намерим за добре. Например:

public void printSomething(int[] intArray){
for(int i : intArray){
System.out.println(i); // or do something else with the //elements
}
}

След пускането на Java 5 е възможно да избегнем предварителното деклариране на масив, за да го предадем към метода с произволен брой аргументи. Декларирането му става, като в списъка с параметри, параметърът, който желаем да направим да бъде с произволен брой, го декларираме като <тип> …<име>. Различното е, че слагаме „…“ между тип и име. Реално „отдолу“ компилаторът приема тези аргументи в масив от тип <тип> и оперира с него така, както ще го направи и ако подадем масив от тип <тип>. Реално за компилатора става без значение дали ще декларираме <тип>… или <тип>[] (Например: int… или int[]) . Според тази логика main методът също би могъл да се запише по по-лесния начин:
public static void main(String … args){
Естествено, няма да има проблеми при компилация.

Важно!
o Параметърът с променлив брой винаги се поставя на последно място в списъка с параметри!
o В един списък с параметри може да има най-много 1 с променлив брой!
Нека дадем пример:

public class VarArgsExample
{
public static void main (String [] args)
{
System.out.println ("Average: " +
avg (20.3, 3.1415, 32.3));
System.out.println ("Concatenation: " +
concat ("Hello", " ", "World"));
System.out.println ("Maximum: " +
max (30, 22.3, -9.3, 173.2));
System.out.println ("Minimum: " +
min (30, 22.3, -9.3, 173.2));
System.out.println ("Sum: " +
sum (20, 30));
}

static double avg (double ... numbers)
{
double total = 0;
for (int i = 0; i < numbers.length; i++)
total += numbers [i];
return total / numbers.length;
}

static String concat (String ... strings)
{
StringBuilder sb = new StringBuilder ();
for (int i = 0; i < strings.length; i++)
sb.append (strings [i]);
return sb.toString ();
}

static double max (double ... numbers)
{
double maximum = Double.MIN_VALUE;
for (int i = 0; i < numbers.length; i++) if (numbers [i] > maximum)
maximum = numbers [i];
return maximum;
}

static double min (double ... numbers)
{
double minimum = Double.MAX_VALUE;
for (int i = 0; i < numbers.length; i++)
if (numbers [i] < minimum)
minimum = numbers [i];
return minimum;
}

static int sum (int ... numbers)
{
int total = 0;
for (int i = 0; i < numbers.length; i++)
total += numbers [i];
return total;
}
}

Статични методи:
Статичните методи са известни още като „методи на класа“. Те не принадлежат на нито една инстанция на класа. Тоест не е нужно да създавате обект от даден клас и през него да извикате метода. Нормалните методи се извикват през обект, защото най-вероятно, за да си свършат работата, имат нужда от определено поле на дадения обект- тоест обикновените методи са свързани с конкретни стойности на обекта. Статичните методи не зависят от никой обект и от никоя клас данна. Най-много може да зависят от статично поле на класа. Статичните полета на класа са като нормалните полета, но с разлика, че те отново принадлежат на класа и не зависят от неговите инстанции- тоест те са общи за всички обекти – например известни константи, други справочни данни или коефициенти, броячи и тн.
Важно!
• Можем да използваме статични методи и статични полета навсякъде в други методи.
• Обикновени(нестатични) методи и обикновени полета не можем да използваме в статични методи.
• В статични методи можем да викаме само статични методи и да използваме само статични полета.
Например:

public class Bicycle {

private int cadence;
private int gear;
private int speed;
private int id;
private static int numberOfBicycles = 0;

public Bicycle(int startCadence, int startSpeed, int startGear){
gear = startGear;
cadence = startCadence;
speed = startSpeed;

// increment number of Bicycles
// and assign ID number
id = ++numberOfBicycles;
}

// new method to return the ID instance variable
public int getID() {
return id;
}
...
}

Overloading:
Както казахме, това, което различава един метод от друг е т.нар. сигнатура на метода. Да припомним, че тя се състои от името на метода и списъка с параметри. Тоест компилатора не разпознава методите по име, а по име и параметри- за него има значение дали параметрите са от същия тип, като тези, които са декларирани, дали са в същата последователност и дали са същия брой, ако методът е с константен брой параметри. Какво ще се случи, ако имаме 2 метода в рамките на един клас, които се различават само по списъците си с параметри? Отговорът е „overloading“ – ще имаме т.нар. разновидност от методи. Те се казват по един и същи начин, но кой от тях ще се извика от компилатора зависи от това какви и колко аргумента ще предадем. Нека дадем пример:

public class DisplayOverloading
{
public void disp(char c)
{
System.out.println(c);
}
public void disp(char c, int num)
{
System.out.println(c + " "+num);
}
}
public class Sample
{
public static void main(String args[])
{
DisplayOverloading obj = new DisplayOverloading();
obj.disp('a');
obj.disp('a',10);
}
}

Изход:
a
a 10

• Примери за невалиден overloading:
int myМethod(int a, int b, float c)
int myМethod(int var1, int var2, float var3)

Ще даде Compile time error: Argument lists are exactly same! Както виждате не е достатъчно да сменим имената на параметрите, за да „излъжем“ компилатора.
int mymethod(int a, int b)
float mymethod(int var1, int var2)

Ще даде Compile time error: Argument lists are exactly same! Както виждате не е достатъчно просто да сменим типа на връщания резултат, просто той не влиза в сигнатурата на метода.

Задачи:
Създаване на body mass index калкулатор(BMI). BMI калкулаторът пресмята вашият индекс по определена формула, спрямо теглото, което ще му подадете.
1. Създайте клас BMICalculator.
2. Създайте и имплементирайте метод printIntroduction() – той ще се извиква всеки път при стартиране на програмата. Той ще трябва да информира потребителя какво прави програмата, която е пуснал.
3. Създайте метод getBMI(), който ще „поиска“ и ще „събере“ информация от потребителя за неговото тегло и височина. Нека да приема като аргумент Scanner, който чете от клавиатурата. В имплементацията на метода ще трябва да подканите потребителя да въведе височината си в инчове(За да си пресметнете височината използвайте, че 1см.= 0.3937 inch) и теглото си в паунди(използвайте формулата
kg = lb / 2,2046). Нека за сега методът ви връща void.
4. Създайте метод bmiFor(), който по подадени тегло и височина, изчислява body mass index и го връща като резултат. Формулата за индекса е: weight*703/ (height*height).
5. Направете така, че методът getBMI() да връща като резултат, резултатът, който връща методът bmiFor().
6. Направете метод getStatus(). Той трябва да връща като резултат един от символните низове: underweight, normal, overweight, obese. По статистики на център за здраве, тези 4 характеристики се дават съответно ако: индексът е не повече от 18,5, ако индексът е не повече от 25, ако индексът е не повече от 30 и ако е над 30.
7. Създайте метод ReportResults, който приема като параметри: пореден номер на резултата, BMI индекс, статус. Той трябва да изведе на конзолата подходящо съобщение, с което уведомява, че човекът с номер Х има BMI= х.х, закръглен до втория знак. За закръглянето изпозлвайте Math.round(BMI);
8. В main метод на програмата създайте Scanner, който чете от стандартен вход, подайте го към getBM(), после по върнатия от него резултат вземете статуса с метод getStatus() и извикайте метода reportResults(), за да изведете даннитe.

Явор Томов,  Даниел Джолев

3 thoughts on “Методи – деклариране, имплементация и извикване на методи. Видове методи.”

  1. public class Example{
    public void callSum(int a, int b) {
    (2.1) int theSum = add(a, b);
    (2.2) System.out.print(theSum);
    }

    public int add(int value1, int value2) {
    (3.1) return value1 + value2;
    }
    public static void main(String[] args){
    (1.1) System.out.println(“We will call callSum() method to calculate 2+3:”);
    (1.2) callSum(2,3);
    (1.3) System.out.println(“The method callSum() finished and the main begins.”);
    }
    }
    Не трябва ли методите да бъдат static за да могат да бъдат извикани

Leave a Reply

Your email address will not be published. Required fields are marked *