CodeGym /Blogue Java /Random-PT /Métodos em Java
John Squirrels
Nível 41
San Francisco

Métodos em Java

Publicado no grupo Random-PT
Olá de novo! Na última lição, nos familiarizamos com classes e construtores e aprendemos como criar nossos próprios. Hoje vamos conhecer melhor os Métodos Java, parte essencial das aulas. Um Método em Java é um conjunto de comandos que permitem realizar uma operação específica em um programa. Em outras palavras, um método é uma função; algo que sua classe é capaz de fazer. Em outras linguagens de programação, os métodos costumam ser chamados de "funções", mas em Java a palavra "método" é mais comum. :) Se você se lembra, na última lição criamos métodos simples para uma classe Cat , para que nossos gatos pudessem dizer miau e pular:

public class Cat {

    String name;
    int age;

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

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

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

        smudge.sayMeow();
        smudge.jump();
    }
}
sayMeow() e jump() são métodos da nossa classe. E a execução desses métodos resulta na seguinte saída do console:
Meow!
Pounce!
Nossos métodos são bastante simples: eles simplesmente enviam texto para o console. Mas em Java, os métodos têm uma tarefa importante: eles executam ações nos dados de um objeto. Eles alteram os dados do objeto, transformam-no, exibem-no e fazem outras coisas com ele. Nossos métodos atuais não fazem nada com os dados do objeto Cat . Vejamos um exemplo mais ilustrativo:

public class Truck {

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

    public int getVolume() {
        int volume = length * width * height;
        return volume;
    }
}
Por exemplo, aqui temos uma classe que representa um Truck . O semi caminhão tem comprimento, largura, altura e peso (que precisaremos mais tarde). No método getVolume() , realizamos cálculos, convertendo os dados do nosso objeto em um número que representa o seu volume (multiplicamos o comprimento, a largura e a altura). Este número será o resultado do método. Observe que a declaração do método é escrita como public int getVolume . Isso significa que esse método deve retornar um int . Calculamos o valor de retorno do método, e agora devemos retorná-lo ao programa que chamou nosso método. Para retornar o resultado de um método em Java, usamos a palavra-chave return. volume de retorno;

Parâmetros do Método Java

Podemos passar valores chamados "argumentos" para um método ao chamá-lo. A declaração de um método inclui uma lista de variáveis ​​que nos informam o tipo e a ordem das variáveis ​​que o método pode aceitar. Esta lista é chamada de "parâmetros de método". O método getVolume() da nossa classe Truck atualmente não define nenhum parâmetro, então vamos tentar estender nosso exemplo de caminhão. Crie uma nova classe chamada BridgeOfficer . Trata-se de um policial de plantão em uma ponte, que verifica todos os caminhões que passam para ver se a carga deles ultrapassa o 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 checkTruck aceita um argumento, um objeto Truck , e determina se o policial permitirá ou não o caminhão na ponte. Dentro do método, a lógica é bastante simples: se o peso do caminhão ultrapassar o máximo permitido, o método retorna false . Vai ter que achar outra estrada :( Se o peso for menor ou igual ao máximo, pode passar, e o método retorna true. Se você ainda não entende totalmente as frases "retorno" ou "o método retorna um valor", vamos fazer uma pausa na programação e considerá-las usando um exemplo simples da vida real. :) Digamos que você fique doente e fique em casa sem trabalhar por alguns dias. Você vai ao departamento de contabilidade com o atestado do seu médico, porque a licença médica deve ser paga. Se compararmos esta situação com métodos, então o contador tem um paySickLeave()método. Você passa um atestado médico como argumento para esse método (sem ele, o método não funciona e você não recebe!). Em seguida, são feitos os cálculos necessários dentro do método usando sua nota (o contador usa para calcular quanto a empresa deve pagar a você) e o resultado do seu trabalho (uma quantia em dinheiro) é devolvido a você. Nosso programa funciona de maneira semelhante. Ele chama um método, passa dados para ele e, por fim, recebe um resultado. Aqui está o método main() do 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 1! Can I go, officer?");
    boolean canFirstTruckGo = officer.checkTruck(first);
    System.out.println(canFirstTruckGo);

    System.out.println();

    System.out.println("Truck 2! And can I?");
    boolean canSecondTruckGo = officer.checkTruck(second);
    System.out.println(canSecondTruckGo);
}
Criamos dois caminhões com cargas de 10.000 e 20.000. E a ponte onde o oficial trabalha tem um peso máximo de 15.000. O programa chama o método officer.checkTruck(first) . O método calcula tudo e retorna true , que o programa salva na variável booleana canFirstTruckGo . Agora você pode fazer o que quiser com ele (assim como com o dinheiro que o contador lhe deu). No final do dia, o código

boolean canFirstTruckGo = officer.checkTruck(first);
resume-se a

boolean canFirstTruckGo =  true;
Aqui está um ponto muito importante: a instrução return não apenas retorna o valor de retorno do método, mas também interrompe a execução do método! Qualquer código que vier após a instrução return não será executado!

public boolean checkTruck(Truck truck) {

    if (truck.weight > maxWeight) {
        return false;
        System.out.println("Turn around, you're overweight!");
    } else {
        return true;
        System.out.println("Everything looks good, go ahead!");
    }
}
Os comentários do oficial não serão exibidos, pois o método já retornou um resultado e finalizou! O programa retorna ao local onde o método foi chamado. Você não precisa ficar atento a isso: o compilador Java é inteligente o suficiente para gerar um erro quando você tenta escrever código após uma instrução de retorno .

Vingadores: Guerra de Parâmetros

Existem situações em que queremos várias maneiras de chamar um método. Por que não criar nossa própria inteligência artificial? Amazon tem Alexa, Apple tem Siri, então por que não deveríamos ter um? :) No filme Homem de Ferro, Tony Stark cria sua própria inteligência artificial incrível, Jarvis. Vamos prestar homenagem a esse personagem incrível e nomear nossa IA em sua homenagem. :) A primeira coisa que precisamos fazer é ensinar Jarvis a dizer olá para as pessoas que entram na sala (seria estranho se um intelecto tão incrível se revelasse indelicado).

public class Jarvis {

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
    }
}
Saída do console:
Good evening, Tony Stark. How are you?
Muito bom! Jarvis agora pode receber convidados. Claro, na maioria das vezes será seu mestre, Tony Stark. Mas e se ele não vier sozinho! Nosso método sayHi() aceita apenas um argumento. E assim ele só pode cumprimentar uma pessoa entrando na sala e ignorará a outra. Não é muito educado, não concorda? :/

Sobrecarga de método Java

Neste caso, podemos resolver o problema simplesmente escrevendo 2 métodos 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?");
    }

    public void sayHi(String firstGuest, String secondGuest) {
        System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
    }
}
Isso é chamado de sobrecarga de método. A sobrecarga de métodos permite que nosso programa seja mais flexível e acomode várias formas de trabalho. Vamos rever como funciona:

public class Jarvis {

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

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

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark");
        jarvis.sayHi("Tony Stark", "Captain America");
    }
}
Saída do console:
Good evening, Tony Stark. How are you?
Good evening, Tony Stark and Captain America. How are you?
Excelente, ambas as versões funcionaram. :) Mas não resolvemos o problema! E se houver três convidados? Poderíamos, é claro, sobrecarregar o método sayHi() novamente, para que ele aceite três nomes de convidados. Mas podem ser 4 ou 5. Até o infinito. Não existe uma maneira melhor de ensinar Jarvis a lidar com qualquer número de nomes, sem sobrecarregar o método sayHi() um milhão de vezes? :/ Claro que tem! Se não houvesse, você acha que 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?");
    }
}
Quando ( String... nomes ) é usado como parâmetro, indica que uma coleção de Strings será passada para o método. Não precisamos especificar com antecedência quantos serão, então agora nosso método é 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?");
        }
    }

    public static void main(String[] args) {
        Jarvis jarvis = new Jarvis();
        jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
    }
}
Saída do console:
Good evening, Tony Stark. How are you?
Good evening, Captain America. How are you?
Good evening, Black Widow. How are you?
Good evening, Hulk. How are you?
Algum código aqui não será familiar para você, mas não se preocupe com isso. É simples em sua essência: o método pega cada nome de cada vez e cumprimenta cada convidado! Além disso, funcionará com qualquer número de strings passadas! Dois, dez, até mil - o método funcionará adequadamente com qualquer número de convidados. Muito mais conveniente do que sobrecarregar o método com todas as possibilidades, não acha? :) Aqui está outro ponto importante: a ordem dos argumentos é importante! Digamos que nosso método receba uma String e um número:

public class Person {

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

    public static void main(String[] args) {
        sayYourAge("My age is ", 33);
        sayYourAge(33, "My age is "); // Error!
    }
}
Se o método sayYourAge da classe Person receber uma string e um número como entradas, o programa deverá passá-los nessa ordem específica! Se os passarmos em uma ordem diferente, o compilador gerará um erro e a pessoa não poderá dizer sua idade. A propósito, os construtores, que abordamos na última lição, também são métodos! Você também pode sobrecarregá-los (ou seja, criar vários construtores com diferentes conjuntos de parâmetros) e a ordem dos argumentos passados ​​também é fundamentalmente importante para eles. São métodos reais! :)

Mais uma vez em relação aos parâmetros

Sim, desculpe, ainda não terminamos com eles. :) O tema que estudaremos agora é muito importante. Há 90% de chance de você ser questionado sobre isso em todas as entrevistas futuras! Vamos falar sobre passar argumentos para métodos. Considere 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 = 2018;

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

        timeMachine.goToPast(currentYear);
        System.out.println("How about now?");
        System.out.println(currentYear);
    }
}
A máquina do tempo tem dois métodos. Ambos tomam como entrada o número que representa o ano atual e aumentam ou diminuem seu valor (dependendo se queremos ir para o passado ou para o futuro). Mas, como você pode ver na saída do console, o método não funciona! Saída do console:
What year is it?
2018
How about now?
2018
Passamos a variável currentYear para o método goToPast() , mas seu valor não mudou. Estávamos em 2018, e aqui ficamos. Mas por que? :/ Porque os primitivos em Java são passados ​​para os métodos por valor. O que isso significa? Quando chamamos o método goToPast() e passamos a variável int currentYear (=2018) para ele, o método não obtém a variável currentYear em si, mas sim uma cópia dela. Claro, o valor desta cópia também é 2018, mas qualquer alteração na cópia não afeta nossa variável currentYear original de forma alguma! Vamos tornar nosso código mais explícito e observar 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 running!");
        System.out.println("currentYear inside the goToPast method (at the beginning) = " + currentYear);
        currentYear = currentYear-10;
        System.out.println("currentYear inside the goToPast method (at the end) = " + currentYear);
    }

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

        System.out.println("What was the year when the program started?");
        System.out.println(currentYear);

        timeMachine.goToPast(currentYear);
        System.out.println("And what year is it now?");
        System.out.println(currentYear);
    }
}
Saída do console:
What was the year when the program started?
2018
The goToPast method has started running!
currentYear inside the goToPast method (at the beginning) = 2018
currentYear inside the goToPast method (at the end) = 2008
And what year is it now?
2018
Isso mostra claramente que a variável passada para o método goToPast() é apenas uma cópia de currentYear . E alterar a cópia não afeta o valor "original". "Passar por referência" significa exatamente o oposto. Vamos praticar em gatos! Quero dizer, vamos ver como é passar por referência usando um exemplo de gato. :)

public class Cat {

    int age;

    public Cat(int age) {
        this.age = age;
    }
}
Agora, com a ajuda de nossa máquina do tempo, enviaremos Smudge , o primeiro gato viajante do tempo do mundo, ao passado e ao futuro! Vamos modificar a classe TimeMachine para que funcione com objetos Cat ;

public class TimeMachine {

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

    public void goToPast(Cat cat) {
        cat.age -= 10;
    }    
}
Agora os métodos não mudam apenas o número passado. Em vez disso, eles alteram o campo específico de idade do gato . Você deve se lembrar que isso não funcionou para nós com primitivos, porque o número original não mudou. Vamos ver o que vai acontecer!

public static void main(String[] args) {

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

    System.out.println("How old was Smudge when the program started?");
    System.out.println(smudge.age);

    timeMachine.goToFuture(smudge);
    System.out.println("How about now?");
    System.out.println(smudge.age);

    System.out.println("Holy smokes! Smudge has aged 10 years! Back up quickly!");
    timeMachine.goToPast(smudge);
    System.out.println("Did it work? Have we returned the cat to its original age?");
    System.out.println(smudge.age);
}
Saída do console:
How old was Smudge when the program started running?
5
How about now?
15
Holy smokes! Smudge has aged 10 years! Back up quickly!
Did it work? Have we returned the cat to its original age?
5
Uau! Agora o método fez algo diferente: nosso gato envelheceu drasticamente, mas depois ficou jovem de novo! :) Vamos tentar descobrir o porquê. Ao contrário do exemplo com primitivas, quando os objetos são passados ​​para um método, eles são passados ​​por referência. Uma referência ao objeto de mancha original foi passada para o método changeAge() . Então, quando mudamos smudge.age dentro do método, estamos referenciando a mesma área de memória onde nosso objeto está armazenado. É uma referência ao mesmo Smudge que criamos inicialmente. Isso é chamado de "passagem por referência"! No entanto, nem tudo com referências é tão fácil. :) Vamos tentar mudar nosso exemplo:

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 smudge = new Cat(5);

        System.out.println("How old was Smudge when the program started?");
        System.out.println(smudge.age);

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

        System.out.println ("And if you try going back?");
        timeMachine.goToPast(smudge);
        System.out.println(smudge.age);
    }
}
Saída do console:
How old was Smudge when the program started running?
5
Smudge went to the future! Has his age changed?
5
And if you try going back?
5
Não funciona de novo! О_О Vamos descobrir o que aconteceu. :) Tem tudo a ver com os métodos goToPast / goToFuture e como as referências funcionam. Agora, sua atenção, por favor! Esta é a coisa mais importante a entender sobre como as referências e os métodos funcionam. O fato é que, quando chamamos o método goToFuture(Cat cat) , é uma cópia da referência ao objeto cat que é passada, não a própria referência. Assim, quando passamos um objeto para um método, existem duas referências ao objeto. Isso é muito importante para entender o que está acontecendo. É exatamente por isso que a idade do gato não mudou em nosso último exemplo. No exemplo anterior, ao alterar a idade, simplesmente pegamos a referência passada para o método goToFuture()e o usou para encontrar o objeto na memória e alterar sua idade ( cat.age += 10 ). Mas agora, dentro do método goToFuture() , estamos criando um novo objeto ( cat = new Cat(cat.age) ), e esse objeto recebe a mesma cópia de referência que foi passada para o método. Como resultado:
  • A primeira referência ( Cat smudge = new Cat (5) ) aponta para o gato original (com 5 anos)
  • Depois disso, quando passamos a variável cat para o método goToPast() e atribuímos a ela um novo objeto, a referência foi copiada.
E isso nos trouxe ao resultado final: duas referências apontando para dois objetos diferentes. Mas apenas mudamos a idade de um deles (o criado dentro do método).

cat.age += 10;
E claro, no método main() podemos ver no console que a idade do gato, smudge.age , não mudou. Afinal, smudge é uma variável de referência que ainda aponta para o antigo objeto original com 5 anos de idade, e não fizemos nada com esse objeto. Todas as nossas alterações de idade foram realizadas no novo objeto. Então, acontece que os objetos são passados ​​para os métodos por referência. As cópias de objetos nunca são criadas automaticamente. Se você passar um objeto cat para um método e mudar sua idade, você mudará sua idade. Mas as variáveis ​​de referência são copiadas ao atribuir valores e/ou chamar métodos! Vamos repetir aqui o que dissemos sobre a passagem de primitivas: "Quando chamamos o método changeInt() e passamos o intvariável x (=15) , o método não obtém a variável x em si, mas sim uma cópia dela. Portanto, quaisquer alterações feitas na cópia não afetam nosso x originalAinda acabarei discutindo mais de uma vez sobre como os argumentos são passados ​​em Java (mesmo entre desenvolvedores experientes). Mas, agora você sabe exatamente como funciona. Mantem! :) Para reforçar o que você aprendeu, sugerimos que você assista a uma vídeo aula do nosso Curso de Java
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION