美文网首页Effective JavaAndroid开发经验谈Android开发
Effective Java——对于所有对象都通用的方法

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

作者: 码农一颗颗 | 来源:发表于2018-01-20 17:42 被阅读55次
    Android.jpg

    本系列文章是总结Effective Java文章中我认为最重点的内容,给很多没时间看书的朋友以最短的时间看到这本书的精华。
    第一篇《Effective Java——创建和销毁对象》

    第三章对于所有对象都通用的方法

    目录.png

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

    符合以下条件则不需要覆盖equals情况

    类的每个实例本质上都是唯一的

    对于代表活动实体而不是值(value)的类。例如:Thread,他只关注该类能完成的任务或者功能,而不是像Integer关心他中存在的值(value),对于Thread这样的类Object提供的默认equals方法完全能够满足要求。

    不关心类是否提供了“逻辑相等”的测试功能

    例如:Random类,如果对他进行覆盖equals方法将毫无意义,使用者根本不关心多个Random实例产生的随机数是否相等。
    逻辑相等:实例内存在的值是否相等,例如:Integer的两个实例用equals来判断是否逻辑相等。

    超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的

    集合类:Set实现都从AbstractSet继承equals实现,List实现从AbstractList继承equals实现,Map实现从AbstractMap继承equals实现。

    类是私有的或是包级私有的,可以确定他的equals方法永远不会被调用

    私有的或是包级私有的类不可能对外部提供。

    需要覆盖equals方法

    对于“值类(value class)”来说,它们需要判断“逻辑相等”,且超类还没有覆盖equals实现期望的行为,这时我们需要覆盖equals方法。
    值类:例如,IntegerDate,仅仅表示一个值,我们在调用equals时希望知道它们逻辑上是否相等,而不是想了解他们是否指向同一个对象。

    覆盖equals遵守的约定

    自反性

    对于任何非null的引用值x,x.equals(x)必须返回true

    对称性

    对于任何非null的引用值x和y,如果x.equals(y)返回true,则y.equals(x)也必须返回true

    传递性

    对于任何非null的引用值x、y和z,如果x.equals(y)返回truey.equals(z)返回truex.equals(z)也必须返回true

    一致性

    对于任何非null的引用值x和y,只要equals方法内部比较所用到的字段内容没有被修改,那么多次调用x.equals(y)就会一致地返回true,或者false。

    非空性

    所有比较的对象都不为null,如下代码:

     @Override
    public boolean equals(Object o){
        if(null == o){
            return false;
        }
    ......
    }
    

    其实如上代码根本不必要我们只需要用instanceof来判断参数类型,如果参数时null那么instanceof一定会返回false,如下代码:

    @Override
    public boolean equals(Object o){
        if(!(o instanceof MyClass)){
            return false;
        }
    ......
    }
    

    编写equals规则

    1. 使用==操作符检查“参数是否为这个对象的引用”
    2. 使用instanceof操作符检查“参数是否为正确的类型”
    3. 把参数转换成正确的类型
    4. 对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配

    在这里要多说几点:
    对于float字段,要用Float.compare方法来比较,对于double字段,则使用Double.compare方法,由于存在着Float.NaN、-0.0f以及类似的double常量。
    为了获得equals方法的最佳性能,应该最先比较最有可能不一致的字段,或者开销最低的字段。

    5. 当编写完成equals方法之后,检查他是否满足,对称,传递,一致性

    如下代码:

    public class TestClass{
        private String field;
        @Override
        public boolean equals(Object obj) {
            //第一步
            if(this == obj){
                return true;
            }
            //第二步
            if(!(obj instanceof TestClass)){
                return false;
            }
            //第三步
            TestClass testClass = (TestClass) obj;
            //第四步
             return null == this.field ? null == testClass.field : this.field.equals(testClass.field);
        }
    }
    

    编写equals的忠告

    1. 覆盖equals时一定要覆盖hashCode方法
    2. 要尽量让equals方法简单一些
    3. 不要将equals方法声明中的参数转换为其他类型

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

    hashCode约定

    1. 在同一个实例对象中,只要equals方法用到的信息没有被修改,那么这个对象多次调用hashCode方法应该返回结果相同。
    2. 如果两个对象根据equals方法比较是相等的,那么这两个对象hashCode方法产生的结果也必须相等。
    3. 如果两个对象根据equals方法比较是不相等的,那么这两个对象hashCode方法产生的结果有可能相等,也有可能不相等。如果能实现不相等,可以提高散列表的性能。
    public static final 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(this == obj){
                    return true;
                }
                if(!(obj instanceof PhoneNumber)){
                    return false;
                }
                PhoneNumber phoneNumber = (PhoneNumber) obj;
                return this.areaCode == phoneNumber.areaCode
                        && this.prefix == phoneNumber.prefix
                        && this.lineNumber == phoneNumber.lineNumber;
            }
        }
    

    如上代码只覆盖了equals方法没有覆盖hashCode,当我们把PhoneNumber应用到Map集合上如下代码:

    Map<PhoneNumber, String > map = new HashMap<>();
    map.put(new PhoneNumber(010,1111,2222),"Jenny");
            
    String value = map.get((new PhoneNumber(010,1111,2222));
    

    我们期望value=Jenny,实际上返回的null
    这是由于有两个PhoneNumber实例,第一个实例用于put插入到HashMap中,第二个实例用于getHashMap中获取,由于没有覆盖hashCode方法所以两个实例的hashCode是不相等的。因此第一个实例根据自己的散列码来保存数据,第二个实力根据自己的散列码来获取数据,由于两个散列码不相等所以get方法返回为null
    对于HashMap如何根据key来保存value查看这篇文章,说的非常想详细.
    解决这个问题就是在PhoneNumber类中根据上面的约定来覆盖hashCode方法,
    举例如下代码:

    @Override
    public int hashCode() {
          return lineNumber;
    }
    

    这段代码只是举个例子,具体的哈希值的计算非常复杂这里就不做讲解了。哈希值计算的好坏直接影响HashMap的效率,有兴趣可以在网上查查。

    第10条:始终要覆盖toString

    1. 对于任何类都推荐覆盖toString方法,尤其是“值类”。
    2. toString返回的字符串描述了这个类的所有有用的信息,如下代码:
     @Override
    public String toString() {
        return "PhoneNumber{" +
                   "areaCode=" + areaCode +
                   ", prefix=" + prefix +
                   ", lineNumber=" + lineNumber +
                    '}';
    }
    
    1. toString的返回值格式进行约束。例如返回字符串在文档中约束为Json格式,那么无论何种情况都不能修改它的返回格式。如果修改了,那么之前代码中对这个返回值进行解析的代码都会报错。
    2. toString的返回值格式不进行约束。在文档中不限定他的返回格式,返回格式是可变的,有可能是Json有可能是Xml形式。如果代码中用到解析toString返回格式的代码其正确性就应该由使用者来保证了。
    3. 对于toString中用到的有用信息,类中都要有相关的方法来返回值。避免使用者来解析格式不固定的toString方法的返回值.
      如下代码:
     @Override
    public String toString() {
        return "PhoneNumber{" +
                   "areaCode=" + areaCode +
                   ", prefix=" + prefix +
                   ", lineNumber=" + lineNumber +
                    '}';
    }
    //对于上面toString方法用到的信息都需要提供getX方法来获取他们的值
    public int getAreaCode() {
        return areaCode;
    }
    public int getPrefix() {
        return prefix;
    }
    public int getLineNumber() {
        return lineNumber;
    }
    

    第9条:谨慎的覆盖clone

    设计模式的原型模式可以由Objectclone方法来实现。

    实现clone
    1. 当前对象实现Cloneable接口。
    2. 当前对象在public Object clone()方法中调用super.clone得到当前类的一个对象。前提是父类实现的clone方法没有问题,也就是所有字段(可变字段)都已经clone好了(深拷贝)。
    3. 把克隆对象浅拷贝的字段指向该对象clone得到的地址空间。
      如下代码:
    public class E implements Cloneable {
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class C implements Cloneable {
        @Override
        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    public class D implements Cloneable {
        public E e = new E();
        @Override
        public Object clone() throws CloneNotSupportedException {
            D d = (D) super.clone();
            d.e = (E) this.e.clone();
            return d;
        }
    }
    
    public class B implements Cloneable{
        public D d = new D();
        @Override
        public Object clone() throws CloneNotSupportedException {
            B b = (B) super.clone();
            b.d = (D) this.d.clone();
            return b;
        }
    }
    
    public class A extends B implements Cloneable {
        private static String TAG = "aaaaa";
        private int count = 100;
        public C c = new C();
        @Override
        public Object clone() throws CloneNotSupportedException {
            A a = (A) super.clone();
            a.c = (C) this.c.clone();
            return a;
        }
        public static void main(String[] args) throws CloneNotSupportedException{
            System.out.println("main");
            A a = new A();
            A a1 = (A) a.clone();
            System.out.println("a  : " + a.toString());
            System.out.println("a1 : " + a1.toString());
            System.out.println("a.c  : " + a.c);
            System.out.println("a1.c : " + a1.c);
        }
    }
    //打印信息
    a  : com.example.test.A@723279cf
    a1 : com.example.test.A@10f87f48
    a.c  : com.example.test.C@b4c966a
    a1.c : com.example.test.C@2f4d3709
    
    深拷贝

    以上代码实现了深拷贝,每一个可变字段都调用clone方法产生一个最新的对象,每个类都实现了Cloneable接口并且覆盖了public Object clone()方法。

    浅拷贝

    将上述代码稍微修改就可以变成浅拷贝

    public class A extends B implements Cloneable {
        private static String TAG = "aaaaa";
        private int count = 100;
        public C c = new C();
        @Override
        public Object clone() throws CloneNotSupportedException {
            A a = (A) super.clone();
     //如果类中的可变字段没有调用如下代码,那么这个类就是浅拷贝
    //        a.c = (C) this.c.clone();
            return a;
        }
        public static void main(String[] args) throws CloneNotSupportedException{
            System.out.println("main");
            A a = new A();
            A a1 = (A) a.clone();
            System.out.println("a  : " + a.toString());
            System.out.println("a1 : " + a1.toString());
            System.out.println("a.c  : " + a.c);
            System.out.println("a1.c : " + a1.c);
        }
    }
    //打印信息如下:
    a  : com.example.test.A@6e0be858
    a1 : com.example.test.A@61bbe9ba
    a.c  : com.example.test.C@610455d6
    a1.c : com.example.test.C@610455d6
    

    以上是浅拷贝,在A类的public Object clone()方法中没有对可变对象C进行拷贝,导致打印的信息中C对象的地址是相同的。

    代替clone的方案

    写代码很少覆盖Cloneable接口,说两种代替方案

    1. 拷贝构造器
      例如HashMap类的构造方法public HashMap(Map<? extends K, ? extends V> map);就是一个拷贝构造器
    2. 拷贝工厂方法
      例如public static Yum newInstance(Yum yum);

    最好不要覆盖Cloneable接口,使用clone方法。

    第12条:考虑实现Comparable接口

    实现这个接口主要是为了对象之间的排序,Java平台类库中的所有“值类”都实现了这个接口。
    规则:
    将这个对象与指定对象进行比较。当该对象小于、等于、大于指定对象的时候,分别返回负数、零、正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。
    如下代码:

    public class PhoneNumber implements Comparable<PhoneNumber> {
        public final int area;
        public final int phone;
        public PhoneNumber(int area, int phone) {
           this.area = area;
           this.phone = phone;
        }
        @Override
        public int compareTo(@NonNull PhoneNumber pn) {
            if (area < pn.area) {
                return -1;
            }
            if (area > pn.area) {
                return 1;
            }
            if (phone < pn.phone) {
                return -1;
            }
            if (phone > pn.phone) {
                return 1;
            }
            return 0;
        }
        @Override
        public String toString() {
            return "PhoneNumber{" +
                    "area=" + area +
                    ", phone=" + phone +
                    '}';
        }
        public static void main(String[] args) throws CloneNotSupportedException {
            PhoneNumber[] phoneNumbers = {
                    new PhoneNumber(100,300),
                    new PhoneNumber(200,500),
                    new PhoneNumber(50,10),
    
            };
            List<PhoneNumber> phoneNumberList = Arrays.asList(phoneNumbers);
            System.out.println(phoneNumberList);
            Collections.sort(phoneNumberList);
            System.out.println(phoneNumberList);
        }
    }
    打印日志
    [PhoneNumber{area=100, phone=300}, PhoneNumber{area=200, phone=500}, PhoneNumber{area=50, phone=10}]
    [PhoneNumber{area=50, phone=10}, PhoneNumber{area=100, phone=300}, PhoneNumber{area=200, phone=500}]
    

    相关文章

      网友评论

        本文标题:Effective Java——对于所有对象都通用的方法

        本文链接:https://www.haomeiwen.com/subject/chtunxtx.html