Alura > Cursos de Programação > Cursos de Java > Conteúdos de Java > Primeiras aulas do curso Certificação Java SE 7 Programmer I: métodos e encapsulamento

Certificação Java SE 7 Programmer I: métodos e encapsulamento

Crie métodos com argumentos e valores de retorno - Crie métodos com argumentos e valores de retorno

Nesta seção nós vamos falar sobre como criar e usar métodos, argumentos e o valor de retorno desses métodos. Então o primeiro passo vou criar um arquivo, vou chamar de TestaMetodos e vou criar uma classe, que é minha classe que tem métodos, ClasseComMetodos.

Toda classe interface ou enum, que não cai na prova, pode ter métodos e todo método tem uma assinatura e um corpo caso o método não seja abstrato. Se o método for abstrato ele não tem corpo, como vamos ver quando falarmos de métodos abstratos.

Então todo método tem uma assinatura e um corpo, se o método for concreto e não abstrato, isso quer dizer que se eu vou criar um método, por exemplo, que devolve um número, int getNumero, ele tem a assinatura dele e ele tem o corpo dele, return 5;, uma assinatura e um retorno.

Como que é feita a assinatura desse método? O primeiro passo é colocar o nome do método, então getNumero, foi o nome do método que eu escolhi. O nome do método tem que seguir a regra de identificadores, assim como variáveis seguia a regra de identificadores.

O tipo de retorno é obrigatório, eu tenho que falar se esse meu método devolve int, se esse meu método devolve long, se o meu método devolve um string, uma referência para uma string. Se ele devolve uma referência para uma pessoa, no meu caso o meu método devolve um int, ele poderia não devolver nada, void, mas eu preciso falar qual é o tipo de retorno.

Inclusive, se ele não for retornar nada, eu tenho que falar void. No meu caso, meu método vai devolver int, então o tipo de retorno e o nome do método, são obrigatórios e aí eu falo quais são os parâmetros. Se eu não quero parâmetro nenhum, parênteses, sem parâmetro nenhum. Se eu quero receber parâmetros, eu falo os parâmetros aqui.

Por exemplo, eu vou receber um número, int i, aqui o parâmetro funciona como a declaração de uma variável sem inicialização, só a declaração, int espaço e o nome da variável. Por exemplo, quero receber uma variável do tipo string, String x. Quero receber uma pessoa, Pessoa P.

Sempre separando os argumentos com a virgula e aí o bloco que é o corpo do meu método, a implementação do meu método, então esses são os pontos obrigatórios, o tipo de retorno, o nome do método, o parênteses para receber os parâmetros, aí quantos parâmetros eu recebo é opcional. Posso receber três, como no caso desse método, posso receber dois, um ou zero, no caso desse método.

E o bloco, que é o corpo do meu método, então o meu método int getNumero. Eu posso ter também os modificadores, os modificadores vem antes do retorno, um exemplo de modificador public, private, protected, que são modificadores de acesso. Os modificadores de acesso eu só posso ter um. Se eu não coloco nada, o modificador de acesso é o default, o padrão que vamos falar bastante quando falarmos de modificadores de acesso.

Agora eu tenho o default, o protected, o private e o public, só pode ter um desses. Os outros modificadores de método, que não são modificadores de acesso, são modificadores gerais, esses modificadores eu posso ter diversos de uma vez só, por exemplo, o meu método pode ser final, pode ser final e syncronized, então existem diversas combinações de modificadores que eu posso usar.

Os modificadores são final, que significa que se essa classe for herdada, o método não poderá ser sobrescrito. abstract significa que esse método é só a definição do método, só a assinatura do método, sem corpo, veremos bastante quando falarmos de abstract. Static, significa que o método é estático, o método pertence à classe, o método não pertence ao objeto, nós vamos falar quando falarmos e static.

Synchronized significa que o acesso ao método é sincronizado, ele bloqueia diversas treds para acessar esse método. Native, que assim como o método abstrato, só vou definir a assinatura sem a implementação, sem o corpo, que é um modificador que não é cobrado nessa prova.

Strictfp, assim como o native, ele não é cobrado na prova, o native permite a implementação do método através de um código nativo, através de JNI e o strictfp faz com que as contas matemáticas funcionem de uma maneira bem determinística nas contas de ponto flutuante, que também não é cobrado nessa prova.

Além dos modificadores existe uma outra coisa opcional, que é quais as exceptions que esse método joga. Após a lista de parâmetros eu posso falar que esse método joga, throws, determinadas exceptions, por exemplo RuntimeException, por exemplo IOException. Eu posso falar as exceptions que eu jogo aqui, no meu caso vou deixar throws RuntimeException.

A ordem dos modificadores tem que ser modificadores, tipo de retorno, nome do método, parâmetros, throws. Lembrando que o throws é opcional, modificadores opcionais. Parâmetros dentro do parênteses opcionais, mas o parênteses é obrigatório.

Vamos falar um pouco mais sobre os parâmetros? Então se eu tenho um método, por exemplo, em uma classe Param, tenho aqui um void teste(int a, int b), que recebe a e recebe b. Esse método é um método que se chama teste e eu tenho que passar dois parâmetros para ele, a e b. Então quando eu crio uma classe Param e chamo p.teste, eu tenho que passar o primeiro e tenho que passar o segundo.

Se eu passar só um os dois não compila, vamos testar? Compilou e rodou. A inicialização desses parâmetros, repara que ela é feita por quem chama o método, não sou eu quem fala "a = 3", quem coloca o valor três nas variáveis do meu método para executar o meu método, é quem chamou o meu método. Isso é, não é possível no Java ter valores padrão para parâmetros da maneira que outras linguagens oferecem, por exemplo, igual a cem, igual a alguma coisa.

Não posso inicializar um parâmetro dentro da definição do parâmetro, eu posso depois fazer um a = 100, mas eu estrou sobrescrevendo o valor do meu a, então eu teria que tomar alguns cuidados. Não significa que eu tenho um valor padrão, valor padrão aqui igual a tanto, não funciona no Java.

O único modificador de acesso que eu posso ter para um parâmetro é o final, um parâmetro final funciona como uma variável local final. A variável local final recebeu um valor, não muda mais, enquanto aqui eu recebia a = 1, b = 2, se eu tentar mudar o valor de b para dez, joia. Se eu tentar mudar o valor de a, variável local final, isso é, um parâmetro também final, não pode ter o seu valor trocado, alterado. Vou tentar compilar.

Quando eu compilo ele fala que a é um parâmetro final e não pode ter o seu valor trocado. Temos também que saber que os nossos métodos e os parâmetros que recebemos neles, estão sujeitos ao polimorfismo e a promoção de tipo. Por exemplo, se tenho um método chamado primitivo que recebe um int e dentro aqui do meu método main coloco int a = 1, p.primitivo(a).

Então estou chamando um método passando um int, existe esse método que recebe int? Existe, compila, vamos testar? Vou comentar a linha aqui e compilo, beleza, mas pode ocorrer a promoção, int pode ser promovido para o que mesmo automaticamente? Para long, então se eu receber um long aqui, compila.

Porque ele encontra um método que recebe long, int para long tudo bem, não tem problema. int para float tudo bem? Tudo bem, não tem problema. Lembra a tabela de conversão automática? Para float não tem problema, para double também não tem problema, então a conversão, a promoção é feita automaticamente.

A mesma coisa para referências, se eu tenho um método referências que recebe void referencia(Object 0), esse método pode receber p.referencia(new object). Eu posso passar para ele um objeto ou eu posso passar para ele uma string p.referencia(“Guilherme”), os dois não válidos porque ele procura. Existe um método chamado referência que recebe string?

Não existe, mas toda string é object, polimorfismo, então ele aceita. Todo object é object, então ele aceita, compilo, tudo ok. Polimorfismo, as regras de polimorfismo e as regras de promoção de tipos primitivos, continuam valendo na chamada de método, importante.

Inclusive, um caso estranho pode acontecer aqui, p.primitivo(‘z’), z é char, mas char pode ser promovido para double. Compilo e funciona. Agora o que acontece se existe uma certa ambiguidade, se existe um método que recebe double e um método que recebe int, esses casos vamos ver e aprender a lidar com a sobrecarga de método em uma outra seção específica, mas isso pode acontecer e vamos falar isso ainda durante essa prova.

O que mais é importante agora quando estamos trabalhando com métodos, é o retorno do método, se o método é void ele não retorna nada, então eu posso simplesmente falar em qualquer lugar o meu código return. Qualquer lugar do meu código eu posso dar simplesmente um return, retorna nada, escreve um return em qualquer lugar do meu método vou poder dar um return.

Lembra que eu não posso ter código depois do meu return, um código que seria executado depois do return. O compilador vai perceber, você retornou esse código, nunca será executado, então esse código aqui eu não posso ter. No caso do return ser a última expressão do método, eu posso tirar ela, então se aqui eu estava imprimindo a variável A e a última coisa que eu faço é um return nada, return em nada e nada é a mesma coisa.

Ele sabe que é para retornar nada, então return nada como última linha do método que devolve void é opcional, mas eu posso colocar, então porque eu colocaria o return nada? No caso de colocar alguma coisa aqui dentro, se eu quero colocar um if e retornar mais cedo, então é comum eu ter um return nada.

Agora todo o método que tem um tipo de retorno diferente de void, quer dizer, que retorna alguma coisa, por exemplo o meu getNumero(), se ele retorna alguma coisa, o return é obrigatório, vamos testar? Eu compilo e ele fala que está faltando um return aí, cadê o seu return?

Então eu tenho que colocar um return aqui dentro, então vou colocar return e vou mandar esse método devolver um string, return “nada”, e agora eu compilo. Estou devolvendo a string nada. Então o meu método getNumero, que retorna nada, devolve uma string nada, poderia, claro, devolver null se eu estou falando de referências, posso devolver null no caso de uma referência.

O que mais? Se eu receber um parâmetro aqui, por exemplo, int a, agora quero fazer um if (a > 0) return “maior”, caso contrário se o A for menor do que zero, return “menor”. Legal, estou feliz e contente, salvo. Coloquei return, compilo, faltou um return. Como assim faltou um return, Guilherme?

Olha todas as condições possíveis do seu método, se alguma coisa faz isso, se não, se alguma coisa faz isso, mas ainda tem um caso, se isso daqui não for verdadeiro e nem isso for verdadeiro, ele vai continuar executando o seu código, ele não tem nada para executar. Então, nesse caso, o que que ele vai retornar?

Então toma muito cuidado, um método que tem um retorno definido, todos os caminhos dele, caminhos válidos, tem que retornar alguma coisa ou jogar algum exception, mas tem que fazer alguma das duas coisas. Então o primeiro caminho está devolvendo alguma coisa, o segundo caminho está devolvendo alguma coisa, mas o terceiro caminho não está.

Então eu tenho que colocar aqui um return “zero”, aí ele compila, porque eu posso cair nesse caso, porque quando você faz um if tem um caso do else e o compilador não sabe se o else vai acontecer ou não. Inclusive, se eu colocasse aqui else if (a <= 0) aí você fala que sempre vai cair em um desses dois.

Sim, para você ser humano que já está executando o código, o compilador não está executando o código, o compilador está só olhando que tem um if com else if, ainda tem uma terceira condição que é o else se não cair nenhum dos outros, então o compilador não consegue verificar que já foram cobertos todos os casos, eles só vê que foi if e else if, faltou no else você colocar um return e ele não se toca.

Mesma coisa para o if (true), se eu colocar aqui if(true) return “maior”, e no caso falso? O caso falso nunca vai acontecer, nós sabemos que nunca vai acontecer, mas o compilador não está atento a isso, enquanto lá no IO ele poderia ser atento a isso, no if ele não é atento a isso. Eu tenho que colocar alguma coisa return “menor ou igual”, aqui é if(true) return “verdadeiro”, porque eu estou sempre devolvendo isso.

Mas eu tenho que colocar no if, ele não é esperto o suficiente e eu tenho que colocar essa validação. É claro, eu comentei que eu poderia retornar alguma coisa ou jogar alguma exception, vou jogar uma exception throw RuntimeException(“jogando exception resolve o problema”), por quê? Porque o que eu estou dizendo é para terminar o método jogando uma exception.

Ou termina o método retornando um valor, de qualquer maneira eu estou devolvendo um resultado para quem chamou, seja a devolução em um resultado bonito ou uma exception que estoura. Então quando tenho um método que tem que retornar alguma coisa, todos os caminhos precisam jogar um new RuntimeException, jogar o new, alguma exception, algum error.

Ou precisam retornar alguma coisa. Vou compilar e ele compila e funciona bonito. Então, quer dizer, sempre tenho que retornar alguma coisa ou jogar alguma exception ou algum error caso o método devolva alguma coisa em todos os caminhos.

E quando eu invoco esses métodos, se esses métodos devolvem alguma coisa, eu não posso atribuir isso a algum lugar? Sim, quando eu invoco esses métodos, por exemplo, p.getNumero, certo? Vou invocar esse método, esse cara aqui devolve uma string, então eu posso só chamar um método passando para ele o número que eu queria.

Compila, não tem problema ou eu posso chamar o método usando o retorno dele de alguma maneira, por exemplo, dando System.out.println, pegando essa string que ele devolve e imprimindo ou atribuindo esse resultado a uma variável, não tem problema, posso fazer isso. Os dois casos eu estou pegando um retorno do meu método e fazendo alguma coisa com esse retorno. É opcional utilizar o retorno do método, não é obrigatório, compila.

E se eu chamar um método que não devolve nada, o método primitivo void, será que eu consigo usar ele para alguma coisa, consigo atribuir ele para um int? Não, não faz sentido, ele é void. Consigo atribuir ele para uma referência? Não, não faz sentido ele é void.

Método que é void não tem como utilizar o resultado dele para alguma coisa, porque ele não devolve nada, ele é void, você não tem como criar uma variável do tipo void minúsculo, não rola criar uma variável do tipo void minúsculo e então ele não compila.

Aplique a palavra chave static a métodos e campos - Aplique a palavra-chave static a métodos e campos

Vamos falar agora sobre a palavra-chave static, isto é, como aplicar e utilizar a palavra-chave static para métodos, campos e quais os efeitos dessa utilização. Vou criar primeiro um arquivo TestaStatic, vou criar uma classe, a minha classe Moto e a classe Moto vai ter uma variável chamada totalDeMotos.

Mas calma aí, o total de motos é um número que toda a moto tem, é uma variável membro? Toda a moto tem um total de motos diferente? Não, eu queria um total de motos só, um total de motos que estivesse atrelado a moto, a todas as motos. Então eu coloco ela como static, ela vira uma variável da classe e não uma variável do objeto da instância, é uma variável estática, uma variável da classe, uma única só para essa minha classe.

Então eu tenho um total de motos, inicializo como zero que é opcional, assim como uma variável membro, uma variável static já é inicializada com o valor default. Comecei com zero e quando eu vou acessar, como é que eu acesso uma variável estática? System.out.println(Moto.totalDeMotos), ela pertence à minha classe, então eu acesso diretamente através do nome da classe, do nome do meu tipo Moto.totalDeMotos = 15.

Coloquei lá que o total de motos é quinze e imprimiu o total de motos, vamos testar? E agora, o que eu quero fazer? Além de acessar uma variável que era membro e virou uma variável estática, eu posso ter métodos estáticos, não faz sentido. Por exemplo, eu quero proteger esse cara agora, então eu gostaria de modificar esse cara aqui para modificador de acesso privado, assim não compila mais.

Vamos falar bastante de modificador de acesso quando for discutir modificador de acesso. Deixei privado e não pode acessar, então eu queria educadamente acessar através de um getter, eu queria ter um método getTotalDeMotos, esse método devolve um int return Moto.totalDeMotos, criei um método.

Só que esse método é um método de classe, um método de instância, para acessar ele o que eu tenho que fazer? Eu tenho que fazer Moto m = new Moto(), m.setTotalDeMotos() ou System.out.println(m.getTotalDeMotos()). Vou ter que criar uma moto para saber o total de motos? É uma variável da classe, porque eu tenho que criar um objeto para chamar um método que não tem nada a ver com um objeto, que tem a ver com a classe e não com o objeto.

Então eu quero que esse método seja estático. Quando o método é estático, quando a variável é estática, o escopo dela é a classe e não o objeto, ela existe na classe, ela não existe dentro de cada um dos objetos, ela existe lá na nossa classe, tanto a variável de classe, quanto o método de classe, variável estática, método estático.

Eu posso chamar esses métodos agora diretamente, getTotalDeMotos. É, claro, eu não consigo mais acessar a variável para alterar o valor dela, se eu acessar assim diretamente, vou deixar aqui public só para acessarmos, porque eu não estou preocupado em criar o setter.

Então estou acessando diretamente com o valor quinze e estou mostrando que também tenho métodos estáticos que eu posso acessar, vamos compilar? Eu compilo e agora eu rodo, quinze. Agora e se eu tiver aqui a marca do meu carro e se eu tiver aqui a marca da minha moto? String marca e eu tenho a marca da minha moto, vou lá e escrevo Moto.marca = “Honda”, acho que não rola, por que não?

Porque marca é uma variável membro, é uma variável de uma moto, cada moto tem sua marca, não é uma variável da classe moto, não é uma variável para todas as motos, então isso aqui não faz sentido, eu preciso de uma moto para poder executar esse código.

Então a mesma coisa aqui dentro, será que aqui dentro desse método estático eu conseguiria escrever marca = “Honda”? Não faz sentido, um método estático é executado dentro de uma classe, se eu estou dentro de uma classe moto, não existe a variável marca, dentro de um método estático eu só tenho acesso ao escopo estático.

Porque não existe a moto aqui dentro, eu estou dentro da classe moto, não estou dentro do objeto, essa moto, aquela moto, então isso aqui não compila. Não posso acessar uma coisa não estática dentro de uma coisa estática e é uma regra geral, uma coisa estática só pode acessar outras coisas estáticas ou coisas que são daquele escopo.

É claro, se eu receber uma string aqui dentro, uma variável local, posso acessar essa variável local, mas acessar uma coisa não estática não rola. Poderia acessar uma variável estática, acessar um método estático, assim como o método acessou uma variável estática, a variável estática poderia acessar outra variável estática, poderia acessar um método estático, vamos dar um exemplo?

Public static int PADRAO_TOTAL_DE_MOTOS = 15, igual a dez, igual a oito e eu coloco aqui padrão total de motos. Por que eu coloquei tudo maiúsculo? Porque se a variável estática é final, o padrão no Java é colocar tudo maiúsculo, então coloquei que o valor padrão é oito, eu vou tirar aqui essa variável quando eu setei ela e vou rodar o get, vamos testar? Compilo, TestaStatic, oito.

Então uma variável estática pode acessar outras variáveis estáticas, métodos estáticos sem problema, método estático só pode acessar variáveis estáticas, métodos estáticos e variáveis locais, parâmetros, etc, que você criar aqui dentro do seu método estático.

Toma cuidado com a ordem das variáveis, se eu colocar essa variável aqui fora depois da declaração da primeira variável, o que acontece? Vou tentar compilar e ele fala que não dá para você acessar uma variável que foi declarada inicializada depois.

E um método, será que consigo definir um método aqui antes e voltar aquela ordem? O método pode acessar qualquer ordem, não tem problema nenhum, por quê? Porque o método só vai ser invocado depois da classe ser executada, vamos testar? Compilei, não tem problema.

Um caso estranho seria eu ter uma variável estática A valendo quinze, uma variável estática B chamando o resultado de getMetodo e eu tenho um método estático que vai calcular alguma coisa, vai devolver um int para mim. Se esse meu cara aqui devolvesse dezoito, estou feliz e contente, eu vou devolver e vou testar.

Vou imprimir aqui System.out.println(Moto.b), vou compilar e vou rodar. Ele imprimi, legal, dezoito, por que dezoito? Porque B é igual a getMetodo e getMetodo igual a dezoito. Agora vou fazer o getMetodo devolver o A. Repara que eu roubei antes uma variável estática não podia acessar conteúdo de uma variável estática que é declarada depois dela, mas você pode acessar um método estático.

Então eu defini ela como acessando um método estático e o método estático acessa uma variável estática que veio depois, eu roubei, o que acontece? Quando eu compilo eu tenho um resultado ok, diferente de antes que ele falava que você não pode acessar uma variável estática que foi declarada depois, ele deixou agora.

E quando eu rodar, o que vai acontecer? Afinal, quando eu declarar a variável B e tentar inicializar ela, ele vai chamar um método e a variável A teoricamente ainda não foi inicializada, quando eu rodo ele imprimi zero, por quê? Porque o código de execução estático vai declarando as variáveis e inicializando elas na mesma ordem.

Então ele declarou todas as variáveis B, A etc, todas com valor padrão, todas aqui no caso int, todas zero. Aí ele começa a executar todas as inicializações, ele executou primeiro essa inicialização, a inicialização do getMetodo, então ele chama o getMetodo, A vale quanto? A vale zero. Então B passa a valer zero.

Aí depois ele chamou essa inicialização, aí ele chama essa inicialização e essa inicialização faz com que o A valha quinze. Vamos testar? Compilo e rodo, zero e quinze, então eu não posso acessar de uma variável estática, uma variável estática que foi declarada depois, mas eu posso acessar um método e aí tenho que tomar um cuidado absurdo, porque se esse método acessar variáveis estáticas que não foram inicializadas ainda, vai dar um erro enorme.

Por isso é muito cuidado que nós temos que tomar com coisas estáticas de inicialização, porque a ordem das invocações é importante e se você tem uma variável acessando um método, vai ser um inferno e é por isso que na prática nós vamos evitar essas coisas estáticas, essas inicializações estáticas porque é um inferno que você não faz ideia da ordem.

Porque alguém pode adicionar um método que de repente inicializa em uma ordem diferente da que você planejava e quebra todo o seu código, muito cuidado com isso então. Um outro detalhe importante é que as variáveis estáticas, elas pertencem a minha classe, mas já que elas pertencem a minha classe, se eu tenho uma moto, a moto é meio que uma maneira de eu reverenciar as motos.

Então eu consigo acessar métodos estáticos e variáveis estáticas, através de uma referência daquele tipo, m.getTotalDeMotos() vai chamar o método getTotalDeMotos, mas ele vai chamar esse método na classe, não no objeto, nunca no objeto, pois se o método é estático ele está chamando na classe.

Só que eu não preciso chamar através da referência da classe, eu posso chamar através da referência de um objeto daquele tipo e ele vai usar o tipo da variável, tempo de compilação, tipo da variável para invocar esse método Moto.getTotalDeMotos, vamos testar?

Então ele vai chamar, ele vai imprimir oito e oito de novo e ele imprimi, oito e oito, por quê? Porque moto.getTotalDeMotos e m.getTotalDeMotos tem o mesmo resultado, ele chama o método na classe. E, cuidado, porque se nós temos um método estático, ela não pode ter um método membro com o mesmo nome, não pode ter um método estático e um método de classe com o mesmo nome, da erro de compilação, não compila, já foi declarado esse método.

Mesmo que eu use herança, se eu tiver aqui uma MotocicletaDupla e ela estender de moto e eu colocar aqui um método int getTotalDeMotos, ele não vai deixar, por quê? Porque eu tenho já esse método estático dentro da minha classe moto.

Então se eu chamasse motocicletadupla.getTotalDeMotos, se eu tiver uma motocicleta dupla e chamar o método getTotalDeMotos dentro dessa motocicleta dupla, ele estaria chamando método estático da classe moto ou ele estaria chamando o método de instância dessa minha motocicleta dupla? Isso não ficou definido, então isso não compila, você não pode ter um método estático e um método e instância com o mesmo nome.

Mesmo que esteja herança envolvida, vamos tentar compilar? Opa, não pode, já foi definido esse método na classe moto, mesmo que você fala que esse método é público, ele não vai deixar. Então vou tirar esse método daqui. Outro ponto importante é o que eu falei, isso aqui, esse atrelamento da invocação, o binding da invocação é feito em compilação.

Todo método estático, o binding é feito em tempo de compilação, isso significa o que? Que se a minha classe MotocicletaDupla tiver um método estático com o mesmo nome e ela pode ter um método estático com o mesmo nome, em que eu vou devolver menos quinhentos mil, ela pode ter um método estático com o mesmo nome, compila? Compila.

São dois métodos totalmente diferentes, se eu chamar moto.getTotalDeMotos, ele vai chamar moto.getTotalDeMotos, se eu chamar MotocicletaDupla.getTotalDeMotos, vai devolver MotocicletaDupla, beleza, vamos testar? Não tem problema.

Só que não ocorre uma sobrescrita de métodos, são dois métodos totalmente diferentes, como é que eu posso ver isso? Quando eu chamo aqui m.getTotalDeMotos, ele vai olhar em tempo de compilação. M é do tipo moto, então ele troca para moto.getTotalDeMotos e aí ele imprimi que valor? O valor oito.

Mas e se a minha moto é uma MotocicletaDupla em tempo de execução? Em tempo de execução eu estou usando polimorfismo, ela é uma motocicleta dupla, mas o binding de métodos estáticos é feito em compilação, ele vai continuar chamar moto.getTotalDeMotos, vamos testar? Ele imprimi oito.

Métodos estáticos não tem a sobrescrita da mesma maneira que métodos de classe terão, no método estático se eu sobrescrever ele na minha classe filha, eu vou ter dois métodos diferentes, ou chamo um ou eu chamo outro. Se eu chamar através da instância, eu estou sempre chamando através do tipo da variável e não do tipo referenciado em tempo de execução.

A palavra static também pode ser aplicada a classes dentro de outras classes, mas esse tópico de classes alinhadas não é cobrado nessa certificação, então não precisamos nos preocuparmos com a classe estática, static class que tem que estar dentro de uma outra classe.

Crie métodos sobrecarregados - Crie métodos sobrecarregados

Esta seção vai nos cobrar a criação e utilização de métodos sobrecarregados. Primeiro passo, vamos criar a nossa classe de teste, então primeiro exemplo é criar uma classe de teste em que eu tenha um método e um outro método que tenha um mesmo nome, porém em um tipo de retorno diferente.

Esse aqui devolve int e o outro não devolve nada, isso não pode, isso não é sobrecarga de método. Sobrecarga de método significa que o número de parâmetros ou o tipo dos parâmetros é diferente, o retorno, os modificadores de acesso, os throws, essas coisas, não influenciam a decisão se um método está sendo sobrecarregado ou não.

Um método só é sobrecarregado se ele possui um mesmo nome, tipos ou quantidade de parâmetros diferentes, então, nesse, caso como eu não estou sobrecarregando, eu estou criando um mesmo método, vou tentar compilar e não pode, já existe esse método na classe overloader.

Então eu volto para o meu código e agora sim, eu quero definir dois métodos que se sobrecarregam, então vou definir até com um mesmo tipo de retorno para deixar mais parecido, só que um eles recebe um int e o outro não recebe nada, tudo bem, por quê? Se eu chamar o método new Overloader, criei o overloader, chamei o método sem argumentos.

Ele está chamando esse método de baixo. Se eu chamar o método com um int, ele está chamando o método de cima. Então System.out.println(“com int”) e System.out.println(“sem nada”), vamos testar? Compilo e testo, sem nada e depois com int, vamos ver? Primeiro sem nada e depois com int, por quê?

Porque o compilador fala tal método recebe nada, tal método recebe um argumento, está vendo aqui em cima, esse método não recebe nada, esse método recebe um int, se eu não passar nada na hora que eu estou chamando, o compilador fala que está chamando esse. Se eu passar um int ele fala que está chamando aquele.

E ele sabe qual é o método que eu estou chamando, então essa é a sobrecarga, quantidade de argumentos diferentes ou tipos de argumentos diferentes. Vamos mostrar um exemplo de tipos de argumentos diferentes? Eu tenho um método que recebe int e um método que recebe double, tipos diferentes, um é com int e o outro é com double.

Se eu passar um int, ele chama com int, se eu passar um double, ele chama com double, vamos testar? Compilo e testo, sem nada com int e com double. Então a decisão de qual método será invocado é um método que recebe int, um método que recebe double, método que não recebe nada, é feito em tempo de compilação, por isso que em tempo de compilação o Java reclama para nós que esse método é igual ao outro.

Porque ele tem exatamente o mesmo tempo de argumentos e exatamente os mesmo tipos, aí ele não deixa compilar. E o que acontece se eu passar então aqui um short? Vou tentar chamar um new Overloader().metodo((short) 123), mas quero passar o número treze como um short, como faço para forçar ele ao shot? Fazer um cast para short, então isso é um short.

Vou tentar chamar esse método com um short, qual método ele vai chamar? O do int ou o do double? Compilo e rodo. O short está tendo a conversão dele para int automaticamente, e se eu passar um float? Aí o float é mais fácil de imaginar, o float não pode ser passado para um int, então ele tem que ser chamado com o double, compilo e rodo com o double.

Agora o que acontece se eu tenho referências, objetos em vez do que tipos primitivos? Então se eu tenho aqui um public void metodo2(String s) que recebe um string, System.outr.prinln(“com string”), e eu tenho o mesmo método agora com object, public void metodo2(Object s), System.out.prinln(“com object”). Quer dizer, todo objeto, toda string é um objeto.

E agora se eu chamo new Overloader().metodo2(”Guilherme”), com uma string, qual dos dois métodos ele vai chamar? Se eu chamar o mesmo método com object, new overloader().metodo2(new Object()), qual dos dois métodos ele vai chamar?

Com o object é óbvio, só tem um método que é compatível com esse parâmetro, mas e com o string? Os dois métodos são compatíveis, qual dos dois ele vai chamar? Ele precisa deixar claro para nós uma regra ou falar que a regra é não compilo, vamos primeiro tentar compilar?

Se eu passo uma string para um método que tem uma versão que recebe string e uma versão que recebe object, compila, qual dos dois ele chama? Ele chama com string e depois com object, claro, porque eu passei um object. Então ele chama o mais específico, se você está passando um objeto, uma referência para um objeto e existem duas versões desses método.

O método foi sobrecarregado com uma versão mais genérica e uma versão mais especifica, ele vai chamar a mais específica. Se nós queremos forçar a invocação ao tipo mais genérico, eu vou ter que fazer o que eu fiz ali com o short, vou passar aqui e falar para pegar essa string, mas tratá-la ela como um object, isso é, pega ela e finge que é um object, quer dizer, ele é um object, mas trata ele como um object.

Então procura o método dois que recebe object e aí sim ele vai chamar o método com object, então o resultado agora deveria ser com string, com object e com object. Compilo, com string, com object e com object. Um outro exemplo clássico é a troca de ordem dos parâmetros, então, por exemplo, se eu tenho um método três que recebe um string a e um int b.

E eu tenho um metodo três que recebe int e depois string, isso também é, claro, uma sobrecarga válida de método, tenho a e b que são string e int e depois int e string, totalmente diferentes. Se eu passar uma string e um int, é o primeiro. Se eu passar um int e uma string, é o segundo.

Então, lembra, a regra geral é se o compilador tem claro qual é o método que ele vai invocar por causa dos tipos e quantidades de parâmetros, é claro, ele vai chamar aquele metodo, acabou o assunto. Se existe uma certa ambiguidade porque um tipo é mais genérico ou mais específico, ele vai chamar o mais específico.

Mas eu posso ter um caso um pouco mais chato, que é um método, por exemplo, public void metodo4(int a, double b), que recebe um a e um double b. E depois eu coloco um outro método quatro que recebe o inverso dele public void metodo4(double b, int a), System.out.println(“double, int”).

Agora, se eu chamar o método quatro, vou tentar chamar ele, new Overloader().metodo4(3, 5.2), é claro, ele está chamando o int double. Se eu passar os números 5.2 e 3, double, int. Não tem segredo, int double, double int.

Agora e se eu passar para ele os valores 4 e 3? Eu estou passando um int e um int. Nesse caso, repara que o primeiro parâmetro pode ser considerado de int e o compilador pode fazer uma conversão automática, uma promoção para double do segundo parâmetro.

Mas ele poderia considerar que o segundo parâmetro é um int e fazer uma conversão automática, uma promoção do primeiro parâmetro. Aí complicou. Os dois casos são justos e os dois casos tem o mesmo peso, os dois casos estão tão distantes da realidade da invocação, um quanto o outro. Quer dizer, nos dois casos eu tenho que fazer uma promoção, promoções diferentes, mas é uma promoção.

E agora? Não tem como, o compilador não tem o que fazer aqui, as duas coisas é a mesma coisa, qual dos dois você quer? Eu não sei qual dos dois você quer, então você vai ter que ser mais explícito para mim, então se tenho dois métodos que é uma sobrecarga de método em que tenho dois ou mais parâmetros.

E eu estou chamando eles de determinada maneira que o compilador olha e fala que para chamar esse primeiro método eu tenho que fazer uma certa promoção, para chamar o segundo método eu também tenho que fazer uma outra promoção, então eu não sei qual das duas promoções você quer que eu faça, então eu não tenho o que fazer.

Se eu tivesse que somente em um caso fazer uma promoção e os outros são totalmente incompatíveis, faz aquele lá, os outros não estão nem aí. Então quando é só um parâmetro ou quando somente um dos métodos pode ser invocado, beleza.

Agora quando todos eles envolvem uma promoção ou n promoções, então ele não sabe o que fazer, eu tento compilar e ele fala que a referência para esse método é ambígua, os dois métodos poderiam receber esse tipo de coisa. O mesmo vale para eu escrever esse método com uma variação de referência, ao invés de ints e doublés, vamos escrever esse método de novo?

O metodo5(String a, Object b), System.out.println(“String, Object”), metodo5(String a, Object b), System.out.println(“Object, String”) e então vou invocar esses métodos, então vou querer invocar aqui new Overloader().metodo5(“Guilherme”, “João”), mesma coisa, esse é tanto string quanto object.

Mesmo que ele pegue o mais especifico, a regra que geral que eu havia comentado no caso de tentar pegar primeiro o mais específico em todos os casos, ele não consegue, não tem um que recebe string, string. Então em algum desses ele vai ter que generalizar, mas se ele generalizar o primeiro parâmetro, ele recebe um método.

Se ele generalizar o segundo parâmetro, ele pega o outro método e então ele fica perdido, vamos tentar compilar? Compilo e o mesmo problema de ser ambíguo. Vou deixar comentado essa linha e vou deixar comentada essa linha. Repara que esse caso é um pouco diferente do caso em que os dois métodos são muito parecidos, um recebe object e object, o outro recebe string e string.

Não tem mais nada a ver, porque se eu passar duas strings, qual é a regra? Primeiro tenta pegar o que é tudo especifico, ele acha esse método. Se eu passar dois objects ou duas quaisquer outras coisas, ele vai chamar qual método? O método de cima. E se eu passar uma string e um object? O método de baixo é compatível? String e object não é compatível.

O único método que é compatível com string object é o de cima. Vamos testar essas variações? Então método seis eu vou querer testar com string, string. Vou querer testar com object, object e vou querer testar com string object.

Então o primeiro caso é óbvio, ele procura o tipo mais específico, fechou, matou o assunto, então ele vai chamar string, string. Aqui também ele vai procurar o mais específico, o mais específico agora é object, object, funciona. Se ao invés de object, object eu passasse para ele um outro tipo de objeto, por exemplo um new Overloader().metodo6(new Carro(), new Carro()), porque eu tenho a classe carro.

A classe carro está aqui, então se eu passar o new carro, o mais específico não tem, mas ele tem o mais genérico que matcheia os dois casos, que batem os dois casos, object, object. E nesse caso string, string bate? Não bate, o único que bate aqui é object, object, vamos compilar?

Compila porque não tem ambiguidade e roda, então string, string só no primeiro caso que foi o mais específico. Resumindo, a regra geral é o overload, a sobrecarga de método só é feita se o número e argumentos ou o tipo dos argumentos é diferente, se você mudar só uma outra parte não é sobrecarga que você está fazendo.

Se você colocar dois métodos com o mesmo nome e mesma quantidade de argumentos, mesmo tipo de argumentos e mudar só os modificadores de acesso, não compila. Se eu fiz a sobrecarga de método e eu tenho uma chamada para um método, qual que ele vai chamar? Ele vai chamar o do tipo do parâmetro e da quantidade de parâmetros.

Se os dois métodos recebem parâmetros que são possivelmente compatíveis por causa de uma generalização, ele vai sempre pegar o mais específico, mas no caso em que você tem vários parâmetros, esse mais específico pode ser ambíguo, por quê? Porque pode ser que em um argumento ele deixa mais específico e no outro mais genérico e pega.

Lembrando, primeiro ele tenta tudo o mais específico possível, se tiver é isso que ele vai pegar, se não ele começa a tentar generalizar, mas se em um método ele generalizar um e no outro método ele generaliza outro e os dois são possíveis de serem chamados, então ele não sabe o que fazer e não consegue chamar.

Mas antes disso é importante, sempre o que ele tem que invocar? O mais específico, se existe o mais especifico, é ele que ele vai invocar.

Sobre o curso Certificação Java SE 7 Programmer I: métodos e encapsulamento

O curso Certificação Java SE 7 Programmer I: métodos e encapsulamento possui 114 minutos de vídeos, em um total de 53 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

Plus

De
R$ 1.800
12X
R$109
à vista R$1.308
  • Acesso a TODOS os cursos da Alura

    Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

Matricule-se

Pro

De
R$ 2.400
12X
R$149
à vista R$1.788
  • Acesso a TODOS os cursos da Alura

    Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Luri, a inteligência artificial da Alura

    Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com Luri até 100 mensagens por semana.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

Matricule-se
Conheça os Planos para Empresas

Acesso completo
durante 1 ano

Estude 24h/dia
onde e quando quiser

Novos cursos
todas as semanas