第7章 方法

38、检查参数的有效性

       非公有方法应该使用断言(assertion)来检查它们的参数。如在方法里assert a!=null;

       但并不是说对参数的任何限制都是好事。有效性检查工作非常昂贵,而且有的有效性检查已经隐含在计算过程中。假如方法对于它所能接受的所有参数值都能够完成合理的工作,那么对参数的限制应该是越少越好。每当编写方法和构造器的时候,应该考虑它的参数有哪些限制。应该把这些限制写到文档中,并且在这个方法体的开头处,通过显示的检查来实施这些限制。(感觉自己实施起来不一定简单···)

39、必要时进行保护性拷贝

       第一种攻击:如一个类内部有Date成员变量,在构造函数执行时,将外部Date参数传进来,但我们可通过改变该Date参数引用来改变Date值。这样就可以绕过构造时对Date的检查。为了保护实例内部信息避免受到这种攻击,对于构造器的每个可变参数进行保护性拷贝必不可少。如构造函数中:this.start = new Date(outStart.getTime());这样,就算外部调用参数outStart.setYear()等函数,也无法改变实例的start了。(保护性拷贝是在检查参数的有效性之前进行的(防止在检查参数后到拷贝之前改变外部参数),并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象)。

       这边没有用Dateclone方法进行保护性拷贝。因为Date是非final的,不能保证clone方法一定返回类为java.util.Date的对象,有可能返回处于恶意目的而设计的不可信子类的实例。

       第二种攻击:对于get方法获得Date的成员变量,我们还是可以通过该成员返回值的方法改变成员的值,因此需要使它返回可变内部域的保护性拷贝:即get方法里用return new Date(start.getTime());,这样就不怕返回值被外部改变影响内部了。

       对于是否要进行保护性拷贝需要考虑,如果类信任它的调用者不会修改内部组件,则可以不用并在类文档中清楚说明。

40、谨慎设计方法签名

       (1)谨慎地选择方法的名称;(2)不要过于追求提供便利的方法(说的也不是很清楚···)。(3)避免过长的参数列表(目标是4个参数,或者更少)。

       (3)共有三种方法:(1)把方法分解成多个方法;每个方法只需要这些参数的一个子集;(2)创建辅助类,用来保存参数的分组。(3)从对象到方法调用都采用Builder模式。

       对于参数类型,要优先使用接口而不是类(常识)。

       对于boolean参数,要优先使用两个元素的枚举类型。它使代码易于阅读和编写。

41、慎用重载

public static Stringclassify(Set<?> s){

              return "Set";

       }

       public static Stringclassify(List<?> s){

              return "List";

       }

       public static Stringclassify(Collection<?> s){

              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,而是3个UnknownCollection。这是因为classify方法被重载的,而要调用哪个重载方法是在编译时做出的决定

       对于重载(overloaded)方法的选择是静态的,而对于被覆盖(overridden)的方法的选择则是动态的。如用父类引用引用子类,父类引用调用方法,最后都是调用的相应子类的方法(多态)。当调用被覆盖的方法时,对象的编译时类型不会影响到哪个方法将被执行;“最为具体的”那个覆盖版本总是会得到执行。(覆盖机制是规范,重载机制是例外)

       安全保守的策略是:永远不要导出两个具有相同参数数目的重载方法。如果方法使用可变参数,保守策略是根本不要重载它

       自动装箱也会导致莫名bug,如:

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,输出[-2,0,2]是因为list.remove(i)调用重载方法remove(int i),它从列表的指定位置去除元素。,换成list.remove((Integer)i);就对了。

       结论:至少应该避免:同一组参数只需要经过类型转换就可以被传递给不同的重载方法。如果无法避免,应该保证:当传递同样的参数时,所有重载方法的行为必须一致

42、慎用可变参数

       可变参数方法接受0或者多个指定类型的参数。写法如,

static int sum(int...args){//…与args之间空格可有可无

              int sum = 0;

              for(int arg : args)

                     sum += arg;

              return sum;

       }

当要编写需要一个或者多个某种类型参数的方法,则static int sum(int firstArg, int…remainingArgs)

       List<String> homophones =Arrays.asList(“to”,”too”,”two”);(参数是可变参数Integer/String等... a)这样是可以的

int[] digits = {1,2,3,4};

System.out.println(Arrays.asList("to","too","two"));// [to, too, two]

System.out.println(Arrays.asList(digits));// [[I@12b6651]

System.out.println(Arrays.asList(1,2,3,4));// [1, 2, 3, 4]

       将数组转变成字符串的Arrays.asList做法是过时的(数组从Object上继承toString,因此直接在数组上调用toString,输出内存地址这些无意义字符串)。Arrays类得到了补充完整的Arrays.toString方法(不是可变参数方法,参数是特定类型的数组),专门为了将任何类型的数组转变成字符串。

       可变参数方法的每次调用都会导致进行一次数组分配和初始化。如果无法承受,可以重载多个方法,如一个参数、两个参数···,一直到i个参数,后面再用可变参数。

43、返回零长度的数组或者集合,而不是null

       如果数组的size()为0,返回null,将数组没有元素当成特例是不合理的。这样会要求客户端必须有额外的代码来处理null返回值。忘了写专门处理null返回值就会出错。

       返回类型为数组或集合的方法没理由返回null,而不是返回一个零长度的数组或者集合。如:

       private static final String[] EMPTY_STRING = new String[0];

       public String[] getString(){

              return st.toArray(EMPTY_STRING);

       }

Collection.toArray(T[])的规范保证:如果输入数组大到足够容纳这个集合,它就将返回这个输入数组。有意思的函数,toArray只有当T[]够大才返回T,否则返回本身。其他方法如:returnCollection.emptyList();

       在C语言中,数组长度是与实际的数组分开返回的(关键还是这个是阻碍,不然也可以返回0长度数组或集合)。因此如果返回的数组长度为0,再分配一个数组就没有任何好处···。

       string——length();数组——length;集合——size();

44、为所有导出的API元素编写文档注释

       为了正确编写API文档,必须在每个被导出的类、接口、构造器、方法和域声明之前增加一个文档注释。

       文档注释应该列举这个类所有前提条件和后置条件。

       @throws标签之后的文字应该包含单词“if”,描述这个异常将在什么样的条件下会被抛出。

       {@code 语句}用于使该代码片段在生成文档中以代码字体显示,并限制HTML标记和嵌套的javadoc标签在代码片段中进行处理(即对于<这些就可以以小于的形式展现,而不是HTML的符号)。({@literal}类似,只是没有展现成代码显示的功能,其他都一样)

       this被用在实例方法的文档注释中时,它应该始终是指方法调用所在的对象。

       为泛型或者方法编写文档时,确保要在文档中说明所有的类型参数。

       为枚举类型编写文档时,要确保在文档中说明常量,以及类型,还有任何公有方法。

       为注解类型编写文档时,要确保在文档中说明所有成员,以及类型本身。

       应该在文档中对类是否线程安全进行说明,如果类是可序列化的,就应该在文档中说明它的序列化形式。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值