Good day to all.
We have already launched another stream of the Java Developer course, but we still have some materials that we would like to share with you.
Welcome to the Java Challengers series! This series of articles focuses on the features of Java programming. Their development is your way to becoming a highly qualified Java programmer.
Mastering the techniques covered in this series of articles requires some effort, but they will make a big difference in your everyday experience as a java developer. Avoiding errors is easier when you know how to properly apply basic Java programming techniques and track errors much easier when you know exactly what is happening in your java code.
Are you ready to start mastering the basic concepts of Java programming? Then let's start with our first task!
Term "Method Overload"
About the term overload, developers tend to think that this is about rebooting the system, but it is not. In programming, method overloading means using the same method name with different parameters.
Method overloading is a programming technique that allows a developer to use the same name for methods with different parameters in the same class. In this case, we say that the method is overloaded.
Listing 1 shows methods with different parameters that differ in number, type, and order.
Listing 1. Three options for overloading methods.
// public class Calculator { void calculate(int number1, int number2) { } void calculate(int number1, int number2, int number3) { } } // public class Calculator { void calculate(int number1, int number2) { } void calculate(double number1, double number2) { } } // public class Calculator { void calculate(double number1, int number2) { } void calculate(int number1, double number2) { } }
In Listing 1, you saw the primitive types int
and double
. Let's digress for a minute and recall the primitive types in Java.
Table 1. Primitive types in Java
Type of | Range | Default value | The size | Literal examples |
---|---|---|---|---|
boolean | true or false | false | 1 bit | true false |
byte | -128 ... 127 | 0 | 8 bit | 1, -90, -128 |
char | Unicode character or from 0 to 65 536 | \ u0000 | 16 bits | 'a', '\ u0031', '\ 201', '\ n', 4 |
short | -32,768 ... 32,767 | 0 | 16 bits | 1, 3, 720, 22,000 |
int | -2 147 483 648 ... 2 147 483 647 | 0 | 32 bits | -2, -1, 0, 1, 9 |
long | -9 223 372 036 854 775 808 to 9 223 372 036 854 775 807 | 0 | 64 bit | -4000L, -900L, 10L, 700L |
float | 3.40282347 x 1038, 1.40239846 x 10-45 | 0.0 | 32 bits | 1.67e200f, -1.57e-207f, .9f, 10.4F |
double | 1.7976931348623157 x 10308, 4.9406564584124654 x 10-324 | 0.0 | 64 bit | 1.e700d, -123457e, 37e1d |
Using overload makes your code cleaner and easier to read, and also helps to avoid errors in the program.
As opposed to Listing 1, imagine a program where you will have many methods of calculate()
with names similar to calculate1
, calculate2
, calculate3
... not good, right? Overloading the calculate()
method allows you to use the same name and change only what is needed - the parameters. It is also very easy to find overloaded methods, since they are grouped in code.
Remember that changing a variable name is not an overload. The following code will not compile:
public class Calculator { void calculate(int firstNumber, int secondNumber){} void calculate(int secondNumber, int thirdNumber){} }
You also cannot overload the method by changing the return value in the method signature. This code also does not compile:
public class Calculator { double calculate(int number1, int number2){return 0.0;} long calculate(int number1, int number2){return 0;} }
You can overload the constructor in the same way as the method:
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; } }
Are you ready for the first test? Let's find out!
Start by carefully studying the following code.
Listing 2. Difficult task overloading methods
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"; } }
Good. You have studied the code. What will be the conclusion?
The correct answer is given at the end of the article.
To understand what happened in Listing 2, you need to know a few things about how the JVM compiles the overloaded methods.
First of all, the JVM is reasonably lazy: it will always make the least effort to implement the method. Thus, when you think about how the JVM handles overload, keep in mind three important features of the compiler:
If you have never encountered these techniques, then a few examples should help you understand them. Note that the JVM performs them in the order in which they are listed.
Here is an example of the extension:
int primitiveIntNumber = 5; double primitiveDoubleNumber = primitiveIntNumber ;
This is the extension order of primitive types:
( Note of the translator - In JLS, the primitive extension is described with large variations, for example, long can be expanded in float or in double. )
Example of autopacking:
int primitiveIntNumber = 7; Integer wrapperIntegerNumber = primitiveIntNumber;
Notice what happens behind the scenes when compiling code:
Integer wrapperIntegerNumber = Integer.valueOf(primitiveIntNumber);
Here is an example of unpacking:
Integer wrapperIntegerNumber = 7; int primitiveIntNumber= wrapperIntegerNumber;
This is what happens behind the scenes when compiling this code:
int primitiveIntNumber = wrapperIntegerNumber.intValue();
And here is an example of a method with variable length arguments. Note that variable length methods are always the last to execute.
execute(int... numbers){}
Variable length arguments are simply an array of values, given by three dots (...). We can pass as many int
numbers as necessary to this method.
For example:
execute(1,3,4,6,7,8,8,6,4,6,88...); // ...
Variable-length arguments (varargs) are very convenient in that values can be passed directly to a method. If we used arrays, we would have to create an array instance with values.
When we pass the number 1 directly to the executeAction()
method, the JVM automatically interprets it as an int
. That is why this number will not be passed to the executeAction(short var)
method.
Similarly, if we pass the number 1.0
JVM automatically recognizes that it is double.
Of course, the number 1.0
can also be a float
, but the type of such literals is predetermined. Therefore, in Listing 2, the executeAction(double var)
method executeAction(double var)
is executed.
When we use the Double
wrapper, there are two options: either the number can be decompressed into a primitive type, or it can be expanded in Object
. (Remember that every class in Java extends the Object
class.) In this case, the JVM chooses an extension of type Double
in Object
, because it requires less effort than unpacking.
Last we pass 1L
and since, we have specified the type - this is long
.
By now you have probably realized that with the overloading of methods everything can be confusing, so let's look at a few problems you are likely to encounter.
Java is a strongly typed programming language and, when we use autopacking with wrappers, there are a few things we need to consider. First, the following code does not compile:
int primitiveIntNumber = 7; Double wrapperNumber = primitiveIntNumber;
Autopack will only work with double
type because when you compile the code, it will be equivalent to this:
Double number = Double.valueOf(primitiveIntNumber);
This code will compile. The first int
will be expanded to double
and then packed in Double
. But when autopacking there is no type expansion and the Double.valueof
constructor expects a double
, not an int
. In this case, autopacking will work if we do an explicit type conversion, for example:
Double wrapperNumber = (double) primitiveIntNumber;
Remember that the Integer
cannot be Long
and Float
and cannot be Double
. There is no inheritance. Each of these types ( Integer
, Long
, Float
, and Double
) is Number
and Object
.
If in doubt, just remember that wrappers numbers can be expanded to Number
or Object
. (There is a lot more that can be said about wrappers, but let's leave it for another article.)
When we do not specify a literal number type, the JVM will calculate the type for us. If we directly use the number 1
in the code, the JVM will create it as an int
. If we try to pass 1
directly to the method that short
accepts, it will not compile.
For example:
class Calculator { public static void main(String... args) { // // , char, short, byte, JVM int calculate(1); } void calculate(short number) {} }
The same rule will apply when the number 1.0
. Although it may be a float
, the JVM will consider it a double
.
class Calculator { public static void main(String... args) { // // , float, JVM double calculate(1.0); } void calculate(float number) {} }
Another common mistake is to assume that Double
or any other wrapper is better suited for the method that receives a double
.
The fact is that the JVM requires less effort to expand the Double
wrapper in Object
instead of unpacking it into the primitive type double
.
To summarize, when used directly in the java-code, 1
will be int
and 1.0
will be double
. Expansion is the easiest way to accomplish, then comes packaging or unpacking and the last operation will always be variable length methods.
Like a curious fact. Did you know that the type char
takes numbers?
char anyChar = 127; // , ,
Overloading is a very powerful technique for when you need the same method name with different parameters. This is a useful technique because using the correct names makes the code easier to read. Instead of duplicating the name of the method and adding clutter to your code, you can simply overload it.
This allows you to keep the code clean and easy to read, and also reduces the risk that duplicate methods will break part of the system.
What should be borne in mind: in case of overloading, the JVM will make the least effort possible.
Here is the order of the laziest way to execute:
What should be considered: difficult situations arise when declaring numbers directly: 1
will be int
and 1.0
will be double
.
Also remember that you can declare these types explicitly using the syntax 1F
or 1f
for float
and 1D
or 1d
for double
.
This concludes the role of JVM in method overloading. It is important to understand that the JVM is inherently lazy, and will always follow the most lazy path.
The answer to Listing 2 is Option 3. efce.
Introduction to classes and objects for absolute beginners, including small sections on methods and method overloading.
Learn more about why it is important that Java is a strongly typed language and learn Java primitive types.
Learn the limitations and disadvantages of overloading methods, as well as ways to eliminate them by using custom types and parameter objects.
Source: https://habr.com/ru/post/428307/
All Articles