spring boot

Effective Java(第三版)-学习笔记

2019-04-22  本文已影响0人  Monica2333
Chapter01:引言

这本书的目的是帮助编写清晰正确,可用的,健壮性灵活性高和可维护的代码,而非针对高性能。主要从对象,类,接口,泛型,枚举,流,并发和序列化等方面介绍。

Chapter02:对象的创建和销毁
public class Stack  {
    private Object[] elements;
    
    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
    private int size;

    public Stack{
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }
    public Object pop(){
        if(size == 0){
            throw new EmptyStackException();
        }
        Object result = elements[--size];
        //释放引用
        elements[size] = null;
        return result;
    }
.......

}

因为Java是有垃圾回收机制的,通常不需要手动将对象置为null。但是如果对象内部管理自己的内存分配,则需要手动释放元素的引用,(如上面的例子,只有数组将元素置空了,元素对应的对象才能被回收)否则会导致内存泄漏。此外,缓存,监听器和其他回调函数也可能导致内存泄漏,可借助性能分析工具来分析这类问题。

Chapter03:Object类的方法

Object类是所有类的父类,它定义了一些非final的方法,如equals,hashCode,toString等,这些方法有它们自己的规定。此外,Comparable.compareTo方法虽然不是Object的方法,但是它也经常出现在任何同类对象中用来比较两个对象,以下也会讨论。

//AbstractList.equals
public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof List))
            return false;

        ListIterator<E> e1 = listIterator();
        ListIterator<?> e2 = ((List<?>) o).listIterator();
        while (e1.hasNext() && e2.hasNext()) {
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());
    }
public int hashCode() {
        int hashCode = 1;
        for (E e : this)
//使用31这个质数尽可能保证了运算值的唯一性
            hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
        return hashCode;
    }
x.clone() != x;
x.clone().getClass() == x.getClass();
//非必须
x.clone().equals(x);

使用clone方法需要注意:
1.不可变的类不应该提供clone方法。
2.clone方法的功能就像是构造方法,应该确保不会对原来的对象造成影响。
3.对于final成员变量,将其作为可变对象进行clone是不太合适的,可能需要去掉final修饰。
4.对于复杂对象的clone,应该先调用super.clone,然后在组装变量之间的关联关系,如HashTable中的Entry链表引用.
5.对象克隆的更好方法其实是提供copy 构造方法或者 copy工厂方法,如:

//copy constructor
    public static Yum(Yum yum){
        .....
    }
//copy factory
    public static Yum newInstance(Yum yum){
        .....
    }

因为他们不依赖危险的克隆机制,也没有文档克隆要求,和final fields的使用不冲突,同时不需要抛出检查异常和强转。
对于数组,是适合使用clone的,因为它的运行时类型为Object[],不需要进行强转。如ArrayList.copy

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
Chapter04:类和接口
Chapter05:泛型

Java5之后,泛型成为Java语言的一部分。没有泛型前,操作集合中的元素必须进行强转,而类型转换异常只能在运行期才能发现。泛型可以告诉编译器集合中每个元素是什么类型的,从而可以在编译期就发现了类型转换的错误。泛型使得程序更加安全,简洁明了。

        E[] elements = (E[])new Object[16];

推荐使用第二种方式,因为它更加易读,简洁,只在创建数组时进行了一次强转。
此外,可以使用<E extends T>可使泛型元素获得原来T的功能。
总之,使用泛型类型的参数可尽量避免运行时的类型强转。

//保证放进去的元素都有E的特性
    public void pushAll(Iterable<? extends E> src){
        for (E e : src) {
            push(e);
            
        }
    }
//保证取出来的元素至多有E的特性
    public void popAll(Iterable<? super E> dst){
        while (! isEmpty()){
            dst.add(pop());
        }
    }
Chapter06:枚举和注解
public enum Operation {
    PLUS{
        @Override
        public double apply(double x, double y) {
            return x + y;
        }
    },
    MINUS{
        @Override
        public double apply(double x, double y) {
            return x-y;
        }
    };
//由子类实现
   public abstract double apply(double x,double y);
}

3.当由某个特征获取枚举对象时,可返回Optional<T>对象,由客户端判断是否能获取到枚举对象。
4.当编译期可确定常量的集合内容时,都可以使用枚举类来实现。

public enum Ensemble {
    SOLO(1),
    DUET(2);
    
    private int numberOfMusicians;

    Ensemble(int size){
        this.numberOfMusicians = size;
    }
    //正确使用
    public int numberOfMusicians1(){
        return numberOfMusicians;
    }
    //错误使用
    public int numberOfMusicians(){
        return ordinal() + 1;
    }
    
}

Enum类中ordinal的设计是用来比较枚举对象的大小和EnumSet,EnumMap中使用的。

Chapter07:Lambda表达式和流处理
//Lambda 表达式形式
Comparator c = (p1, p2) -> p1.getAge().compareTo(p2.getAge());
//方法引用
Comparator c = Comparator.comparing(Person::getAge);

使用方法引用大多时候会显得更加简洁易懂。但是当Lambda 表达式形式比方法引用更简洁的时候,使用前者。

1.代码块可读写作用域内访问的任何局部变量。Lambda只能读用final修饰或者effectively final(A variable or parameter whose value is never changed after it is initialized is effectively final.)的变量,并且不能修改任何局部变量。
2.代码块可使用returnbreakcontinue或者抛异常;Lambda不能做这些事情。
如果需要上述这些代码块操作的,则不适合使用streams。streams适合做的事情为:
1.统一的流中元素转换
2.按照条件过滤一些元素
3.用简单的操作(如求最小值)处理元素
4.把元素放入集合容器中,如分组
5.查询满足某些条件的元素集合
其实也就是map-reduce操作适合流式处理。

Chapter08:方法
  public static <E extends Enum<E>> EnumSet<E> of(E e) {
      ....
    }
 public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2) {
 ....
    }
 public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3) {
    ....
    }
 public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4) {
        ....
    }
public static <E extends Enum<E>> EnumSet<E> of(E e1, E e2, E e3, E e4,E e5)
    {
 ....
    }
 public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
       ....
    }
  private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 
//返回空数组
  public Object[] getObject(){
        ...
        return Collections.emptyList();
    }
//返回空集合
    public List<Object> getObject(){
       ...
        return list.toArray(EMPTY_OBJECT_ARRAY);
    }
Chapter09:通用编程
 public static void main(String[] args) {
        Long sum = 0L;
        for (long i = 0;i<Integer.MAX_VALUE;i++){
//不停的拆箱封箱
            sum += i;
        }
        System.out.println(sum);
    }

2.原始类型有默认值,而包装类型初始值为null,进行运算时可能会报NullPointerException。
3.原始类型可使用==比较,包装类型的==总是false。

Chapter10:异常
            try {
                ... //调用其他低级方法
                
            }catch (LowerLevelException e){
                throw new HigherLevelException(...);
            }
Chapter11:并发
            synchronized (obj){
                while (<condition does not hold>){
                    obj.wait();//不满足条件时释放锁,唤醒时重新再获得锁
                }
                    ... //满足条件时的操作
                
            }

这样子保证了只有条件满足时,才能执行操作。否则可能由于notifyAll/notify唤醒了不该唤醒的线程等导致条件不满足就被唤醒了,而执行错误操作。

Chapter12:序列化

首先介绍下Java原生序列化的原理:

Java原生序列化使用ObjectInputStream.readObjectObjectOutputStream.writeObject来序列化和反序列化对象。对象必须实现Serializable接口,同时对象也可以自实现序列化方法readObject和反序列化方法writeObject定义对象成员变量的序列化和反序列化方式。此外对象还可实现writeReplace,readResolve,serialVersionUID
writeReplace主要用来定义对象具体序列化的内容,可对原来按照readObject方法序列化的内容进行修改替换。
readResolve主要用来定义对象反序列化时的内容,可对原来按照writeObject方法反序列化的内容进行修改替换。
serialVersionUID是类的序列化版本号,保证能将二进制流反序列化为内存中存在的类的对象。如果不主动生成的话,在序列化反序列化过程中根据类信息动态生成,耗时且类结构不灵活。

关于原生序列化的优化,其实都不用看。。。

因为生产上一般是不会使用原生序列化的,性能差又不安全又不易读....有关序列化的知识和框架选择,可参考:
序列化和反序列化
分布式服务序列化框架的使用与选型

感谢您的阅读,我是Monica23334 || Monica2333 。立下每周写一篇原创文章flag的小姐姐,关注我并期待打脸吧~

上一篇下一篇

猜你喜欢

热点阅读