Effective Java——对所有对象通用的方法

第8条:覆盖equals时请遵守通用约定

如果不对equals进行覆盖,那么类的每个实例都只与它自身相等。如果类具有自己特有的”逻辑相等”的概念,并且超类还没有覆盖equals以实现期望的行为,这时就可以进行覆盖。

高质量equals方法的诀窍

1、使用==操作符检查参数是否为这个对象的引用

2、使用instanceof操作符检查参数是否为正确的类型

3、把参数转换成正确的类型

4、当编写完成了equals方法之后,应该问自己三个问题,它是否是对称的、传递的、一致的。

例如下面实现的equals:

class PhoneNumber {
    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    @Override
    public boolean equals(Object obj) {
        // 使用==操作符检查参数是否为这个对象的引用
        if (obj == this) {
            return true;
        }
        // 2、使用instanceof操作符检查参数是否为正确的类型
        if (!(obj instanceof PhoneNumber)) {
            return false;
        }
        // 3、把参数转换成正确的类型
        PhoneNumber pn = (PhoneNumber)obj;
        // 4、应该问自己三个问题,它是否是对称的、传递的、一致的。
        return pn.lineNumber == lineNumber
                && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }   
}

其他需要注意的:

1、覆盖equals时总要覆盖hashCode

2、不要企图让equals方法过于智能

3、不要将equals声明中的Object对象替换成其他的类型。

第9条:覆盖equals时总要覆盖hashCode

在每个覆盖了equals方法的类中,必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用约定,从而也导致该类无法结合所有基于散列的结合一块正常运转,这样的结合包括HashMap、HashSet和Hashtable。

对于上面PhoneNumber的例子,它没有实现hashCode如果我们调用下面代码:

Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber(408, 867, 5309),  "hello");
map.get(new PhoneNumber(408, 867, 5309));

可以发现上面取到的值为null,而不是”hello”。

原因就是没有重写hashCode方法,每次hashCode返回的都是不同的值。

下面来看看HashMap的源码:

@Override public V put(K key, V value) {

    // 只展示关键代码
    // 插入的位置索引值会对key取hansh
    int hash = Collections.secondaryHash(key);

}

public static int secondaryHash(Object key) {
    return secondaryHash(key.hashCode());
}

从上面可以看到传入的是key的hashCode,所以即使是相同的key,如果它的hashCode不同对应的index索引也不同。

如果我执行下面代码:

Map<String, String> map = new HashMap<String, String>();
map.put(new String("aaa"),  "hello");
map.get(new String("aaa"));

我们可以知道返回的就是hello,所以我们可以来看看String里面是怎么实现hashCode的。

public int hashCode() {
    int h = hash;//hash默认为0,用来缓存String里面的hashCode
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

它是根据字符串的内容来确定hashCode的,所以如果字符串的内容是相同的,那么hashCode也是相同的。所以我们可以类似来实现PhoneNumber的hashCode。

所以对应PhoneNumber中的hashCode,也可以有类似的实现:

public int hashCode() {
    int result = 17;
    result = 31 * result + areaCode;
    result = 31 * result + prefix;
    result = 31 * result + lineNumber;
    return result;
}

这样得到的hashCode值跟里面的属性内容是有关系的,如果属性内容都相同,hashCode也就相同了。

第10条:始终要覆盖toString方法

在实际应用中,toString方法应用返回对象中含有的值得关注的信息。另外可以为toString指定返回的格式。

比较好的toString实现方法:

    public String toString() {  
            StringBuffer sb = new StringBuffer();  
            Class c = this.getClass();  
            Field[] f = c.getDeclaredFields();  
            for (int i = 0; i < f.length; i++) {  
                f[i].setAccessible(true);  
                String fieldName = f[i].getName();  
                sb.append(fieldName);  
                sb.append("=");  
                try {  
                    sb.append(f[i].get(this));  
                } catch (IllegalArgumentException e) {  
                    e.printStackTrace();  
                } catch (IllegalAccessException e) {  
                    e.printStackTrace();  
                }  
                sb.append("\n");  
            }  
            return sb.toString();  
        }  

第11条:谨慎地覆盖clone方法

Cloneable接口的目的作为对象的mixin接口,表示该对象可以被克隆。但是它并没有提供clone方法,clone方法在Object中,但是这个方法是protected的,我们可以直接重写这个方法,但是也没办法直接进行调用,所以一般除了实现Cloneable之外,就是对Object中受保护的clone方法提供公有的访问途径。

既然Cloneable并没有包含任何方法,那么它到底有什么作用呢?它决定了Object中受保护的clone方法的行为,只有实现了Cloneable,Object的clone方法才能返回拷贝对象,否则会抛出异常。

拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。

如果希望拷贝一个对象,超类提供这种功能的唯一途径是,返回一个通过调用super.clone而得到拷贝对象。但是我们一般在使用的时候需要特别注意浅拷贝和深拷贝。

简而言之,所有实现了Cloneable接口的类都必须用一个公有的方法覆盖clone,此公有方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,就意味着要拷贝任何包含内部”深层结构”的可变对象,并用执行新对象的引用代替原来指向的这些对象的引用。

第12条:考虑实现Comparable接口

如果类实现了Comparable接口就表明它的实例具有内在的排序关系。

下面来看看Collections中的sort方法。它要求List中的元素对象继承自Comparable,这样它内部的元素对象就具有内部比较功能了。

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

default void sort(Comparator<? super E> c) {
    // 传进来的c为null
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ......
}

public static <T> void sort(T[] a, Comparator<? super T> c) {
    // 传进来的c为null
    if (c == null) {
        sort(a);
    }
    ......
}

public static void sort(Object[] a) {
    ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
}

static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
    if (nRemaining < MIN_MERGE) {
        int initRunLen = countRunAndMakeAscending(a, lo, hi);
        binarySort(a, lo, hi, lo + initRunLen);
        return;
    }
}

private static void binarySort(Object[] a, int lo, int hi, int start) {
    ......
    for ( ; start < hi; start++) {
        Comparable pivot = (Comparable) a[start];
       ......
        while (left < right) {
            int mid = (left + right) >>> 1;
            if (pivot.compareTo(a[mid]) < 0)
                right = mid;
            else
                left = mid + 1;
        }

    }
}

上面代码都不完整,只把重点代码列举处理了,可以看到对象的比较使用的就是Comparable中的compareTo来进行比较。所以如果希望自己的对象拥有排序的能力,可以考虑实现这个接口。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值