[翻译]方法重载在JVM中的实现原理(Method overloading in the JVM)

原文链接:https://www.javaworld.com/article/3268983/learn-java/java-challengers-1-method-overloading-in-the-jvm.html

什么是方法重载?

方法重载是一种允许开发者在同一个类中多次使用拥有不同参数及相同方法名方法的一种编程技巧。在这种情况下,我们称之为方法被重载。下面的示例显示了拥有不同参数(不同之处包括参数数量,参数类型及参数顺序)的简单方法:

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) { }
}

方法重载及基本数据类型

在上述示例中,你能看到该示例存在基本数据类型int和double。因为我们会频繁使用这些数据类型,这里我们复习下Java中的基本数据类型。


为什么要使用方法重载?

重载能够使你的代码变得更加简洁易读同时能够有效帮助你减少程序中的bug。

想象一下如果在你的程序存在多个与calculate()方法名相似的方法比如:calculate1,calculate2,calculate3...看起来感觉不太好,对不对?

重载calculate()方法能够让你在仅改变方法参数的情况下使用相同的方法名。与此同时,找到这些重载的方法也是十分容易的因为他们通常都会在你及代码中分组在同一片区域中。

哪些情况下不是重载?

需要注意的是仅改变参数名称不能称之为重载。下面的代码不能编译:

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

    void calculate(int secondNumber, int thirdNumber){}

}

你同样不能通过改变方法的返回值类型来实现方法的重载。下面的代码同样不会编译:

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

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

}

重载试题

仔细阅读下列代码

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)edce

4)aecf


JVM是如何编译重载方法的?

为了理解方法是如何被重载的,你需要了解一些关于JVM编译重载方法的知识。

首先,JVM是很“懒”的,它总是仅使用最高效的方式来执行一个方法。因此当你在思考JVM是如何操控重载时,你需要牢记3个重要的编程技巧:

    1)扩展

    2)装箱(自动装箱/拆箱)

    3)可变参数

如果你不了解这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();

这里是一个可变参数的示例,注意可变参数总是最后被执行的:

execute(int… numbers){}

什么是可变参数?

用于可变参数,varargs总体上来说就是被简写为三个点(...)的一组数组的值。我们能够传递任意数量的int类型的值给这个方法:

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

Varargs非常便利因为这些值能够直接传递到方法中。如果我们使用arrays作为参数,我们必须先实例化一个带有对应值的array。

扩展 一个实用的例子

当我们直接传递number1至executeAction方法中,JVM会自动将他识别成一个int类型的值。这是为什么这个数值没有进入executeAction(short var)的原因。

类似的,如果我们传递1.0,JVM会自动将他识别成一个double类型的数值。

当然,1.0也能够使一个float类型的值,但是它的类型已经被预先定义了。

如果使用Double包装类,则会出现两种可能的情况:包装的数值被解箱成一个基本数据类型或者扩展为一个Object。(记住在Java中每一个类都继承了Object)在这种情况下,JVM选择将Double类型扩展为Object,因为这样比拆箱更简单。

关于重载的常见误区

到目前为止你可能发现与方法重载相关的东西有些难易掌握,这里我们列举几个你有可能会碰到的示例。

用包装类进行封箱

Java是一种强类型语言,当我们使用包装类进行装箱的时候有些事情是需要我们特别注意的。举例说明,下面的代码不会编译:

int primitiveIntNumber = 7;
Double wrapperNumber = primitiveIntNumber;

在这里自动装箱只有与double类型一起才有效,这是因为这段代码实际操作是这样:

Double number = Double.valueOf(primitiveIntNumber);

上述代码能够成功地编译。首先该int类型的数值会被转换成double然后再被封装成Double。但是当我们使用自动封装时,因为不存在上述的第一步也就是扩展操作,与此同时Double.valueOf方法接收的是double类型而不是int类型。在这种情况下,如果想正常实现装箱,可采用如下方法:

Double wrapperNumber = (double) primitiveIntNumber;

注意,Integer不能转化为Long,Float也不能转化为Double因为他们直接没有继承关系。Integet,Long,Float以及Double对应的对象只能是一个Number或者一个Object。

JVM中的硬编码数据类型

当我们不为一个数据指定具体类型,JVM会帮我们处理。如果我们直接在代码中使用数字1,JVM会把它视为一个int类型。如果你尝试将数字1直接传给传入参数为short的方法中,该程序不能编译成功。举例如下:

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) {}
  
  }

相同的规则同样适用于数字1.0,尽管它是一个float类型,JVM会将其设置为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) {}  
  }
另一个误区是Double或者其他转换类型对于传入参数为double的方法来说更合适。事实上,JVM能够更高效地将Double转换成Object而不是解箱成一个double基本数据类型。

总的来说,在Java中,1会被JVM当成是int而1.0则是double。扩展是最“懒”的转换方式,封/拆箱次之再次之则是可变参数。

另一个奇特的事实,你认为char类型能够接受数字吗?

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

关于重载需要注意的知识点

重载是一个非常强大的技术,它能允许你编写出不同参数相同方法名的方法。这是一项十分有用的技巧因为它能够使你的代码根据可读性。你能够轻松简单地重载方法从而避免代码出现重复以及过度复杂的情况。使用重载能够令你的代码变得更为简洁同时十分易读同时能够减少重复方法带来的系统崩溃的风险。

需要记住的是,当重载方法时,JVM会使用最简单的操作:

1)首先是扩展

2)其次选择装箱

3)最后是可变参数


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值