【笔记52】明智审慎地使用重载

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, String>().values()
        };
        for(Collection<?> c : collections) {
            System.out.println(classify(c));
        }
    }
}

期望打出的是:set,list,Unknown Collection。实际上,它的输出是:

Unknown Collection
Unknown Collection
Unknown Collection

classify方法被重载,而要调用哪个重载方法是在编译时做出决定的。每次运行时的类型都不同,但这不影响对重载方法的选择。因为该参数的编译时类型都是Collection<?>,所以唯一适合的方法是第三个,参数为Collection<?>。

对于重载方法(overloaded method)的选择是静态的,而对于被覆盖的方法(overridden method)的选择则是动态的。选择被覆盖的方法的正确版本是在运行时进行的,选择的依据是被调用方法所在对象的运行时类型。这里重新说明一下,当一个子类包含的方法声明与其祖先类中的方法声明具有同样的签名时,方法就被覆盖了。如果实例方法在子类中被覆盖了,并且这个方法是在该子类的实例上被调用的,那么子类中的覆盖方法(overriding method)将会执行,而不管该子类实例的编译时类型到底是什么。

修正方案,使用单个方法替换重载的classify方法,也就是不使用重载机制:

public static String classify(Collection<?> c) {
    return c instanceof Set ? "Set" :
            c instanceof List ? "List" : Unknown Collection";
}

到底怎样才算胡乱使用重载机制呢?安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法。如果方法使用可变参数(varargs),保守的策略是根本不要重载它。这项限制并不麻烦,因为你始终可以给方法起不同的名称,而不使用重载机制。例如ObjectOutputStream类,对于每个基本类型,以及几种引用类型,它的write方法的命名都不一样,如writeInt,writeBoolean,writeLong等。这是一种好的实践。

对于构造器,你没有选择使用不同名称的机会;一个类的多个构造器总是重载的。在许多情况下,可以选择导出静态工厂,而不是构造器。对于构造,还不用担心重载和覆盖的相互影响,因为构造器不可能被覆盖。或许你有可能导出多个具有相同参数数目的构造器,所以有必要了解一下如何安全的做到这一点。

如果对于每一对重载方法,至少有一个对应的参数在两个重载方法中具有“根本不同”的类型,那么,导出多个具有相同参数数目的重载方法就不可能使程序员感到混淆。如果显然不可能把一种类型的实例转换为另一种类型,这两种类型就是根本不同的。在这种情况下,一组给定的实际参数应用于哪个重载方法上就完全由参数的运行时类型来决定,不可能受到其编译时类型的影响,所以主要的混淆根源就消除了。

Java 1.5发行后,出现了自动装箱和泛型,谨慎重载就更加重要了

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][-2,0,2]

原因是List有remove(int)和remove(Object)方法,Set只有一个remove(Object)方法,list.remove(i)调用的是remove(int)方法,所以导致这样的结果。修改成list.remove((Integer) i);就能使结果一致了。

简而言之,“能够重载方法”并不意味着就“应该重载方法”。一般情况下,对于多个具有相同参数数目的方法来说,应该尽量避免重载方法。在某些情况下,特别是涉及构造器的时候,要遵循这条建议也许是不可能的。在这种情况下,至少应该避免这样的情形:同一组参数只需经过类型转换就可以被传递给不同的重载方法。如果不能避免这种情形,例如,因为正在改造一个现有的类以实现新的接口,就应该保证:当传递同样的参数时,所有重载方法的行为必须一致。如果不能做到这一点,程序员就很难有效地使用被重载的方法或者构造器,他们就不能理解他为什么不能正常的工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值