zoukankan      html  css  js  c++  java
  • Java

    给方法的参数加上限制是很常见的,比如参数代表索引时不能为负数、对于某个关键对象引用不能为null,否则会进行一些处理,比如抛出相应的异常信息。

    对于这些参数限制,方法的提供者必须在文档中注明,并且在方法开头时检查参数,并在失败时提供明确的信息,即:

    detect errors as soon as possible after they occur

    这将成为准确定位错误的一大保障。

    如果没有做到这一点,最好的情况是方法在处理过程中失败并抛出了莫名其妙的异常,错误的源头变得难以定位,但这是最好的情况。
    更差的情况是方法执行通过,没有发生任何错误,只是得出的结果和方法描述完全不符,最后在某个关键的部分看到奇怪的数据时才亡羊补牢。


    对于参数违反有效性时使用的异常类,我们通常抛出IllegalArgumentException,IndexOutOfBoundsException,NullPointerException
    而对于违反约束时抛出的异常类型,需要用Javadoc的@throws标签对其进行说明。
    另外,并不是所有参数检查都需要做到这种地步。
    如果方法或构造器不对外导出,则可以简单使用assert来保证参数的有效性。
    比如java.util.Collections$CopiesList:

    private static class CopiesList<E>
            extends AbstractList<E>
            implements RandomAccess, Serializable{
    
            //...
    
            CopiesList(int n, E e) {
                assert n >= 0;
                this.n = n;
                element = e;
            }
    
            //...
    }
    


    但是,有效性检查并不都是简单的,这一操作的代价也可能非常大甚至不切实际,于是有些操作直接将参数检查隐含在计算过程中。
    比如java.util.Collectionssort()方法,列表中的元素当然是都可比较的,而方法并没有在开头检查有效性,而是计算中遇到问题时抛出异常。
    与这种方式相比,提前检查有效性的意义确实不大。
    但在计算途中检查参数的有效性需要考虑一点,即方法的原子性。


    说道参数就不得不说方法重载,首先上一段例子:

    import java.math.BigInteger;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    public class CollectionClassifier {
        public static String classify(Set<?> s) {
            return "Set";
        }
    
        public static String classify(List<?> lst) {
            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));
        }
    }
    


    很常见的笔试题,会输出三次"Unknown Collection",编译时已决定了将调用的方法。
    与重载方法的静态选择(为什么当初会这样设计重载? overriding is the norm and overloading is the exception,似乎没有人希望这种做法 )相对的是覆盖方法,overridden method的选择是动态的。


    即,选择方法是在运行时进行的,子类用同样的方法签名覆盖了上级类的方法,如果这个方法是实例方法则会在子类的实例上被调用。
    比如下面这个例子,实例的编译时类型对其没有造成影响:

    class Wine {
        String name() {
            return "wine";
        }
    }
    
    class SparklingWine extends Wine {
        @Override
        String name() {
            return "sparkling wine";
        }
    }
    
    class Champagne extends SparklingWine {
        @Override
        String name() {
            return "champagne";
        }
    }
    
    public class Overriding {
        public static void main(String[] args) {
            Wine[] wines = { new Wine(), new SparklingWine(), new Champagne() };
            for (Wine wine : wines)
                System.out.println(wine.name());
        }
    }
    


    鉴于重载方法的这种特征,而语言本身对其也没有特别的限制,作者建议<不要提供相同参数数量的重载方法>,而对于可变参数则不要考虑重载。
    关于这个建议的不错的例子就是ObjectOutputStream,对于其write方法,设计者并没有提供相同参数数量的重载,而是提供了诸如writeInt,writeBoolean,writeLong等方法,而且read方法也是与write对称的。
    这种方式不适用于构造器,我们无法对构造器进行命名,但我们可以对构造器进行私有化并导出静态工厂。
    但也并不能说必须严格遵守这种规则,比如为fetchSalaryInfo()提供了两种方法重载,分别是int uid和User userInfo,很难想象会出现调用错误的情况。


    破坏<不要提供相同参数数量的重载方法>这一规则不仅仅是出现在导出某个方法的时候,更新现有类的时候也有可能。
    比如String类有一个since 1.4的contentEquals(StringBuffer),另外还有一个since 1.5的contentEquals(CharSequence)。
    (Java 1.5版本中增加了CharSequence接口,并作为StringBuffer,StringBuilder,CharBuffer,String等类的公共接口) 但这并不会带来危害,因为这个例子的两个重载方法的行为是完全一样的。


    对于泛型还有下面这种有趣的情况:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    import java.util.TreeSet;
    
    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);
        }
    }
    


    槽点:remove里的参数是index还是element?
    执行结果是[-3,-2,-1][-2,0,2],也就是说 list.remove中的参数是index,而不是被自动装箱。
    应该说问题根本原因是List同时提供了remove(int)remove(E)吗?
    但作为API的使用者,我们能做的仅仅是对Java 5的这一大特性持更谨慎的态度。

  • 相关阅读:
    js里面的 InttoStr 和 StrtoInt
    预编译知识 (转载记录)
    C语言操作内存
    C语言操作文件
    C语言
    如何调试shell脚本
    设计模式-装饰者模式
    自己动手制作一个模版解析
    设计模式-单例模式
    http中关于缓存的那些header信息
  • 原文地址:https://www.cnblogs.com/kavlez/p/4278007.html
Copyright © 2011-2022 走看看