วิธีการโอเวอร์โหลดใน JVM

ยินดีต้อนรับสู่บล็อกJava Challengersใหม่! บล็อกนี้มีไว้สำหรับแนวคิดที่ท้าทายในการเขียนโปรแกรม Java เชี่ยวชาญพวกเขาแล้วคุณจะก้าวไปสู่การเป็นโปรแกรมเมอร์ Java ที่มีทักษะสูง

เทคนิคในบล็อกนี้ต้องใช้ความพยายามในการฝึกฝน แต่จะสร้างความแตกต่างอย่างมากในประสบการณ์ประจำวันของคุณในฐานะนักพัฒนา Java การหลีกเลี่ยงจุดบกพร่องนั้นง่ายกว่าเมื่อคุณรู้วิธีใช้เทคนิคการเขียนโปรแกรม Java หลักอย่างถูกต้องและการติดตามจุดบกพร่องนั้นง่ายกว่ามากเมื่อคุณรู้ว่าเกิดอะไรขึ้นกับโค้ด Java ของคุณ

คุณพร้อมที่จะเริ่มเรียนรู้แนวคิดหลักในการเขียนโปรแกรม Java แล้วหรือยัง? มาเริ่มกับ Java Challenger เครื่องแรกของเรากันเลย!  

คำศัพท์: วิธีการโอเวอร์โหลด

เนื่องจากคำว่าโอเวอร์โหลดนักพัฒนามักคิดว่าเทคนิคนี้จะทำให้ระบบทำงานหนักเกินไป แต่นั่นไม่เป็นความจริง ในการเขียนโปรแกรมmethod overloadingหมายถึงการใช้ชื่อ method เดียวกันกับพารามิเตอร์ที่ต่างกัน 

วิธีการโอเวอร์โหลดคืออะไร?

Method overloadingเป็นเทคนิคการเขียนโปรแกรมที่ช่วยให้นักพัฒนาสามารถใช้ชื่อเมธอดเดียวกันได้หลายครั้งในคลาสเดียวกัน แต่มีพารามิเตอร์ต่างกัน กรณีนี้เราว่าเมธอดเกิน รายการที่ 1 แสดงวิธีการเดียวที่มีพารามิเตอร์จำนวนประเภทและลำดับที่แตกต่างกัน

รายการ 1. สามประเภทของวิธีการมากเกินไป

 Number of parameters: public class Calculator { void calculate(int number1, int number2) { } void calculate(int number1, int number2, int number3) { } } Type of parameters: public class Calculator { void calculate(int number1, int number2) { } void calculate(double number1, double number2) { } } Order of parameters: public class Calculator { void calculate(double number1, int number2) { } void calculate(int number1, double number2) { } } 

วิธีการโอเวอร์โหลดและประเภทดั้งเดิม

ในรายการ 1 คุณจะเห็นประเภทดั้งเดิมintและdouble. เราจะทำงานมากขึ้นกับประเภทเหล่านี้และประเภทอื่น ๆ ดังนั้นโปรดใช้เวลาสักครู่เพื่อตรวจสอบประเภทดั้งเดิมใน Java

ตารางที่ 1. ประเภทดั้งเดิมใน Java

ประเภท พิสัย ค่าเริ่มต้น ขนาด ตัวอย่างตัวอักษร
 boolean  จริงหรือเท็จ  เท็จ  1 บิต  ถูกผิด
 byte  -128 .. 127  0  8 บิต  1, -90, 128
 char  อักขระ Unicode หรือ 0 ถึง 65,536  \ u0000  16 บิต  'a', '\ u0031', '\ 201', '\ n', 4
 short  -32,768 .. 32,767  0  16 บิต  1, 3, 720, 22,000
 int  -2,147,483,648 .. 2,147,483,647  0  32 บิต  -2, -1, 0, 1, 9
 long  -9,223,372,036,854,775,808 ถึง 9,223,372,036,854,775,807  0  64 บิต  -4000L, -900L, 10L, 700L
 float  3.40282347 x 1038, 1.40239846 x 10-45  0.0  32 บิต  1.67e200f, -1.57e-207f, .9f, 10.4F
 double

 1.7976931348623157 x 10308, 4.9406564584124654 x 10-324

 0.0  64 บิต  1.e700d, -123457e, 37e1d

เหตุใดฉันจึงควรใช้วิธีการโอเวอร์โหลด

การโหลดมากเกินไปทำให้โค้ดของคุณสะอาดและอ่านง่ายขึ้นและยังอาจช่วยคุณหลีกเลี่ยงข้อบกพร่องในโปรแกรมของคุณ

ในทางตรงกันข้ามกับรายชื่อ 1 คิดโปรแกรมที่คุณมีหลายcalculate()วิธีการที่มีชื่อเช่น1calculate , . . ไม่ดีใช่ไหม? การใช้เมธอดมากเกินไปทำให้คุณใช้ชื่อเมธอดเดียวกันได้ในขณะที่เปลี่ยนเฉพาะสิ่งที่ต้องเปลี่ยนเท่านั้น: พารามิเตอร์ นอกจากนี้ยังง่ายมากในการค้นหาวิธีการที่มากเกินไปเนื่องจากมีการรวมกลุ่มกันในโค้ดของคุณcalculate2calculate3calculate()

สิ่งที่ไม่เกินกำลัง

โปรดทราบว่าการเปลี่ยนชื่อตัวแปรไม่ได้มากเกินไป รหัสต่อไปนี้จะไม่รวบรวม:

 public class Calculator { void calculate(int firstNumber, int secondNumber){} void calculate(int secondNumber, int thirdNumber){} } 

นอกจากนี้คุณไม่สามารถโอเวอร์โหลดเมธอดได้โดยการเปลี่ยนประเภทการส่งคืนในลายเซ็นของวิธีการ รหัสต่อไปนี้จะไม่รวบรวม:

 public class Calculator { double calculate(int number1, int number2){return 0.0;} long calculate(int number1, int number2){return 0;} } 

ตัวสร้างโอเวอร์โหลด

คุณสามารถโอเวอร์โหลดคอนสตรัคเตอร์ได้เช่นเดียวกับวิธีการ:

 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; } } 

ใช้วิธีการที่ท้าทายมากเกินไป!

คุณพร้อมสำหรับ Java Challenger เครื่องแรกหรือยัง? มาหาคำตอบกัน!

เริ่มต้นด้วยการตรวจสอบรหัสต่อไปนี้อย่างรอบคอบ

รายชื่อ 2. วิธีการขั้นสูงความท้าทายที่มากเกินไป

 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"; } } 

โอเคคุณได้ตรวจสอบโค้ดแล้ว ผลลัพธ์คืออะไร?

  1. befe
  2. bfce
  3. efce
  4. aecf

ตรวจสอบคำตอบของคุณที่นี่

เกิดอะไรขึ้น? วิธีที่ JVM รวบรวมวิธีการที่มากเกินไป

เพื่อให้เข้าใจว่าเกิดอะไรขึ้นในรายการที่ 2 คุณจำเป็นต้องรู้บางสิ่งเกี่ยวกับวิธีที่ JVM รวบรวมวิธีการที่มากเกินไป

ประการแรก JVM เป็นคนขี้เกียจอย่างชาญฉลาด : มันจะใช้ความพยายามน้อยที่สุดเท่าที่จะเป็นไปได้ในการดำเนินการตามวิธีการ ดังนั้นเมื่อคุณกำลังคิดถึงวิธีที่ JVM จัดการกับการโอเวอร์โหลดโปรดจำไว้ว่าเทคนิคคอมไพเลอร์ที่สำคัญสามประการ:

  1. ขยับขยาย
  2. ชกมวย (autoboxing และ unboxing)
  3. วาร์กส์

หากคุณไม่เคยพบเทคนิคทั้งสามนี้มาก่อนตัวอย่างบางส่วนจะช่วยให้ชัดเจน หมายเหตุว่า JVM รันพวกเขาในการสั่งซื้อที่กำหนด

นี่คือตัวอย่างของการขยับขยาย :

 int primitiveIntNumber = 5; double primitiveDoubleNumber = primitiveIntNumber ; 

นี่คือลำดับของประเภทดั้งเดิมเมื่อกว้างขึ้น:

ราฟาเอลเดลเนโร

นี่คือตัวอย่างของการทำกล่องอัตโนมัติ :

 int primitiveIntNumber = 7; Integer wrapperIntegerNumber = primitiveIntNumber; 

สังเกตสิ่งที่เกิดขึ้นเบื้องหลังเมื่อคอมไพล์โค้ดนี้:

 Integer wrapperIntegerNumber = Integer.valueOf(primitiveIntNumber); 

และนี่คือตัวอย่างของการ  แกะกล่อง :

 Integer wrapperIntegerNumber = 7; int primitiveIntNumber= wrapperIntegerNumber; 

นี่คือสิ่งที่เกิดขึ้นเบื้องหลังเมื่อรวบรวมโค้ดนี้:

 int primitiveIntNumber = wrapperIntegerNumber.intValue(); 

และนี่คือตัวอย่างของvarargs ; โปรดทราบว่าvarargsเป็นสิ่งสุดท้ายที่จะดำเนินการเสมอ:

 execute(int… numbers){} 

varargs คืออะไร?

Used for variable arguments, varargs is basically an array of values specified by three dots (…) We can pass however many int numbers we want to this method.

For example:

execute(1,3,4,6,7,8,8,6,4,6,88...); // We could continue… 

Varargs is very handy because the values can be passed directly to the method. If we were using arrays, we would have to instantiate the array with the values.

Widening: A practical example

When we pass the number 1 directly to the executeAction method, the JVM automatically treats it as an int. That’s why the number doesn't go to the executeAction(short var) method.

Similarly, if we pass the number 1.0, the JVM automatically recognizes that number as a double.

Of course, the number 1.0 could also be a float, but the type is pre-defined. That’s why the executeAction(double var) method is invoked in Listing 2.

When we use the Double wrapper type, there are two possibilities: either the wrapper number could be unboxed to a primitive type, or it could be widened into an Object. (Remember that every class in Java extends the Object class.) In that case, the JVM chooses to wided the Double type to an Object because it takes less effort than unboxing would,  as I explained before.

The last number we pass is 1L, and because we've specified the variable type this time, it is long.

Video challenge! Debugging method overloading

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the method overloading challenge:

Common mistakes with overloading

By now you’ve probably figured out that things can get tricky with method overloading, so let’s consider a few of the challenges you will likely encounter.

Autoboxing with wrappers

Java is a strongly typed programming language, and when we use autoboxing with wrappers there are some things we have to keep in mind. For one thing, the following code won't compile:

 int primitiveIntNumber = 7; Double wrapperNumber = primitiveIntNumber; 

Autoboxing will only work with the double type because what happens when you compile this code is the same as the following:

 Double number = Double.valueOf(primitiveIntNumber); 

The above code will compile. The first int type will be widened to double and then it will be boxed to Double. But when autoboxing, there is no type widening and the constructor from Double.valueOf will receive a double, not an int. In this case, autoboxing would only work if we applied a cast, like so:

 Double wrapperNumber = (double) primitiveIntNumber; 

Remember that Integer cannot be Long and Float cannot be Double. There is no inheritance. Each of these types--Integer, Long, Float, and Double--is a Number and an Object.

When in doubt, just remember that wrapper numbers can be widened to Number or Object. (There is a lot more to explore about wrappers but I will leave it for another post.)

Hard-coded number types in the JVM

When we don’t specify a type to a number, the JVM will do it for us. If we use the number 1 directly in the code, the JVM will create it as an int. If you try to pass 1 directly to a method that is receiving a short, it won’t compile.

For example:

 class Calculator { public static void main(String… args) { // This method invocation will not compile // Yes, 1 could be char, short, byte but the JVM creates it as an int calculate(1); } void calculate(short number) {} } 

The same rule will be applied when using the number 1.0; although it could be a float, the JVM will treat this number as a double:

 class Calculator { public static void main(String… args) { // This method invocation will not compile // Yes, 1 could be float but the JVM creates it as double calculate(1.0); } void calculate(float number) {} } 

Another common mistake is to think that the Double or any other wrapper type would be better suited to the method that is receiving a double. In fact, it takes less effort for the JVM to widen the Double wrapper to an Object instead of unboxing it to a double primitive type.

To sum up, when used directly in Java code, 1 will be int and 1.0 will be double. Widening is the laziest path to execution, boxing or unboxing comes next, and the last operation will always be varargs.

As a curious fact, did you know that the char type accepts numbers?

 char anyChar = 127; // Yes, this is strange but it compiles 

What to remember about overloading

Overloading is a very powerful technique for scenarios where you need the same method name with different parameters. It’s a useful technique because having the right name in your code makes a big difference for readability. Rather than duplicate the method and add clutter to your code, you may simply overload it. Doing this keeps your code clean and easy to read, and it reduces the risk that duplicate methods will break some part of the system.

What to keep in mind: When overloading a method the JVM will make the least effort possible; this is the order of the laziest path to execution:

  • First is widening
  • Second is boxing
  • Third is Varargs

What to watch out for: Tricky situations will arise from declaring a number directly: 1 will be int and 1.0 will be double.

Also remember that you can declare these types explicitly using the syntax of 1F or 1f for a float or 1D or 1d for a double.

That concludes our first Java Challenger, introducing the JVM’s role in method overloading. It is important to realize that the JVM is inherently lazy, and will always follow the laziest path to execution.

 

Answer key

The answer to the Java Challenger in Listing 2 is: Option 3. efce.

More about method overloading in Java

  • Java 101: Classes and objects in Java: A true beginner’s introduction to classes and objects, including short sections on methods and method overloading.
  • Java 101: Elementary Java language features: Learn more about why it matters that Java is a strongly typed language and get a full introduction to primitive types in Java.
  • พารามิเตอร์มากเกินไปในเมธอด Java ตอนที่ 4: สำรวจข้อ จำกัด และข้อเสียของการโอเวอร์โหลดของเมธอดและวิธีแก้ไขโดยการรวมชนิดที่กำหนดเองและอ็อบเจ็กต์พารามิเตอร์

เรื่องนี้ "Method overloading in the JVM" เผยแพร่ครั้งแรกโดย JavaWorld