Desafiantes Java # 1: Sobrecarga de método na JVM
Bom dia a todos.
Já lançamos o próximo segmento do curso "Java Developer" , mas ainda temos alguns materiais que gostaríamos de compartilhar com você.
Bem-vindo à série de artigos Java Challengers ! Esta série de artigos se concentra nos recursos de programação Java. O desenvolvimento deles é o seu caminho para se tornar um programador Java altamente qualificado.
Dominar as técnicas discutidas nesta série de artigos exige algum esforço, mas elas percorrerão um longo caminho na sua experiência diária como desenvolvedor de java. Evitar erros é mais fácil quando você sabe como aplicar corretamente as técnicas básicas de programação Java e rastrear erros é muito mais fácil quando você sabe exatamente o que está acontecendo no seu código java.
Você está pronto para começar a dominar os conceitos básicos de programação em Java? Então vamos começar com o nosso primeiro quebra-cabeça!

O termo "Sobrecarga de método"
Sobre o termo sobrecarga, os desenvolvedores tendem a pensar que estamos falando sobre a reinicialização do sistema, mas não é assim. Na programação, sobrecarga de método significa usar o mesmo nome de método com parâmetros diferentes.
O que é sobrecarga de método?
Sobrecarga de método é uma técnica de programação que permite que um desenvolvedor da mesma classe use o mesmo nome para métodos com parâmetros diferentes. Nesse caso, dizemos que o método está sobrecarregado.
A Listagem 1 mostra métodos com parâmetros diferentes que variam em número, tipo e ordem.
Lista 1. Três opções para sobrecarregar métodos.
Sobrecarga de método e tipos primitivos
Na Listagem 1, você viu os tipos primitivos int
e double
. Vamos divagar por um minuto e recuperar os tipos primitivos em Java.
Tabela 1. Tipos primitivos em Java
Tipo | Range | Valor padrão | Tamanho | Exemplos literais |
---|
booleano | verdadeiro ou falso | falsa | 1 bit | verdadeiro falso |
byte | -128 ... 127 | 0 0 | 8 bit | 1, -90, -128 |
char | Caractere Unicode ou 0 a 65 536 | \ u0000 | 16 bit | 'a', '\ u0031', '\ 201', '\ n', 4 |
curto | -32.768 ... 32.767 | 0 0 | 16 bit | 1, 3, 720, 22.000 |
int | -2 147 483 648 ... 2 147 483 647 | 0 0 | 32 bit | -2, -1, 0, 1, 9 |
longo | -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807 | 0 0 | 64 bits | -4000L, -900L, 10L, 700L |
flutuar | 3,40282347 x 1038, 1,40239846 x 10-45 | 0,0 | 32 bit | 1.67e200f, -1.57e-207f, .9f, 10.4F |
dobrar | Por que você está reportando essa página? | 0,0 | 64 bits | 1.e700d, -123457e, 37e1d |
Por que devo usar a sobrecarga de método?
O uso de sobrecarga torna seu código mais limpo e fácil de ler, além de ajudar a evitar erros no programa.
Em contraste com a Listagem 1, imagine um programa em que você terá muitos métodos de calculate()
com nomes semelhantes a calculate1
, calculate2
, calculate3
... não é bom, certo? A sobrecarga do método calculate()
permite que você use o mesmo nome e altere apenas o necessário - parâmetros. Também é muito fácil encontrar métodos sobrecarregados, pois eles estão agrupados em código.
Que sobrecarga não é
Lembre-se de que alterar um nome de variável não é uma sobrecarga. O código a seguir não compila:
public class Calculator { void calculate(int firstNumber, int secondNumber){} void calculate(int secondNumber, int thirdNumber){} }
Você também não pode sobrecarregar o método alterando o valor de retorno na assinatura do método. Este código também não compila:
public class Calculator { double calculate(int number1, int number2){return 0.0;} long calculate(int number1, int number2){return 0;} }
Sobrecarga de construtor
Você pode sobrecarregar o construtor da mesma maneira que o método:
public class Calculator { private int number1; private int number2; public Calculator(int number1) { this.number1 = number1; } public Calculator(int number1, int number2) { this.number1 = number1; this.number2 = number2; } }
Resolver o problema de sobrecarga de método
Você está pronto para o primeiro teste? Vamos descobrir!
Comece examinando cuidadosamente o seguinte código.
Listagem 2. O desafio da sobrecarga de método
public class AdvancedOverloadingChallenge3 { static String x = ""; public static void main(String... doYourBest) { executeAction(1); executeAction(1.0); executeAction(Double.valueOf("5")); executeAction(1L); System.out.println(x); } static void executeAction(int ... var) {x += "a"; } static void executeAction(Integer var) {x += "b"; } static void executeAction(Object var) {x += "c"; } static void executeAction(short var) {x += "d"; } static void executeAction(float var) {x += "e"; } static void executeAction(double var) {x += "f"; } }
Bom Você estudou o código. Qual será a conclusão?
- befe
- bfce
- efce
- aecf
A resposta correta é dada no final do artigo.
O que aconteceu agora? Como a JVM compila métodos sobrecarregados
Para entender o que aconteceu na Listagem 2, você precisa saber algumas coisas sobre como a JVM compila métodos sobrecarregados.
Primeiro de tudo, a JVM é razoavelmente preguiçosa: sempre fará o menor esforço para executar um método. Portanto, ao pensar em como a JVM lida com sobrecarga, lembre-se de três recursos importantes do compilador:
- Alargamento
- Embalagem (caixa automática e unboxing)
- Argumentos de comprimento variável (varargs)
Se você nunca se deparou com essas técnicas, alguns exemplos devem ajudá-lo a entendê-las. Observe que a JVM os executa na ordem em que estão listados.
Aqui está um exemplo de extensão:
int primitiveIntNumber = 5; double primitiveDoubleNumber = primitiveIntNumber ;
Esta é a ordem de extensão dos tipos primitivos:

( Nota do tradutor - No JLS, a extensão de primitivas é descrita com grandes variações, por exemplo, por muito tempo pode ser expandida em float ou double. )
Exemplo de embalagem automática:
int primitiveIntNumber = 7; Integer wrapperIntegerNumber = primitiveIntNumber;
Observe o que acontece nos bastidores ao compilar o código:
Integer wrapperIntegerNumber = Integer.valueOf(primitiveIntNumber);
Aqui está um exemplo de descompactação:
Integer wrapperIntegerNumber = 7; int primitiveIntNumber= wrapperIntegerNumber;
Aqui está o que acontece nos bastidores ao compilar este código:
int primitiveIntNumber = wrapperIntegerNumber.intValue();
E aqui está um exemplo de um método com argumentos de comprimento variável. Observe que os métodos de comprimento variável são sempre os últimos a serem executados.
execute(int... numbers){}
O que são argumentos de comprimento variável?
Os argumentos de comprimento variável são apenas uma matriz de valores dados por três pontos (...). Podemos passar tantos números int
para esse método.
Por exemplo:
execute(1,3,4,6,7,8,8,6,4,6,88...);
Argumentos de comprimento variável (varargs) são muito convenientes, pois os valores podem ser passados diretamente para um método. Se usássemos matrizes, teríamos que criar uma instância de matriz com valores.
Extensão: estudo de caso
Quando passamos o número 1 diretamente para o método executeAction()
, a JVM o interpreta automaticamente como um int
. É por isso que esse número não será passado para o executeAction(short var)
.
Da mesma forma, se passarmos o número 1.0
JVM reconhecerá automaticamente que é o dobro.
Obviamente, o número 1.0
também pode ser float
, mas o tipo de literais é predefinido. Portanto, na Listagem 2, o executeAction(double var)
é executeAction(double var)
.
Quando usamos o wrapper Double
, há duas opções: o número pode ser descompactado para um tipo primitivo ou pode ser expandido para Object
. (Lembre-se de que toda classe em Java estende a classe Object
.) Nesse caso, a JVM escolhe uma extensão do tipo Double
in Object
, porque requer menos esforço do que descompactar.
O último que passamos é 1L
e 1L
como especificamos o tipo, é long
.
Erros comuns de sobrecarga
Até agora, você provavelmente já entendeu que as coisas podem estar confusas com a sobrecarga de método, então vamos examinar alguns problemas que você provavelmente encontrará.
Autoboxing com wrappers
Java é uma linguagem de programação fortemente tipada e quando usamos o empacotamento automático com wrappers, há algumas coisas que precisamos considerar. Em primeiro lugar, o seguinte código não compila:
int primitiveIntNumber = 7; Double wrapperNumber = primitiveIntNumber;
O empacotamento automático funcionará apenas com o tipo double
porque, quando você compilar o código, será equivalente a isso:
Double number = Double.valueOf(primitiveIntNumber);
Este código será compilado. O primeiro int
será expandido para double
e depois empacotado em Double
. Mas, com o empacotamento automático, não há extensão de tipo e o construtor Double.valueof
espera um double
, não um int
. Nesse caso, o empacotamento automático funcionará se fizermos uma conversão de tipo explícita, por exemplo:
Double wrapperNumber = (double) primitiveIntNumber;
Lembre-se de que Integer
não pode ser Long
e Float
e não pode ser Double
. Não há herança. Cada um desses tipos ( Integer
, Long
, Float
e Double
) é Number
e Object
.
Em caso de dúvida, lembre-se de que os números do invólucro podem ser expandidos para Number
ou Object
. (Muito mais pode ser dito sobre invólucros, mas vamos deixar para outro artigo.)
Literais de código
Quando não especificamos o tipo de um número literal, a JVM calculará o tipo para nós. Se usarmos diretamente o número 1
no código, a JVM o criará como int
. Se tentarmos passar 1
diretamente para um método que aceita short
, ele não será compilado.
Por exemplo:
class Calculator { public static void main(String... args) {
A mesma regra será aplicada quando o número 1.0
. Embora possa ser um float
, a JVM o considerará um double
.
class Calculator { public static void main(String... args) {
Outro erro comum é a suposição de que Double
ou qualquer outro wrapper é melhor para um método que recebe double
.
O fato é que a JVM exige menos esforço para estender o wrapper Double
para Object
vez de descompactá-lo em um tipo double
primitivo.
Para resumir, quando usado diretamente no código java, 1
será int
e 1.0
será o double
. A extensão é a maneira mais fácil de executar; depois, há empacotamento ou descompactação e a última operação sempre será métodos de comprimento variável.
Como um fato curioso. Você sabia que o tipo char
aceita números?
char anyChar = 127;
O que você precisa se lembrar sobre sobrecarga
A sobrecarga é uma técnica muito poderosa para casos em que você precisa do mesmo nome de método com parâmetros diferentes. Essa é uma técnica útil, pois o uso dos nomes corretos facilita a leitura do código. Em vez de duplicar o nome do método e adicionar confusão ao seu código, você pode simplesmente sobrecarregá-lo.
Isso mantém o código limpo e fácil de ler, além de reduzir o risco de métodos duplicados quebrarem parte do sistema.
O que você deve ter em mente: ao sobrecarregar o método, a JVM fará o mínimo de esforço possível.
Aqui está a ordem do caminho mais lento para a execução:
- O primeiro está aumentando.
- O segundo é o boxe
- Terceiro, argumentos de tamanho variável (varargs)
Pontos a serem considerados: situações difíceis surgem ao declarar números diretamente: 1
será int
e 1.0
será o double
.
Lembre-se também de que você pode declarar esses tipos explicitamente usando a sintaxe 1F
ou 1f
para float
e 1D
ou 1d
para double
.
Isso conclui o papel da JVM na sobrecarga de método. É importante entender que a JVM é inerentemente preguiçosa e sempre seguirá o caminho mais preguiçoso.
A resposta
A resposta para a Listagem 2 é a Opção 3. efce.
Saiba mais sobre a sobrecarga de método em Java
Uma introdução a classes e objetos para iniciantes absolutos, incluindo pequenas seções sobre métodos e sobrecarga de métodos.
Saiba mais sobre por que é importante que o Java seja uma linguagem fortemente tipada e aprenda sobre os tipos primitivos do Java.
Aprenda as limitações e desvantagens da sobrecarga de método, bem como resolvê-las usando tipos personalizados e objetos de parâmetro.