Effective java6——方法重载

本文详细解释了Java中的方法重载(Overload)概念,包括重载如何选择最合适的方法版本、不同类型转换的情况以及泛型和自动装箱拆箱可能引起的重载混乱。并通过具体实例帮助理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

很多人分不清java的方法重载(Overload)和方法覆盖(Override),方法重载是方法名相同,而方法的参数不同(参数个数或者类型,注意不能通过参数名称和方法的返回值来进行方法重载),编译器在编译的时候就已经确定了合适的目标方法,也称为早绑定或者静态分派;方法覆盖是子类拥有和父类相同签名的方法(包括参数和返回值),方法覆盖在编译时无法确定要调用的目标方法,只有在运行时由对象本身来确定合适的调用方法,也称为晚绑定或者动态分派,严格意思上来讲,java的多态主要是使用了方法覆盖。

(1)方法重载选择最合适的版本:

方法重载依赖静态类型来定位方法执行版本的分派动作,被称为静态分派,静态分派发生在编译阶段,因此确定静态分派的动作实际上是由编译器来执行而不是由java虚拟机来执行的。另外编译器虽然可以确定出方法的重载版本,但是在很多情况下方法重载的版本可能不是唯一的,往往只能确定一个更加合适的版本,下面的例子演示方法重载中如何选择更加合适的版本:

public class Overload{

     public static void sayHello(Object arg){

         System.out.println("hello Object");

     }

    public static void sayHello(int arg){

           System.out.println("hello int");

    }

    public static void sayHello(long arg){

         System.out.println("hello long");

     }

   public static void sayHello(Character arg){

           System.out.println("hello Character");

   }

   public static void sayHello(char arg){

        System.out.println("hello char");

   }

  public static void sayHello(char ...arg){

         System.out.println("hello char...");

   }

   public static void sayHello(Serializable arg){

         System.out.println("hello Serializable");

    }

   public static void main(String[] args){

         sayHello('a');

   }

}

上面代码运行时会输出:

hello char

这个很容易理解,因为输入参数'a'是一个char类型数据,编译器会选择char类型参数的重载方法。如果注释掉sayHello(char arg)方法,那么输出会变成:

hello int

这次发生了一次类型的自动转换,'a'除了表示一个字符外,还可以表示数组65(a的ascii码),因此编译器选择int类型参数的方法重载也是合适的,如果再次注释掉sayHello(int arg)方法,那么输出会变成:

hello long

这次发生了两次类型的自动转换,首先'a'转换为整数65,然后进一步转换为长整型65L(java中byte->char(short)->int->long->float->double是可以自动转换的),匹配了long类型参数的方法重载。如果再次注释掉sayHello(long arg)方法,那么输出变成:

hello Character

这次发生了一次java基本数据类型自动装箱,'a'被封装为其包装类Character,编译器选择了Character类型参数的重载方法。如果再次注释掉sayHello(Character)方法,那么输出会变成:

hello Serializable

这个结果相信很多人都无法预料,首先'a'被封装为其包装类Character,此时找不到合适的匹配参数,只能继续向上转型为其父类型或者父接口,Character实现了Serializable接口,因此编译器选择了Serializable类型参数重载方法。如果再次注释掉sayHello(Serializable arg)方法,那么输出会变成:

hello Object

这个容易理解,首先'a'被封装为其包装类Character,此时找不到合适的匹配参数,只能继续向上转型为其父类或者父接口,如果找不到任何的父类或者父接口,则java中所有的类都是Object的子类,因此编译器会选择参数类型为Object的重载方法。如果再次注释掉sayHello(Object arg)方法,那么输出会变成:

hello char...

只剩最后一个重载方法了,此时字符'a'被当作字符数组的一个元素,可变参数是优先级最低的。

(2)方法重载时不会自动向下进行类型转换:

下面一个例子演示使用方法重载时不会自动向下进行类型转换:

public class CollectionClassifier{

    public static String classify(Set<?> s){

               return "Set';

    }

    public static String classify(List<?> l){

          return "List";

    }

   public static String classify(Collection<?> c){

         return "Unknown Collection";

    }

   public static void main(String[] args){

          Collection<?>[] collections = {

                new HashSet<String>(),new ArrayList<BigInteger>(),new HashMap<String>()};

           for(Collection<?> c : collections){

                System.out.println(classify(c));

       }

   }

}

本来期望这个程序会打印出"Set" "List” 和"Unknown Collection",但是运行过后打印出来3次"Unknown Collecion"。出现这个问题的原因是在for循环时编译类型都是Collection<?>,因此调用的也是Collection<?>参数类型的重载方法。

(3)泛型和自动装箱拆箱可能引起方法重载混乱:

下面的例子期望把集合中0,1,2三个元素删除:

public class SetList{

    public static void main(String[] args){

          Set<Integer> set = new TreeSet<Integer>();

          List<Integer> list = new ArrayList<Integer>();

          for(int i=-3;i<3;i++){

              set.add(i);

              list.add(i);

       }

       for(int i=0;i<3;i++){

                set.remove(i);

               list.remove(i);

          }

          System.out.println(set + "  " + list);

     }

}

我们期望打印出:

[-3,-2,-1] [-3,-2,-1]

而程序真正运行打印出的结果是:

[-3,-2,-1] [-2,0,2]

为什么List打印结果令人感到混乱且无法理解?

实际情况是:

Set集合是无序的,即元素没有下标索引,因此set.remove(i)调用选择的是重载方法remove(E),这里的E是集合的元素类型,因此程序会将i从int自动装箱为Integer,因此程序符合我们的预期。

List集合是有序的,即元素是有下标索引的,因此List的remove方法有两个:remove(int)根据元素下标删除元素,remove(E)直接删除指定的元素。上述程序中list.remove(i)调用重载的remove(int)方法,即首先删除列表[-3,-2,-2,0,1,2]中的第一个元素-3,接着删除列表[-2,-1,0,1,2]中第一个元素-1,最后删除列表[-2,0,1,2]中第二个元素1,因此得到的结果是[-2,0,2]。

如果想要得到预期的结果,需要将代码重构如下:

for(int i=0;i<3;i++){

        set.remove(i);

        list.remove((Integer)i);//或者list.reomve(Integer.valueOf(i));

  }

在JDK1.5之前,List没有被泛化,List接口的remove方法是remove(Object)和remove(int),由于int和Objec根本不同,并且由于JDK1.5之前没有自动装箱,因此不会出现重载问题。而在JDK1.5之后,List<E>被泛化,List<E>接口的remove变成了remove(E)和remove(int),泛型和自动装箱同时出现之后就有可能引起重载混乱。

由于方法重载这种静态分派的特性,如果使用不当可能不会产生我们预期的结果,同时也很难理解根本原因是什么,因此在编程中请务必保证当传递同样的参数时所用重载的方法行为一致,否则请谨慎使用方法重载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值