Effective Item 17 - 关于方法的参数声明

给方法的参数加上限制是很常见的,比如参数代表索引时不能为负数、对于某个关键对象引用不能为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.Collections的sort()方法,列表中的元素当然是都可比较的,而方法并没有在开头检查有效性,而是计算中遇到问题时抛出异常。

与这种方式相比,提前检查有效性的意义确实不大。

但在计算途中检查参数的有效性需要考虑一点,即方法的原子性。

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

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<E>同时提供了remove(int)和remove(E)吗?

但作为API的使用者,我们能做的仅仅是对Java 5的这一大特性持更谨慎的态度。

Effective Item 17 - 关于方法的参数声明,布布扣,bubuko.com

时间: 2024-10-24 02:17:33

Effective Item 17 - 关于方法的参数声明的相关文章

Effective Java - 方法的参数声明

给方法的参数加上限制是很常见的,比如参数代表索引时不能为负数.对于某个关键对象引用不能为null,否则会进行一些处理,比如抛出相应的异常信息. 对于这些参数限制,方法的提供者必须在文档中注明,并且在方法开头时检查参数,并在失败时提供明确的信息,即: detect errors as soon as possible after they occur 这将成为准确定位错误的一大保障. 如果没有做到这一点,最好的情况是方法在处理过程中失败并抛出了莫名其妙的异常,错误的源头变得难以定位,但这是最好的情

Effective Item 5 - 覆盖equals方法时需要注意

多数情况下,我们不会去覆盖equals方法. 什么时候不需要覆盖equals? ·类的每个实例本质上是唯一的,我们不需要用特殊的逻辑值来表述,Object提供的equals方法正好是正确的. ·超类已经覆盖了equals,且从超类继承过来的行为对于子类也是合适的. ·当确定该类的equals方法不会被调用时,比如类是私有的. 如果要问什么时候需要覆盖equals? 答案正好和之前的问题相反. 即,类需要一个自己特有的逻辑相等概念,而且超类提供的equals不满足自己的行为. (PS:对于枚举而言

Effective Item 6 - 覆盖equals方法时不要忘记hashCode方法

任何覆盖了equals方法的类都需要覆盖hashCode方法. 忽视这一条将导致类无法与基于散列的数据结构一起正常工作,比如和HashMap.HashSet和Hashtable. 下面是hashCode相关规范: ·在程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这个对象调用多少次hashCode,起结果必须始终如一地返回同一个证书. 如果是同一个程序执行多次,每次调用的结果可以不一致. ·如果两个对象根据equals方法比较是相等的,那么两个对象的hashCo

Item 17:在单独的语句中将new的对象放入智能指针 Effective C++笔记

Item 17: Store newed objects in smart pointers in standalone statements. 在单独的语句中将new的对象放入智能指针,这是为了由于其他表达式抛出异常而导致的资源泄漏. 因为C++不同于其他语言,函数参数的计算顺序很大程度上决定于编译器. 如果你在做Windows程序设计,或者DLL开发,可能会经常碰到类似__cdecl,__stdcall等关键字.它们便是来指定参数入栈顺序的. 关于函数和参数的讨论可以参考:C++手稿:函数与

Effective Item 2 - 遇到多个构造器参数时考虑使用Builder

静态工厂和够构造器有一个共同的局限性:遇到大量的参数时无法很好的扩展. 先说说构造器. 其实field不多时重叠构造器(telescoping constructor)是个不错的方法,易于编写也易于调用,这种方式在参数数量较少时也很常见. 但问题是参数很多(可能越来越多)时,比如(现在已经很难找到对多个参数进行重叠构造的代码了,于是在这里直接引用一下书中的代码): public class NutritionFacts { private final int servingSize; // (m

Effective Item 3 - 避免不必要的对象

通常,我们更喜欢重用一个对象而不是重新创建一个. 如果对象是不可变的,它就始终可以被重用. 下面是一个反面例子,Joshua Bloch明确指出[DON'T TO THIS]: String s = new String("stringette"); 该语句每次执行时都创建一个新的实例. String构造器中的参数"stringette"本身是一个实例,功能方面等同于那些通过构造器创建的对象. 如果这种语句放到循环里,效果会变得更糟. 于是我们只需要: String

Effective Item 13 - 慎用tagged class

其实作者的原标题是<Prefer class hierarchies to tagged classes>,即用类层次优于tagged class. 我不知道有没有tagged class这么一说,其实作者指的tagged class的是一个类描述了多种抽象,可以根据某个field决定不同的实例. 下面是书中例子,使用shape和部分表示长度的field构成形状并计算面积,脑补一下什么是tagged class: class Figure { enum Shape { RECTANGLE, C

Effective Item 15 - 枚举

于Java 1.5增加的enum type. enum type是由一组固定的常量组成的类型,比如四个季节.扑克花色. 在出现enum type之前,通常用一组int常量表示枚举类型. 比如这样: public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public static final int APPLE_GRANNY_SMITH = 2; public static final i

四、C#方法和参数

方法是一种组合一系列语句以执行一个特定操作或计算一个特殊结果的方式. 它能够为构成程序的语句提供更好的结构和组织. 在面向对象的语言中,方法总是和类关联在一起的,我们用类将相关的方法分为一组. 方法通过参数来用于从调用者向目标方法传递数据的变量. 方法也通过一个返回值将数据返回给调用者 一个方法调用由以下元素构成:命名空间.类型名称.方法名称.参数以及返回数据类型. 1.命名空间 命名空间是一种特殊的分类机制,它将与一个特定功能有关的所有类型都分组到一起. 编译器认为所有命名空间都在同一个级别上