Effective java 总结

用静态工厂方法代替构造器的最主要好处

1.没必要每次都建立新的对象java

Boolean.valueOf编程

Long.valueOfapi

 

2.直接返回接口的子类型,对于外界来讲并不须要关心实现细节,主要知道这个接口就行数组

Collections.unmodifiableList数据结构

......并发

 

为何避免使用终结方法

1.终结方法不会被及时执行app

2.不一样jvm上实现不一样eclipse

3.可能根本不会执行jvm

4.在其中抛出的异常会被忽略ide

5.性能差

 

 

 

什么时候使用:

1.做为释放资源的备用方法

FileInputStream:

protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {

            /*
             * Finalizer should not release the FileDescriptor if another
             * stream is still using it. If the user directly invokes
             * close() then the FileDescriptor is also released.
             */
            runningFinalize.set(Boolean.TRUE);
            try {
                close();
            } finally {
                runningFinalize.set(Boolean.FALSE);
            }
        }
    }

2.调用native方法释放native Object

3.调用父类的终结方法,由于子类覆盖父类,父类的方法可能不会被调用

 

 

equals方法

1.没法在扩展可实例化类的同时,既增长新的值组件,同时有保留equals约定.

因此若是子类equals传入了父类实例,却不是子类的实例,应该返回false.

 

2.equals不该该依赖于不可靠资源,equals方法应该对驻留在内存中的对象执行肯定性的计算.

 

其余:

float比较能够用Float.compare

double同理

 

hashcode

1.覆盖equals,也应该覆盖hashcode.

2.相等的对象应该有相等的hashcode,有相等的hashcode的对象不必定是equals的

3.eclipse能够自动生成hashcode,通常不用本身写

 

clone

1.最好不要扩展Cloneable接口,而是用其余方法克隆,好比公司用BeanCopier

其余:clone虽然返回Object类型对象,可是数组clone的时候可以自动转型不须要额外操做...下面代码是没有编译错误的

 

 String[] s = new String[]{"1"};
 String[] s2 = s.clone();

 

 Comparable接口

1.有序集合contains可能会调用compareTo方法,而非有序集合contains则可能会调用equals来判断. (例子??)

 

使可变性最小化

不可变类的条件:

1.不一样修改对象状态的方法(成员域没有set方法)

2.类不会被扩展(final修饰)

3.全部域都是final的

4.全部域都是私有的

5.外部不能得到不可变对象内部可变对象的引用,或者没有指向可变对象的域

可是感受有些条件是多余的,好比String是不可变类,可是他就有个hash域,不是final的,反正只要这个类的域(状态)不可能被修改就能够了.

 

接口优于抽象类

1.实现了接口的类能够把对于接口方法的调用,转发到一个内部私有类的实力上,这个私有内部类扩展了骨架实现类.

好比List接口有个ListIterator<E> listIterator(); 方法, ArrayList中内私有内部类private class Itr implements Iterator<E>就实现了这个方法.

同时,这是私有内部类的一种用法,用来实现接口,Itr 依赖于外部的ArrayList,不能独立存在,可是又能够有本身额外的一些方法的实现.

 

接口只用于定义类型

1.常量接口模式是对接口的不良使用,实现常量接口会致使把实现细节泄露到该类的导出API中

 

用函数对象表示策略

1.由于策略接口被用做全部具体策略实例的类型,因此咱们并不须要为了导出具体策略,而把具体策略类作成共有的.相反,宿主类还能够导出共有的静态域,其类型为策略接口,具体的策略类能够是宿主类的私有嵌套类.

策略模式使用的时候通常是: 策略接口名称 引用名 = 调用具体策略的得到方法.

因此外界使用的时候是使用接口作为类型的而不是具体的策略类名.这样不须要关心策略的具体实现细节,也是多态的表现.

好比 private static class CaseInsensitiveComparator implements Comparator<String>, java.io.Serializable

String类的CaseInsensitiveComparator是私有的静态嵌套类,外界不可能得到这个类的引用,可是能够从String的共有的域public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

来得到Comparator接口CaseInsensitive的实现.

同时,这是静态内部类的一种用法,也是用来实现接口,与Itr依赖于外部的ArrayList不一样的是这个Comparator不依赖于外部的String,因此是static的.而且不少时候是做为类的静态变量导出,而不是实例的成员域

因此说实现接口的时候若是是用内部类来实现的话,若是实现接口须要用到外部类的实例的域的话,那应该使用private class,若是依赖于外部的类的话可使用private static class.另外就是非静态内部类的是依赖于外部类的实例的,因此若是外部类有static类型的域那是不可能指向非static的内部类的..

 

优先考虑静态成员类

1.静态成员类的一种常见用法是做为共有的辅助类,仅当与它的外部类一块儿使用时才有意义.

好比前面String的CaseInsensitiveComparator.

再好比把枚举类定义在类内部同样(例子??).

 

优先考虑静态成员类

1.静态类和非静态类的例子:

HashMap中的static class Entry<K,V> implements Map.Entry<K,V>, Entry不须要用到HashMap中的域

HashMap中的private final class KeySet extends AbstractSet<K>, KeySet须要用到HashMap中的域,好比size

 

请不要在新代码中使用原生态类型

消除非受检警告

1.数组具备协变的特性,而泛型没有,泛型是具体的.

String extends Object, 因此String[] 是Object[]的子类型, 而List<String>却不是List<Object>的子类型

建立泛型,参数化类型,类型参数的数组(List<E>[], List<String>[], E[])是非法的(建立实例new是非法的,引用名字是能够的), 例外是使用无限制通配符?

 

2. 类型参数T使用通配符?时,只能放null元素

 

3.由于类型擦除,因此instanceof和类文字(XXX.class)中必须使用原生类型

 

优先考虑泛型方法

1.泛型方法被调用的时候大多数时候是不须要制定类型参数的,这是因为类型推断的结果,可是构造方法是个例外,必须指明类型参数的具体值.

例如 Map<String, String> map = new HashMap<String, String>();由于是构造方法,全部必须指明类型参数为String.

 

2.经过包含类型参数的表达式类限制类型参数是容许的,这就是递归类型限制

例如:public static <T extends Comparable<T>> T max(List<T> list){....}

 

利用有限制通配符来提高API灵活性(泛型最有用的一节)

1.由于泛型不是协变的,因此若是方法参数是List<T> list,而你传入的list的类型参数不是T而是T的子类型的话就会报错.

解决办法是在方法的参数中的类型参数中使用通配符,好比ArrayList的addAll方法:

 

 public boolean addAll(Collection<? extends E> c) {
    ..............
 }

 

这里使用Collection<? extends E>而不是Collection<E>就是这个缘由,使用了这个通配符之后泛型就彷佛有了像数组同样协变的特性.

同理? super XXX也是同样的道理,popAll若是容许传入一个Collection,并将pop掉的全部元素都放入那个Collection的话那参数就应该写成

 

public void popAll(Collection<? super E> c){....}

 

书上总结出的规律就是PECS(producer-extends consumer-super),刚才addAll是生产,popAll是消费

当方法的参数是带参数类型的时候均可以考虑使用通配符

 

2.有一个小技巧,通配符?的集合好比List<?>是不能插入null之外的元素的,可是能够将这个集合传入另一个带参数List<T>的方法,用那个方法去add元素

书上的例子:

 

public static void swap(List<?> list, int i, int j){
    swapHelper(list, i, j);
}    

public static <E> void swapHelper(List<E> list, int i, int j){
    list.set(i, list.set(j, list.get(i)));      
}

 

3.当有限制的通配符赶上递归类型限制的话会比较麻烦,可是PECS仍是有用的

 

package test;

import java.util.Arrays;
import java.util.List;

public class GenericTest2 {
    public static <T extends Comparable<? super T>> T max(List<? extends T> list) {
        return null;//具体的实现省略.....
    }

    public static void main(String[] args) {
        max(Arrays.asList(new MyNumber()));//不使用?extends和super的最通常状况
        max(Arrays.asList(new MyInteger()));//若是不是Comparable<? super T>而是Comparable<T>的话就会报错,由于MyInteger extends的是Comparable<MyNumber>而不是Comparable<MyInteger>
        GenericTest2.<MyInteger> max(Arrays.asList(new MyPositiveInteger()));//若是不是List<? extends T>而是List<T>的话就会报错,由于T在这里是MyInteger
    }
}

class MyNumber implements Comparable<MyNumber> {
    @Override
    public int compareTo(MyNumber o) {
        return 0;//具体实现省略....
    }

}

class MyInteger extends MyNumber {

}

class MyPositiveInteger extends MyInteger {

}

 

用enum代替int常量

1.枚举和int或者String常量相比最大的优点就是它是个类,能够有方法和本身的属性,能够提供一些行为.

 

2.枚举是不可变的,因此成员域应该为final

 

3.若是枚举有通用性,那就该成为顶层类(public),不然能够写在另外1个类中做为成员类

 

4.若是每一个枚举对象都有本身独特的行为的话,就是说枚举类中的方法在每一个枚举对象中都是不一样实现的话能够考虑把这个方法写成abstract的(枚举类中能够定义abstract的方法),而后每种枚举对象都实现这个方法.

好比:

 

enum Operation {
    PLUS {
        @Override
        double apply(double a, double b) {
            return a + b;
        }
    },
    MINUS {
        @Override
        double apply(double a, double b) {
            return a - b;
        }
    },
    TIMES{
        @Override
        double apply(double a, double b) {
            return a * b;
        }
    };

    abstract double apply(double a, double b);
}

 

5.若是多个枚举对象共享同一种行为,另外多个枚举对象共享另一种行为,能够考虑使用枚举策略,在其内部再定义一个枚举类.意思就是说枚举类中的abstract至关因而接口方法,枚举对象覆盖这个方法至关因而接口的实现.

好比:

 

enum PayrollDay {
    MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(
            PayType.WEEKDAY), SATURDAY(PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayrollDay(PayType p) {
        payType = p;
    }

    double pay(double hours, double payRate) {
        return payType.pay(hours, payRate);
    }

    private enum PayType {
        WEEKDAY {
            @Override
            double overtimePay(double hours, double payRate) {
                return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT) * payRate / 2;
            }
        },
        WEEKEND {
            @Override
            double overtimePay(double hours, double payRate) {
                return hours * payRate / 2;
            }
        };
        private static final int HOURS_PER_SHIFT = 8;

        abstract double overtimePay(double hours, double payRate);

        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked, payRate);
        }
    }
}

 

 

PayType至关于就是一个策略接口,它的2个实现至关因而具体的策略.若是不用PayType这个内部枚举类的话可能就要写switch case了....

 

6.枚举的构造方法在static区块以前被调用,这个很神奇,可是也能够理解,由于枚举类的实例至关因而final且惟一的,会被优先建立....

 

enum A {
    
    A1("a1"), A2("a2");
    static EnumTest2 test2;
    String s;
    public String toString() {
        return s;
    }
    
    private A(String s){
        this.s = s;
        System.out.println(s);//在static方法以前被调用
    }
    
    static{//枚举建立对象在static以前
        System.out.println("static");
    }
}

 

先输出a1,a2再输出static

 

 

用实例域代替序数

1.获取枚举实例的定义顺序的时候可使用ordinal方法,可是最好仍是构造枚举对象的时候传一个数字做为参数更好,而不是依赖于实例定义的顺序.

 

用EnumSet代替位域

1.方法传递多个配置项的时候最好不用多个int的|操做,而是使用enumset传递多个enum做为参数.

int bold = 1 << 0;

int italic= 1 << 1;

传递 bold | italic 不如传递2个枚举

 

用EnumMap代替序数索引 

1.若是有类型依赖于多个枚举常量,那它也能够写成新的枚举,实例构造方法传递含有以前的枚举常量做为成员域,而不是写个数组简单的包含多个以前的枚举(可使用EnumMap,key强制为枚举类型).

由于数组的话就依赖于数组的下标,维护起来比较麻烦.

 

注解优于命名模式

1.方法名称这样的命名模式好比使用注解来的方便,并且注解能够添加额外信息.

好比junit testXX方法好比使用@Test注解.不会影响原本的方法命名.

 

 用标记接口定义类型

1.接口在编译时就能被检查,而注解能够在运行时获取更多的信息.

2.接口定义好之后很难再去修改,而注解能够不断增长新的信息

3.接口和类只能被类扩展,而注解能够用于任何元素

 

检查参数的有效性

1.public方法可使用@throws说明抛出的异常

2.非public方法使用assert检查参数,由于这些方法只能是你本身调用,外部使用者不可能会调用,我本身写的方法我能保证调用参数是正确的,因此可使用assert尽快发现错误.

3.错误应该尽快发现,尽早检查.

 

必要时进行保护性拷贝

1.能够先copy对象再检查对象的有效性,由于你在检查对象的有效性时,别的线程可能会改变这个对象.

2.当对象进入你的数据结构之后可能发生变化的话,可能也须要进行拷贝,由于传给你的对象的调用者在以后可能会修改这个对象

 

慎用重载

1.方法调用的时候选择哪一个重载方法是在编译期根据引用的类型来判断的,而不是引用指向的对象的类型来判断的.(在编译期就能肯定调用哪一个重载方法)

2.对于重载方法的选择是静态的在编译期根据引用类型来肯定的,对于覆盖方法的选择是动态的在运行期根据对象类型来肯定的.

3.为了不混乱,尽可能不要处处躲个具备相同参数数目的重载方法

 

慎用可变参数

1.当可变参数列表与基本类型数组结合起来的时候会有一些问题.

 

public class ParamterTest1 {
    public static void main(String[] args) {
        Arrays.asList(new int[] { 1, 2, 3, 4 });
        Arrays.asList(new Integer[] { 1, 2, 3, 4 });
        System.out.println(Arrays.asList(new int[] { 1, 2, 3, 4 }));
        System.out.println(Arrays.asList(new Integer[] { 1, 2, 3, 4 }));
    }
}

输出
[[I@6dc57a92]
[1, 2, 3, 4]

 

问题在于new Integer[]{....}传入方法的时候被当作一个Integer的数组,泛型T是Integer

而new int{}{...}传入方法的时候被看成一个只有 一个元素为int[] 的数组,泛型T是int[]

缘由是由于泛型T只能是引用类型,不能是基本类型,因此T只能是int[]而不能是int.

 

返回零长度的数组或者集合而不是null

1.可使用Collections下的类来帮助实现.

 

将局部变量的做用域最小化

1.局部变量应该在使用到它的地方声明并直接初始化,而不是所有在方法第一行直接所有定义好.

2.for比while好的一个地方在于while里用到的便利有时候须要定义在while外侧.致使做用域变大,而for循环里的变量的做用域只限于for,因此CVfor循环的代码通常不会出错,而while代码CV起来不注意的话可能会有问题.

 

for-each循环优先于传统的for循环

1.for-each可能效率会比通常的for高一点,主要仍是简单,不容易出错,可是要在便利过程当中修改集合的话,仍是须要使用迭代器.

 

第48条:若是须要精确的答案,请避免使用float和double

1.精确计算小数可使用bigdecimal..可是操做比较麻烦速度也比较慢..若是速度要求较高的话能够把小数扩大必定倍数之后用int和long来计算..可是也很麻烦.

 

第52条:经过接口引用对象

1.若是能够应该使用接口引用对象而不是类.由于这样可使程序更加灵活. 若是接口引用周围的代码须要依赖具体的实现类的细节,那么在接口引用的地方用注释说明.由于这个时候代码其实与接口的具体实现是相关的.

2.当使用接口引用对象的时候更换具体的实现是很是简单的..更换具体的实现类的缘由多是由于新的实现能够提供更好的效率.

 

第57条:只针对异常的状况才使用异常

1.不该该用异常控制正常程序的流程.

本来的公司就有这样的问题,经过throw不一样的异常来返回上层方法.而后后面需求改了,有些时候这些异常即便throw了也要继续下去.而后代码就控制不了了,由于发生异常之后就会返回上层方法调用,或者进入catch代码块跳过异常后续的代码.

2.若是要有状态相关的方法.能够才用状态测试方法和可识别的返回值两种方法代替异常

我通常在方法的参数里传入dto而后dto里有个success标注此次方法执行的状态.供外层方法检测.

 

第58条:对可恢复的状况使用受检异常,对编程错误使用运行时异常

 1.若是异常是能够恢复的,那就是用受检异常,若是异常是不能够恢复的,那就是用runtimeexception和它的子类.

2.写类继承throwable是不必的,能够直接继承exception

 

第60条:优先使用标准的异常

经常使用异常:

IllegalArgumentsException 非NULL参数值不正确

IllegalStateException 对于方法调用,对象状态不适合

NullPointerException 在禁止传入null的状况下传入了null值

IndexOutOfBoundsException 下表参数值越界

ConcurrentModificationException 在禁止并发修改的状况下检测到对象被修改

UnSupportOperationException 对象不支持用户请求的方法

 

第61条:抛出与抽象相对应的异常

1.高层的方法在必要的时候能够try catch底层方法抛出的异常而且从新throw一个高层的异常,由于底层的异常太底层了有时候很难明白究竟是啥错误,为啥会有这个错误

2.若是底层的异常是有帮助的,那能够利用异常链,throw一个高层异常,同时cause里传入底层的异常.

 

第64条:努力使失败保持原子性

1.由于抛出异常的时候须要输出出错的信息,因此就要尽可能保持对象的状态是原始的状态而不是修改后的状态.

保持对象状态不变有一些方法:

不可变的对象

在操做以前检查有效性

调整操做顺序让可能会抛出异常的操做先于改变对象的操做

编写一段恢复对象的代码

操做对象以前备份

2.不是全部的时候都必定要这么作,由于有时候代价会很大,并且有时候对象就是会被修改.

 

第74条:谨慎地实现Serializable接口

1.实验得出:当子类实现Serializable父类没实现,而父类有没有无参构造方法的时候readObject方法就会报错

2.1的同时若是父类有无参构造方法,那子类能够反序列化,可是父类的属性都没有值,都本身去写readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out)方法

3.当序列化反序列化出现错误的时候(不知道啥错误)不能正确初始化成员域的时候会使用readObjectNoData()代替readObject(ObjectInputStream in)方法(虽然我以为1万年都用不到)

4.writeReplace方法先于writeObject方法调用,readResolve后于readObject方法调用.因此能够分别替换写入和读取的对象可能会用于单例.

 

第75条:考虑使用自定义的序列化形式

1.不少状况须要本身写readObject和writeObject去覆盖默认的序列化实现,由于对象中还有其余对象的引用,会遍历到其余对象,直到遍历全部对象为止.可能会消耗过多资源..若是对象是个值类.那序列化可能没啥问题,否则可能就须要本身去覆盖默认的序列化行为.

2.即便是private的属性,由于序列化的缘由之后也会像public的api同样,不能够随意修改.

3.实验得出:反序列化的时候并无调用构造方法.可是会调用父类的构造方法.

 

第76条:保护性地编写readObject方法

1.若是序列化的类的属性里有private的其余类,那反序列化的时候须要对这个类进行保护性拷贝(代价就是这个属性类不可能被声明为final了,由于要拷贝,二次赋值),以防止被修改(能够修改字节流,额外增长引用指向以前private的类,而后用这个引用修改private类达到目的).另外在拷贝完成以后进行字段的校验.

 

第77条:对于实例控制,枚举类型优先于readResolve

1.若是单例类的属性是非transient的那么修改字节流可让它被反序列化的时候被替换,原理就是编写一个其余类,readResolve返回一个新的值,而后修改字节流,属性引用指向这个新写的类.当单例类被反序列化的时候会先反序列化它的属性类,它的属性类指向新写的修改类,修改类的readResolve就会被调用.

2.解决这个问题能够将属性加上修饰词transient或者将类设计成枚举类(可是绝大部分状况下这个类的成员域可能在编译的时候是不知道的.这样就不能设计成枚举类).

 

第78条:考虑用序列化代理代替序列化实例

1.反序列化是用到了语言以外的特性的.咱们可使用java语言自带的功能来完成反序列化.原理就是写一个private的内部类.外部类序列化的时候writeObject实际上是写入了内部类的对象.而后内部类的readResolve方法能够返回外部类的实例.这样至关因而已知一个内部类对象,copy一个外部类对象.

即便伪造字节流也能够免受影响,由于内部类对象到外部类对象的copy是咱们本身手动用代码完成的.

相关文章
相关标签/搜索