JavaRush /Blogue Java /Random-PT /Métodos, seus parâmetros, interação e sobrecarga

Métodos, seus parâmetros, interação e sobrecarga

Publicado no grupo Random-PT
Olá de novo! Na última palestra conhecemos classes e construtores e aprendemos como criar os nossos próprios. Métodos, seus parâmetros, interação e sobrecarga - 1Hoje examinaremos mais de perto uma parte integrante das classes como os métodos. Um método é um conjunto de comandos que permite realizar alguma operação em um programa. Em outras palavras, um método é uma função; algo que sua classe pode fazer. Em outras linguagens de programação, os métodos são frequentemente chamados de “funções”, mas em Java a palavra “método” se tornou mais popular :) Na última palestra, se você se lembra, criamos métodos simples para a classe Cat para que nossos gatos pudessem miar e pular:
public class Cat {

    String name;
    int age;

    public void sayMeow() {
        System.out.println("Meow!");
    }

    public void jump() {
        System.out.println("Jumping gallop!");
    }

    public static void main(String[] args) {
        Cat barsik = new Cat();
        barsik.age = 3;
        barsik.name = "Barsik";

        barsik.sayMeow();
        barsik.jump();
    }
}
sayMeow()e jump()são métodos da nossa classe. O resultado do seu trabalho é a saída para o console:
Мяу!
Прыг-скок!
Nossos métodos são bastante simples: eles simplesmente imprimem texto no console. Mas em Java, os métodos têm uma tarefa principal - eles devem executar ações nos dados de um objeto . Altere o valor dos dados de um objeto, transforme-o, envie-o para o console ou faça outra coisa com ele. Nossos métodos atuais não fazem nada com os dados do objeto Cat. Vejamos um exemplo mais claro:
public class Truck {

    int length;
    int width;
    int height;
    int weight;

    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}
Por exemplo, temos uma classe que representa um caminhão - Truck. Um reboque de caminhão tem comprimento, largura e altura, além de peso (isso será necessário mais tarde). No método, getVolume()realizamos cálculos – transformamos os dados do nosso objeto em um número que indica o volume (multiplicamos comprimento, largura e altura). Este é o número que será o resultado do método. Observe - na descrição do método está escrito public int getVolume. Isso significa que o resultado deste método deve ser um número no formato int. Calculamos o resultado do método e agora devemos devolvê -lo ao nosso programa que chamou o método. Para retornar o resultado de um método em Java, é usada a palavra-chave return.
return volume;

Parâmetros do método

Os métodos podem aceitar valores como entrada, que são chamados de “parâmetros do método”. Nosso método atual getVolume()na classe Trucknão aceita nenhum parâmetro, então vamos tentar expandir o exemplo com caminhões. Vamos criar uma nova classe - BridgeOfficer. Um policial está de plantão na ponte e verifica todos os caminhões que passam para garantir que suas cargas não excedam o limite de peso permitido.
public class BridgeOfficer {

    int maxWeight;

    public BridgeOfficer(int normalWeight) {
        this.maxWeight = normalWeight;
    }

    public boolean checkTruck(Truck truck) {
        if (truck.weight > maxWeight) {
            return false;
        } else {
            return true;
        }
    }
}
O método checkTruckrecebe um parâmetro como entrada - um objeto caminhão Trucke determina se o oficial permitirá que o caminhão entre na ponte ou não. A lógica dentro do método é bastante simples: se o peso do caminhão ultrapassar o máximo permitido, o método retorna false. Você terá que procurar outro caminho :( Se o peso for menor ou igual ao máximo, você pode passar, e o método retorna true. Se você ainda não entendeu completamente as frases “retorna”, “o método retorna um valor ” - vamos fazer uma pausa na programação e ver isso usando um exemplo simples da vida do mundo real :) Digamos que você ficou doente e não trabalhou por vários dias. Você chega ao departamento de contabilidade com sua licença médica, que deve pagar. Se fizermos uma analogia com os métodos, então o contador tem um método paySickLeave()(“pagar licença médica”). Você passa um atestado de licença médica para este método como parâmetro (sem ele o método não funcionará e você não receberá nada!). Dentro do método de planilha, são feitos os cálculos necessários (o contador utiliza para calcular quanto a empresa deve lhe pagar), e o resultado do trabalho é devolvido a você – uma quantia em dinheiro. O programa funciona da mesma maneira. Ele chama um método, passa os dados para lá e finalmente recebe o resultado. Aqui está o método main()para o nosso programa BridgeOfficer:
public static void main(String[] args) {
    Truck first = new Truck();
    first.weight = 10000;
    Truck second = new Truck();
    second.weight = 20000;

    BridgeOfficer officer = new BridgeOfficer(15000);
    System.out.println("Truck number 1! May I pass, officer?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Truck number 2! May I?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
Estamos criando dois caminhões com cargas de 10.000 e 20.000. Ao mesmo tempo, o peso máximo para a ponte onde o oficial está de plantão é 15.000. O programa chamou o método officer.checkTruck(first), o método calculou tudo e retornou o resultado para o programa - true, e o programa o salvou na variável boolean canFirstTruckGo. Agora ele pode fazer o que quiser com isso (assim como você fez com o dinheiro que recebeu do contador). Em última análise, o código
boolean canFirstTruckGo = officer.checkTruck(first);
se resume a
boolean canFirstTruckGo = true;
Um ponto muito importante: o operador returnnão apenas retorna o resultado do método, mas também encerra seu trabalho ! Todo o código escrito após o retorno não será executado!
public boolean checkTruck(Truck truck) {

    if (truck.weight > maxWeight) {
        return false;
        System.out.println("Turn around, overweight!");
    } else {
        return true;
        System.out.println("Alright, move on!");
    }
}
As frases que o oficial disser não serão enviadas para o console, pois o método já retornou um resultado e concluiu seu trabalho! O programa retornou ao ponto onde o método foi chamado. Você não precisa se preocupar com isso - o compilador Java é inteligente o suficiente para gerar um erro se você tentar escrever código depois do return.

Vingadores: Guerra de Opções

Existem situações em que nosso programa requer várias opções de funcionamento de um método. Por que não criamos nossa própria inteligência artificial? Amazon tem Alexa, Yandex tem Alice, então por que somos piores? :) No filme sobre o Homem de Ferro, Tony Stark criou sua própria inteligência artificial notável - JARVIS Vamos prestar homenagem ao personagem maravilhoso e nomear nossa IA em sua homenagem :) A primeira coisa que devemos ensinar a Jarvis é cumprimentar as pessoas que entram na sala (seria estranho se um intelecto tão grande fosse indelicado).
public class Jarvis {

    public void sayHi(String name) {
        System.out.println("Good evening, " + name + ", How are you doing?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
    }
}
Saída do console:
Добрый вечер, Тони Старк, How ваши дела?
Ótimo! Jarvis sabe cumprimentar quem entra. Na maioria das vezes, é claro, será seu dono - Tony Stark. Mas ele pode não vir sozinho! E nosso método sayHi()aceita apenas um argumento como entrada. E, conseqüentemente, ele poderá cumprimentar apenas um dos que vierem e ignorará o outro. Não é muito educado, concorda? :/ Neste caso, para resolver o problema, podemos simplesmente escrever 2 métodos na classe com o mesmo nome, mas com parâmetros diferentes:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ", How are you doing?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + ", " + secondGuest + ", How are you doing?");
    }
}
Isso é chamado de sobrecarga de método . A sobrecarga permite que nosso programa seja mais flexível e acomode diferentes opções de trabalho. Vamos verificar como funciona:
public class Jarvis {

    public void sayHi(String firstGuest) {
        System.out.println("Good evening, " + firstGuest + ", How are you doing?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + ", " + secondGuest + ", How are you doing?");
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
        jarvis.sayHi("Tony Stark", "Captain America");
    }
}
Saída do console:
Добрый вечер, Тони Старк, How ваши дела?
Добрый вечер, Тони Старк, Капитан Америка, How ваши дела?
Ótimo, ambas as opções funcionaram :) Porém, não resolvemos o problema! E se houver três convidados? Claro, podemos sobrecarregar o método novamente sayHi()para aceitar os nomes de três convidados. Mas pode haver 4 ou 5. E assim por diante, ad infinitum. Existe outra maneira de ensinar Jarvis a trabalhar com qualquer número de nomes, sem um milhão de sobrecargas de métodos sayHi()? :/ Claro que sim! Caso contrário, o Java seria a linguagem de programação mais popular do mundo? ;)
public void sayHi(String...names) {

    for (String name: names) {
        System.out.println("Good evening, " + name + ", How are you doing?");
    }
}
O registro ( String...names) passado como parâmetro nos permite indicar que um determinado número de strings são passados ​​para o método. Não especificamos antecipadamente quantos deveriam ser, então a operação do nosso método agora se torna muito mais flexível:
public class Jarvis {

    public void sayHi(String...names) {
        for (String name: names) {
            System.out.println("Good evening, " + name + ", How are you doing?");
        }
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
    }
}
Saída do console:
Добрый вечер, Тони Старк, How ваши дела?
Добрый вечер, Капитан Америка, How ваши дела?
Добрый вечер, Черная Вдова, How ваши дела?
Добрый вечер, Халк, How ваши дела?
Parte do código aqui não é familiar para você, mas não se importe com isso. A sua essência é simples - o método passa por todos os nomes e cumprimenta cada um dos convidados! Além disso, funcionará para qualquer número de linhas transferidas! Dois, dez e até mil - o método funcionará de forma confiável com qualquer número de convidados. Muito mais conveniente do que fazer sobrecargas para todas as opções possíveis, não concorda? :) Outro ponto importante: a ordem dos argumentos importa! Digamos que nosso método receba uma string e um número como entrada:
public class Man {

    public static void sayYourAge(String greeting, int age) {
        System.out.println(greeting + " " + age);
    }

    public static void main(String[] args) {
        sayYourAge("My age - ", 33);
        sayYourAge(33, "My age - "); //error!
    }
}
Se um método sayYourAgede classe Manrecebe uma string e um número como entrada, então esta é a ordem em que eles precisam ser passados ​​no programa! Se os passarmos em uma ordem diferente, o compilador gerará um erro e a pessoa não conseguirá dizer sua idade. A propósito, os construtores que abordamos na aula passada também são métodos! Eles também podem estar sobrecarregados (criar vários construtores com diferentes conjuntos de argumentos) e para eles a ordem de passagem dos argumentos também é de fundamental importância. Métodos reais! :)

E novamente sobre os parâmetros

Sim, sim, ainda não terminamos :) O tema que consideraremos agora é muito importante. Há 90% de chance de que eles perguntem sobre isso em todas as suas entrevistas futuras! Falaremos sobre passagem de parâmetros para métodos. Vejamos um exemplo simples:
public class TimeMachine {

    public void goToFuture(int currentYear) {
        currentYear = currentYear+10;
    }

    public void goToPast(int currentYear) {
        currentYear = currentYear-10;
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2020;

        System.out.println("What is the year now?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("And now?");
        System.out.println(currentYear);
    }
}
A máquina do tempo possui dois métodos. Ambos tomam como entrada um número que representa o ano atual e aumentam ou diminuem o valor (dependendo se queremos voltar no tempo ou no futuro). Mas, como pode ser visto na saída do console, o método não funcionou! Saída do console:
Какой сейчас год?
2020
А сейчас?
2020
Passamos uma variável currentYearpara o método goToPast(), mas seu valor não mudou. Tal como foi em 2020, continua assim. Mas por que? :/ Porque primitivos em Java são passados ​​para métodos por valor. O que isso significa? Quando chamamos um método goToPast()e passamos nossa variável para lá int currentYear = 2020, não é a variável em si que entra no método currentYear, mas uma cópia dela . O valor desta cópia, claro, também é igual a 2020, mas todas as alterações que ocorrem na cópia não afetam de forma alguma a nossa variável originalcurrentYear ! Vamos tornar nosso código mais detalhado e ver o que acontece com currentYear:
public class TimeMachine {

    public void goToFuture(int currentYear) {
        currentYear = currentYear+10;
    }

    public void goToPast(int currentYear) {
        System.out.println("The goToPast method has started!");
        System.out.println("The currentYear value inside the goToPast method (at the beginning) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("The currentYear value inside the goToPast method (at the end) = " + currentYear);
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        int currentYear = 2020;

        System.out.println("What is the year at the very beginning of the program?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("What year is it now?");
        System.out.println(currentYear);
    }
}
Saída do console:
Какой год в самом начале работы программы?
2020
Метод goToPast начал работу!
Значение currentYear внутри метода goToPast (в начале) = 2020
Значение currentYear внутри метода goToPast (в конце) = 2010
А сейчас Howой год?
2020
Isso mostra claramente que a variável que foi passada para o método goToPast()é apenas uma cópia currentYear. E alterar a cópia não teve efeito no significado do “original”. “ Passar por referência ” tem exatamente o significado oposto. Vamos praticar em gatos! Quer dizer, vamos ver como é passar por link usando gatos como exemplo :)
public class Cat {

    int age;

    public Cat(int age) {
        this.age = age;
    }
}
Agora, com a ajuda da nossa máquina do tempo, lançaremos Barsik, o primeiro gato viajante do mundo, ao passado e ao futuro! Vamos mudar a classe TimeMachinepara que a máquina possa trabalhar com objetos Cat;
public class TimeMachine {

    public void goToFuture(Cat cat) {
        cat.age += 10;
    }

    public void goToPast(Cat cat) {
        cat.age -= 10;
    }
}
Os métodos agora alteram não apenas o número passado, mas o campo agede um objeto específico Cat. No caso das primitivas, como você lembra, não tivemos sucesso: o número original não mudou. Vamos ver o que acontece aqui!
public static void main(String[] args) {

    TimeMachine timeMachine = new TimeMachine();
    Cat barsik = new Cat(5);

    System.out.println("How old is Barsik at the very beginning of the program?");
    System.out.println(barsik.age);

    timeMachine.goToFuture(barsik);
    System.out.println("And now?");
    System.out.println(barsik.age);

    System.out.println("Firs-sticks! Barsik has aged 10 years! Drive back quickly!");
    timeMachine.goToPast(barsik);
    System.out.println("Did it work? Have we returned the cat to its original age?");
    System.out.println(barsik.age);
}
Saída do console:
Сколько лет Барсику в самом начале работы программы?
5
А теперь?
15
Елки-палки! Барсик постарел на 10 лет! Живо гони назад!
Получилось? Мы вернули коту его изначальный возраст?
5
Uau! Agora o método funcionou de forma diferente: nosso gato envelheceu de repente e depois pareceu mais jovem de novo! :) Vamos tentar descobrir o porquê. Diferentemente do exemplo com primitivas, no caso de objetos uma referência ao objeto é passada para o método. Uma referência ao nosso objeto original foi passada goToFuture(barsik)para os métodos . Portanto, quando alteramos os métodos internos , acessamos a própria área da memória onde nosso objeto está armazenado. Este é um link para o mesmo Barsik que criamos no início. Isso se chama "passagem por referência"! Porém, com esses links nem tudo é tão simples :) Vamos tentar mudar nosso exemplo: goToPast(barsik)barsikbarsik.age
public class TimeMachine {

    public void goToFuture(Cat cat) {
        cat = new Cat(cat.age);
        cat.age += 10;
    }

    public void goToPast(Cat cat) {
        cat = new Cat(cat.age);
        cat.age -= 10;
    }

    public static void main(String[] args) {
        TimeMachine timeMachine = new TimeMachine();
        Cat barsik = new Cat(5);

        System.out.println("How old is Barsik at the very beginning of the program?");
        System.out.println(barsik.age);

        timeMachine.goToFuture(barsik);
        System.out.println("Barsik went to the future! Has his age changed?");
        System.out.println(barsik.age);

        System.out.println("And if you try in the past?");
        timeMachine.goToPast(barsik);
        System.out.println(barsik.age);
    }
}
Saída do console:
Сколько лет Барсику в самом начале работы программы?
5
Барсик отправился в будущее! Его возраст изменился?
5
А если попробовать в прошлое?
5
Não funciona de novo! O_O Vamos descobrir o que aconteceu :) É tudo sobre os métodos goToPast/ goToFuturee a mecânica de como os links funcionam. Agora atenção!Este ponto é o mais importante para entender como funcionam os links e métodos. Na verdade, quando chamamos um método, goToFuture(Cat cat)não é a referência do objeto em si que é passada para ele cat, mas uma cópia dessa referência. Ou seja, quando passamos um objeto para um método, existem duas referências a esse objeto . Isso é muito importante para entender o que está acontecendo. Afinal, é por isso que o nosso último exemplo não mudou a idade do gato. No exemplo anterior com alteração da idade, simplesmente pegamos a referência passada dentro do método goToFuture(), encontramos o objeto na memória usando-a e alteramos sua idade ( cat.age += 10). Agora dentro do método goToFuture()criamos um novo objeto
(cat = new Cat(cat.age)),
e o mesmo link de cópia que foi passado para o método recebe este objeto. Como resultado:
  • O primeiro link ( Cat barsik = new Cat(5)) aponta para o gato original (com 5 anos)
  • Depois de passarmos a variável catpara o método goToPast(Cat cat)e atribuí-la a um novo objeto, a referência foi copiada.
Depois disso, temos a situação final: dois links apontam para dois objetos diferentes. Mas alteramos a idade de apenas um deles - aquele que criamos dentro do método.
cat.age += 10;
E naturalmente, quando o enviamos main()para o console no método barsik.age, vemos que sua idade não mudou. Afinal barsik, esta é uma variável de referência que ainda aponta para o objeto antigo e original com 5 anos de idade, ao qual nada aconteceu. Todas as nossas manipulações com a idade foram realizadas em um novo objeto. Assim, acontece que os objetos são passados ​​para métodos por referência. Cópias de objetos nunca são criadas automaticamente. Se você passou um objeto cat para um método e alterou sua idade, ele será alterado com sucesso. Mas os valores das variáveis ​​de referência são copiados ao atribuir e/ou chamar métodos! Vamos repetir aqui o parágrafo sobre passagem de primitivas: "Quando chamamos um método changeInt()e passamos nossa variável lá int x = 15, não é a variável em si que entra no método x, mas sim sua cópia . Afinal, todas as alterações que acontecem na cópia não afetar nossa variável original de alguma forma x.” Com a cópia de links, tudo funciona exatamente igual! Você passa o objeto cat para o método. Se você fizer algo com o próprio gato (ou seja, com o objeto na memória), todas as alterações serão realizadas com sucesso - tínhamos apenas um objeto e ainda o temos. Mas se dentro de um método você criar um novo objeto e salvá-lo em uma variável de referência, que é um parâmetro do método, a partir de agora teremos dois objetos e duas variáveis ​​de referência. Isso é tudo! Não foi tão fácil, você pode até ter que dar palestras várias vezes. Mas o principal é que você aprendeu esse tema super importante. Muitas vezes você encontrará disputas (mesmo entre desenvolvedores experientes) sobre como os argumentos são passados ​​em Java. Agora você sabe exatamente como funciona. Mantem! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION