zoukankan      html  css  js  c++  java
  • Effective Java部分读书笔记

    2.创建和销毁对象

    1.使用静态工厂方法代替构造器

    一般使用构造器(构造函数)创建对象实例,还可以使用静态工厂方法来创建对象实例。

    优点

    使用静态工厂方法代替构造器创建对象实例有以下优点:

    1)静态构造方法的名称可以是更加有意义的,具有更好的可读性,而构造器的名称必须与类名称保持一致。

    2)静态工厂方法不必在每次调用时都创建一个新对象,这对于那些需要重复创建相同对象的场景下尤其有用。

    3)静态工厂方法可以返回原返回类型的任意子类型对象

    4)静态工厂方法在创建参数化实例的时候,使程序更加简洁。

    缺点

    1)使用静态工厂方法创建实例的类,一般要求构造器是私有的,也就是说这个类不能被继承,不能被子类实例化。

    2)静态工厂方法与其他静态方法并没有实质的区别,不能显而易见的判断某静态方法是不是工厂方法,所以,人们就做了一个约定,静态工厂方法有一些惯用名称:例如valueOf、of、getInstance、newInstance、getType、newType。

    参考:1  2

    2.遇到多个构造器参数时要考虑用构建器

    如果类的构造器(或者静态工厂方法)有多个参数,并且这些参数中有些是可选参数时,就要考虑使用Builder模式。

    3.通过私有构造器强化不可实例化的能力

    有些工具类不希望被实例化,因为实例化对它来说没有什么意思,例如java.lang.Math,java.util.Arrays。有人通过将类定义为抽象类来避免类的实例化,不过,这样容易引起误解,让用户误以为这个类是专门为了继承而设计的。我们可以通过一个简单的方法来避免类被实例化,就是显式地将类的构造器声明为私有的(private),这样就保证避免类的实例化(前提是类中的其他成员不调用这个私有的构造器)。

    4.避免创建不必要的对象

    也就是说最好是能够重用已有的对象,而不是在每次需要的时候就创建一个相同功能的对象。

    1)如果对象是不可变的(immutable),那它始终可以被重用。基本类型的包装类、String、BigInteger和BigDecimal都是不可变类。以字符串为例:

    String str=new String("Java");

    这条语句执行时,每次都会创建一个新的String实例,不过这是不必要的,因为每次创建的实例是相同的,都是字符串"Java",而“Java”本身就是字符串实例。如果这种方法用在循环或者被频繁调用的方法中,就会创建成千上万个相同功能的实例。改进后的版本如下:

    String str="Java"

    这种方法并没有创建新实例,而是使用已创建好的“Java”实例。并且,如果在代码的其他位置,使用了相同的字符串字面常量,仍然不会创建新对象,而是仍然复用这个“Java”实例,对于其他的不可变类也是如此。

    2)除了重用不可变类之外,还可以重用那些创建了之后就不会修改的可变对象。参考

    6.清除过期的对象引用(内存泄漏)

    一个对象,如果在程序之后的执行过程中不会再使用,正常情况下,其所在的内存区域应该被回收,以便于存储新的对象。不过,由于一些原因未被回收,还是占据着内存,导致可用内存量下降,就像内存变少了一样,称这种现象为内存泄漏。Java虽然具有垃圾回收的功能,不过也会发生内存泄漏。

    1)如果是类自身管理内存,可能会发生内存泄漏。以数组实现的栈为例,栈独自管理数组所占的内存空间,代码实现如下:

     1 public class Stack {
     2     private Object[] elements;
     3     private int size = 0;
     4     private static final int DEFAULT_INITIAL_CAPACITY = 100;
     5     public Stack() {
     6         elements = new Object[DEFAULT_INITIAL_CAPACITY];
     7     }
     8     public void push(Object e){
     9         ensureCapacity();
    10         elements[size++] = e;
    11     }
    12     public Object pop(){
    13         if(size == 0){
    14             throw  new EmptyStackException();
    15         }
    16         return  elements[--size];
    17     }
    18     private void ensureCapacity(){
    19         if(elements.length == size){
    20             elements = Arrays.copyOf(elements, 2 * size + 1);
    21         }
    22     }
    View Code

    出栈时,pop()方法只是简单的返回原栈顶元素的前一个元素,没有做其他任何的处理。假如,栈先增长到80,然后再收缩至20,之后的增长和收缩只是在20以内,那么数组中[21,80]存储的对之前对象的引用一直保持,尽管这些对象在程序中不在使用,垃圾收集器不会收集这些区域,这就造成了内存泄漏。解决办法很简单,在pop()方法里加一句代码(第5行)即可:

    1 public Object pop(){
    2     if(size == 0){
    3         throw  new EmptyStackException();
    4     }
    5     elementsp[size] = null;
    6     return  elements[--size];
    7 }

    2)把对象引用放到缓存中,也经常会引起内存泄漏。

    3)监视器和其他回调也会引起内存泄漏

    解决方法

    可以通过仔细检查代码或者借助于Heap剖析工具(heap Profiler)发现内存泄漏问题。

    7.避免使用终结方法

    finalize()终结方法通常是不可预测的,应该避免使用。

    3.对于所有对象都通用的方法

    Object是Java中所有类的父类,它的equals、hashCode、toString、clone、finalize原则上应该被所有子类覆盖,这些方法都有明确的通用约定,任何子类覆盖这些方法的时候要遵循这些约定,否则就不能与其他依赖这些约定的类一起正常运作。

    8.覆盖equals时请遵守通用约定

    不要覆盖equlas的情况

    1)类的实例本质上都是唯一的,即每次创建的对象实例都是唯一的

    2)不关心类是否提供了判断“逻辑相等”的方法,也即是说类的使用过程中不会出现判断两个类实例是否相等的情况

    3)父类已经实现了equals,子类利用这个equals可以判断自身实例是否“逻辑相等”。

    覆盖equals方法时的约定

    1)自反性:对象等于其自身。对于任意非空的对象x,有x.equals(x)=true。

    2)对称性:对于任意非空x,y,如果x.equals(y)=true,那么y.equals(x)也等于true。

    3)传递性:对于任意非空x,y,z,如果x.equals(y)=true,y.equals(z)=true,那么也有x.equals(z)=true。

    4)一致性:如果两个对象相等,只要两个对象都未被修改,那么它们任意时刻都应该是相等的。

    5)非空性:所有的对象都不等于null。

    实现高质量equals

    1)使用“==”检查参数“是否为这个对象的引用”,这只是一种性能优化。

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

    3)把参数向下转化(Object—>T)为正确的类型。

    4)最重要的,检查参数中的各个域是否与对象中的各个区相匹配。如果域是除了double和float的基本类型,使用"=="进行比较。如果是对象引用域,递归调用equals。对于float类型,使用Float.compare方法,对于double,使用Double.compare,对float和Double进行特殊处理,是因为存在着Float.NaN、-0.0f以及类似的double常量。如果域是数组,将以上原则应用到每个元素上。

    最后,覆盖equlas()是总要同时覆盖hashCode。不要讲equals中的Object类型替换为其他类型,因为这样只是重载了,而非覆盖。可以使用@Override关键字显示指出覆盖,供编译器检查,防止误把覆盖写成重载。

    9.覆盖equals时总要覆盖hashCode

    hashCode约定

    1)在程序的执行期间,如果equals的比较操作所用到的信息没有被修改,也就是说对象还是那个对象,那么多次调用hashCode应该始终如一的返回同一个整数。不过,程序多次执行时,可以返回不同的整数。

    2)通过equals比较相等的两个对象,调用hashCode应该返回相同的整数(哈希码)。

    3)通过equals比较不相等的两个对象,调用hashCode不一定要返回不相等的整数。不过,为了减少哈希冲突,不相等的对象调用其实应该返回不同的整数。

    不覆盖hashCode会怎么样

    如果覆盖了equals的实现,未覆盖hashCode的话,会影响这个类与基于哈希的集合一起正常运作,例如HashMap,HashSet以及Hashtable。因为没有覆盖的话,会调用Object的hashCode实现,即使子类调用已重写的equals,得到两个相等的对象,不过通过Object的hashCode计算得出的哈希码却并不相同(本该相同),因此不能与基于哈希的集合一起运作。

    10.始终要覆盖toString

    Object提供的toString的输出包含类的名称,一个“@”已经标识对象的无符号十六进制散列码。例如,“Phone@163b91”。当对象被传递给println、printf字符串连接符"+"时,自动调用对象的toString()方法。应该重写toString,使其返回对本类有意义的信息。

    4.类和接口

    15.使可变性最小化

    实例对象不能被修改的类成为不可变类。不可变类的对象实例包含的所有信息必须在创建时提供,并在对象的生命周期内保持不变。

    如何实现不可变类

    1)将类的所有字段声明为private类型,将字段定义为final类型,使字段在第一次初始化之后就不能再被修改。将类声明为final类型,表示不能被继承,防止子类无意识修改对象状态。

    2)不提供任何修改对象字段的方法,也就是不提供任何改变对象状态的方法。

    优点

    1)不可变类易于设计、实现和使用,不容易出错且更加安全,不变的东西更容易控制。

    2)不可变对象是线程安全的,因为即使是多个线程并发访问这类对象,它们也不会遭到破坏,因为并未提供更改对象状态的方法。

    缺点

    对于每个不同的值,不可变类都要提供一个单独的对象。这对于大型的对象来说,低价很高。例如上百万位的BigInteger。 

    7.方法

    38.检查参数的有效性

    对于包含参数的方法,在使用之前应该检查其参数的有效性。如果参数是索引、长度等要保证是非负数,如果是对象的引用,要保证是非空的。但是,并不是任何时候都要检查参数的有效性,如果参数的有效性检查是昂贵的,甚至是不切实际的,并且在方法的计算过程中已经隐含了对参数的有效性检查,此时不必检查参数的有效性。如果参数无效,可以采取抛出异常的方式,如下:

    public static double getGravity(double mass) {  
        // 参数有效性检查  
        if (mass <= 0) {  
            throw new IllegalArgumentException("无效质量:" + mass);  
        }  
      
        return mass * 9.82;  
    }  

    41.慎用重载

    8.通用程序设计

    48.如果需要精确的答案,避免使用float和double。

    Java中的float和double在进行算术运算时,不保证得到完全精确的结果。例如

    System.out.println(1-0.9)

    的结果可能为0.100000001。

    可以使用BigDecimal解决需要精确答案的问题。

    52.通过接口引用对象

    如果可以的话,优先使用接口引用对象,而不是使用类。

    什么时候使用

    如果类是实现了某个接口,也就是说类是有接口的话,优先考虑使用接口作为类型。不过,要意识到使用接口类型的对象,并不能访问类中新增的方法(相对接口来说)。

    有什么好处

    使用接口作为类型可以增加程序的灵活性,很容易实现接口实现类间的替换。声明Vector的两种方法:

    //方法1
    Vector<Subscriber> subscribers = new Vector<Subscriber>();
    //方法2
    List<Subscriber> subscribers = new Vector<Subscriber>();

    如果需要更改接口的实现,将Vector改为ArrayList(Vector和ArrayList都是List接口的实现),如果程序中使用方法2的话,只需要将右侧的Vector改为ArrayList即可。

    为什么要改变实现

    新的实现可能会提供更好的性能。

     

  • 相关阅读:
    printcap
    browser-ua
    PHP 开发 APP 接口 学习笔记与总结
    Java实现 LeetCode 72 编辑距离
    Java实现 LeetCode 72 编辑距离
    Java实现 LeetCode 72 编辑距离
    Java实现 LeetCode 71 简化路径
    Java实现 LeetCode 71 简化路径
    Java实现 LeetCode 71 简化路径
    Java实现 LeetCode70 爬楼梯
  • 原文地址:https://www.cnblogs.com/lz3018/p/5817270.html
Copyright © 2011-2022 走看看