Effective Java学习记录

一、创建和销毁对象

1、考虑用静态工厂方法替代构造器

静态工厂方法相对构造方法优势:

1、有名称;
2、不必每次调用的时候都创建一个新的对象;
3、它可以返回原返回类型的任何子类对象;
4、在创建参数化类型实例的时候,它们使得代码变得更加简洁;
构造实例化
Map<String,List<String>> m = new HashMap<String,List<String>>(); 
静态工厂方法:
public static <K,V> HashMap<K,V> newInstance() {
	return new HashMap<K,V>();
}

静态工厂方法缺点:

1、类如果不含公有的或者受保护的构造器,就不能被子类化;
2、它们与其他的静态方法实际上没什么区别;
	常见的静态工厂方法的命名方式:
	valueOf、of、getInstance、getType、newType
2、遇到多个构造器参数时要考虑用构建器

使用构建器可以很好的进行对象参数设置值,采用链式操作更方便

public class NutritionFacts {

    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;

    public static class Builder {
        private final int servingSize;
        private final int servings;

        private int calories = 0;
        private int fat = 0;


        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servings = builder.servings;
        servingSize = builder.servingSize;
        calories = builder.calories;
        fat = builder.fat;
    }
}

使用Builder构建器

NutritionFacts cocaCola = new NutritionFacts.Builder(240,8).fat(100).calories(50).build();
3、用私有构造器或者枚举类型来强化Singleton属性

特权客服端通过AccessibleObject.setAccessible方法通过反射机制破坏单例,解决办法,可以在第二次创建对象的时候抛出异常

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    public Singleton() {
    	// 防止非法破坏单例
        if (INSTANCE != null) {
            throw new RuntimeException("非法访问");
        }
    }
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

枚举单例,默认阻止反射、反序列化破坏,推荐使用

public enum Singleton{
	INSTANCE;
	private Object data;
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}
	public static Singleton getInstance() {
		return INSTANCE;
	}
}
4、通过私有构造器强化不可实例化的能力

简单说就是,不需要实例化的类,把它的我构造方法私有化,防止外界实例化对象,有点单例的味道

5、避免不必要的对象创建

5.1、重用不可变对象
例如: String s = new String(“abc”);程序每次调用都会创建一个新的实例,但是这些创建对象的动作全是不必要的;传递给构造器的参数”abc“本身就是一个String实例,功能方面等同于构造器创建的所有对象。如果这行代码在循环中,就会创建出成千上万这样的实例;
该进方式:
String s = “abc”;
5.2、除了可重用不可变对象外,也可以重用哪些已知不会被修改的可变对象
反例:

public class Person {
    /*
        需求:检验一个人是否出生在1993年到2000年间
     */
    private final Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    public boolean isBabyBoomer() {
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1993,Calendar.JANUARY,1,0,0,0);
        Date boomStart = gmtCal.getTime();
        gmtCal.set(2000,Calendar.JANUARY,1,0,0,0);
        Date boomEnd = gmtCal.getTime();
        
        return birthDate.compareTo(boomStart) >= 0 &&
                birthDate.compareTo(boomEnd) <0;
    }
}

优化后

public class Person {
    /*
        需求:检验一个人是否出生在1993年到2000年间
     */
    private final Date birthDate;

    // 开始结束时间
    private static final Date START_DATE;
    private static final Date END_DATE;

    static{
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1993,Calendar.JANUARY,1,0,0,0);
        START_DATE = gmtCal.getTime();
        gmtCal.set(2000,Calendar.JANUARY,1,0,0,0);
        END_DATE = gmtCal.getTime();
    }

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    public boolean isBabyBoomer() {
        return birthDate.compareTo(START_DATE) >= 0 &&
                birthDate.compareTo(END_DATE) <0;
    }
}

5.3、要优先使用基本类型而不是装箱基本类型,当心无意识的装箱
装箱类型和基本类型性能差距很大

public static void main(String[] args) {

        StopWatch sw = new StopWatch("性能对比");
        sw.start("Long装箱类型性能");
        Long sum = 0L;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            sum += i;
        }
        System.out.println(sum);
        sw.stop();

        System.out.println("======================================");
        sw.start("long基础类型性能");
        long s = 0;
        for (long i = 0; i < Integer.MAX_VALUE; i++) {
            s += i;
        }
        System.out.println(s);
        sw.stop();
    }

执行结果:

2305843005992468481
======================================
2305843005992468481
StopWatch '性能对比': running time = 7373559000 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
6814963900  092%  Long装箱类型性能
558595100  008%  long基础类型性能

测试结果可以发现,使用基础类型的性能要远远高于装箱的性能

6、消除过期对象引用

举例:

public class Stack{
    private Object[] element;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public void push(Object e) {
        ensureCapacity();
        element[size++] = e;
    }
    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }
        return element[--size];//待优化点
        // 具体优化代码
        // Object result = element[--size];
        // return result;
    }
    private void ensureCapacity() {
        if (element.length == size) {
            element = Arrays.copyOf(element,2 * size + 1);
        }
    }
}

上面栈的实现,执行起来没什么问题,但是栈长先增长后紧缩,会隐藏一个问题,pop操作只是把size递减了,但是数组并没有设置为null,这样会存在内存浪费。
总之,只要是程序员自己管理内存,就应该警惕内存泄漏问题
内存泄漏常见原因:

- 自己管理内存,如上述栈的例子
- 缓存
	1、使用WeakHashMap 存放缓存,注意将一对key, value放入到 WeakHashMap 里并不能避免该key值被GC回收,除非在 WeakHashMap 之外还有对该key的强引用。
	2、随着时间边长缓存变得越来越没有价值,缓存应该时刻清楚掉没用的项,清楚工作可以启动一个后台线程(Timer或者ScheduledThreadPoolExecutor)来完成。
	3、给缓存加新条目时清除容器中最早加入的一条,可以使用LinkedHashMap类利用它的removeEldestEntry方法可以很容易的实现;
- 监听器和其他回调
  回调立即被当场垃圾回收的最佳方法是只保存它们的弱引用,例如,保存成WeakHashMap中。
7、避免使用终结方法

终结方法(finalizer)通常不可预测的,也很危险,一般情况下是不必要的。

二、对所有对象都通用的方法

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

1、自反性:对于任何非null的引用值x,x.equals(x)必须要返回false;
2、对称性:对于任何非null的引用之x和y,当且仅当y.equals(x)时,x.equals(y)必须返回true;
3、传递性:对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true;
4、一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致地返回false;
实现高质量equals方法的诀窍:
1、使用==操作符检查“参数是否为这个对象的引用”(基本类型使用,float、double比较使用compare)
2、使用instanceof操作符检查“参数是否为正确的类型”
3、把参数转换成正确的类型。
4、对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配
5、当你编写完成了equals方法之后,应该问自己三个问题:它是否对称的、传递的、一致性的
6、覆盖equals时总要覆盖hashCode
7、不要企图让equals方法过于智能
8、不要将equals声明中的Object对象替换 为其他的类型。(违反重写)

9、覆盖equals时请遵守通用约定

1、在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一对象调用多次,hashCode方法都必须始终如一的返回同一个整数。
2、如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果
3、如果两个对象根据equals(Object)方法比较是不相等的,,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。

10、始终要覆盖toString

重写toString方法,返回对象中应该包含的重要信息。

11、谨慎地覆盖colne

尽量不去实现Cloneable,Cloneable内部没有提供clone方法,如果要克隆,调用的是Object的方法;除了拷贝数组可以使用外,其他对象的拷贝不建议使用,可以使用拷贝构造器或拷贝工厂类似于静态工厂

12、考虑实现Compareable接口

compareTo方法并没有在Object中声明,相反,它是Comparable接口中唯一的方法,不但可以进行简单的同性比较,而且允许执行顺序比较;
public static void main(String[] args) {
Set s = new TreeSet<>();
s.add(“B”);
s.add(“A”);
s.add(“E”);
s.add(“C”);
Collections.addAll(s,args);
System.out.println(s);
}
基本类型的域可以使用< 和 >,浮点型可需要使用compare例如:Double.compare、Float.compare
compateTo方法返回:

-1:由高到低
1:由低到高
0:保持原样

三、类和接口

13、使类和成员的访问性最小化

第一规则:尽可能使每个类或成员不被外界访问,尽可能做到最小访问级别;除了公有静态final域的特殊情形之外,公有类都不应该包含公有域(也就是把属性私有化),并确保公有静态final域所引用的对象都是不可变的。
反例:

public static final Thing[] Values = {....}

修正:
方式一

private static final Thing[] PRIATE_VALUES = {....}
public static final List<Thing> VALUES  = Collections.unmodifiableList(Arrays.asList)(PRIATE_VALUES);

方式二

private static final Thing[] PRIATE_VALUES = {....}
public static final Thing[] values(){
	return PRIATE_VALUES.clone();
};
14、公有类中避免直接暴漏属性

类中避免直接把属性定义成public的类型

15、使可变最性小化

类不能做成不可变的应该尽量的限制它的可变性。

16、复合优于继承

继承相当于是类之间的所属关系,具有很强的传递性,如果父类有些缺陷同样的很大可能传递给子类;复合可以很好的实现和继承某些方面相同的效果,同时减少了类与类之间的耦合。

17、要么为继承而设计,并提供文档说明,要么就禁止继承

为继承而设计的类,必须提供详细的说明文档,避免子类错误继承;
两种禁止子类化的方法:

1、把类声明为final
2、把所有的构造器都变成私有的,并增加一些公有的静态工厂来替代构造器
18、接口优于抽象类

接口通常是允许多个实现的类型的最佳途径。但是当演变的容易性比灵活性更重要的时候就要考虑抽象类了。

19、接口只用于定义类型

有一种接口叫做常量接口,接口中只包含常量,常量接口是对接口的不良使用,应该当成反面例子。
导出常量几种合理选择方案:

1、如果常量和某些现有类或者接口紧密相关,就应该把这些常量放到这个类或者接口中。
2、如果常量最好看成枚举类型的成员,就应该用枚举类型
3、使用不可实例化的工具类

不可实例化工具类:

public class PhysicalConstants{
	private PhysicalConstants(){}
	public static final double ELECTRON_MASS = 9.00;
}
20、类层次优于标签类

一个类包含一些通用并特有的一些功能的时候,可以考虑抽象出一个基类,让子类去实现特有的功能。

21、用函数对象标识策略

当一个具体策略是设计用来重复使用的时候,它的类通常就要被实现为私有化的静态成员类,并通过公有的静态final域被导出,类型为该策略接口。

public class Host {
    
    private static class StrLenCmp implements Comparator<String>, Serializable{
        @Override
        public int compare(String s1, String s2) {
            return s1.length() - s2.length();
        }
    }
    
    public static final Comparator<String> STRING_LENGTH_COMPARATOR = new StrLenCmp();
}
22、优先考虑静态成员类

嵌套类优先考虑静态成员类;
匿名类常见的用法:

1、动态的创建函数对象
2、创建过程对象
3、静态工厂方法的内部

四、泛型

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

声明中具有一个或者多个类型参数的类或者接口,就是泛型类或者接口。
实际使用中尽量使用具体泛型而不是原生态类型,例如使用List而不是List

24、消除非受检警告

尽量消除警告,比如

Set<Lark> exaltation = new HashSet();//提示警告
Set<Lark> exaltation = new HashSet<Lark>();//消除警告

如果无法消除警告,同时可以证明引起警告的代码是类安全的,(只有在这种情况下才可以用一个@SupperessWarnings(“unchecked”)注解来禁止这条警告),应该始终在尽可能小的范围中使用SupperessWarnings注解,如果确定要使用,都要添加一条注释,说明为什么这么做是安全的

25、列表优于数组

一般来说,数组和泛型不能很好的混合使用。如果他们混合起来使用,并且得到了编译时错误或者警告,第一反映就应该是用列表代替数组。

26、优先考虑泛型

在设计新类型的时候,要确保他们不需要这种转换就可以使用。这就意味着要把类做成是泛型的。只要时间允许,就把现有的类型都泛型化。总结来说就是把类做成泛型化更加方便扩展。

27、优先考虑泛型方法

1、静态工具方法尤其适合于泛型化。

public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<E>(s1);
        result.addAll(s2);
        return result;
    }

2、泛型工厂

public static <K,V> HashMap<K,V> newHashMap() {
	return new HashMap<K,V>();
}

3、递归类型限制

public static <T extends Comparable<T>> T max(List<T> list) {
	Iterator<T> i = list.iterator();
	T result = i.next();
	while(i.hasNext()) {
		T t = i.next();
		if(t.compareTo(result) > 0) {
			result = t;
		}
	}
	return result;
}
28、利用优先通配符来提升API的灵活性

一般来说,如果类型参数只在方法声明中出现一次,就可以用通配符取代它。如果是无限制的类型参数,就用无限制的通配符取代它;如果是有限制的类型参数,就用有限制的通配符取代他。

public static <E> void swap(List<E> list,int i,int j);
public static void swap(List<?> list, int i, int j);// 比较好
29、优先考虑类型安全的异构容器

安全的异构容器,可以用Class对象作为键,以这种方式使用的Class对象称作类型令牌,就可以使用定制的键类型

五、枚 举和注解

30、用enum代替int常量

1、需要一组固定常量的时候,需要使用枚举
2、枚举类型的常量并不一定要始终保持不变
3、如果多个枚举常量同时共享相同的行为,则考虑策略枚举

31、用实例域代替序数

反例:

public enum Ensemble {

    SOLO,DUET,TRIO,QUARTET,QUINTET,
    SEXTET,SEPTET,OCTET,NONET,DECTET;

    public int numberOfMusicans() {
        return ordinal() + 1;
    }
}

正例:

public enum Ensemble {
	SOLO(0),DUET(2),TRIO(3),QUARTET(4),QUINTET(5),
    SEXTET(6),SEPTET(7),OCTET(8),NONET(11),DECTET(12);

    private final int numberOfMusicians;

    Ensemble(int size){
        this.numberOfMusicians = size;
    }
    public int numberOfMusicans() {
        return numberOfMusicians;
    }
}

枚举使用中最好比避免使用ordinal方法。

32、用EnumSet代替位域

用OR位运算将几个常量合并到一个集合中,称作位域

public class Text {

    public enum Style {
        BOLD, ITALIC, UNDERLINE, STRIKETHROUGH
    }

    public void applyStyles(Set<Style> styles){
        System.out.println();
    }

    public static void main(String[] args) {
        Text text = new Text();
        text.applyStyles(EnumSet.of(Style.BOLD,Style.ITALIC));
    }
}
33、用EnumMap代替序数索引
public enum Phase {
    SOLID,LIQUID,GAS;

    public enum Transition {
        MELT(SOLID,LIQUID),FREEZE(LIQUID,SOLID);

        public final Phase src;
        public final Phase dst;

        Transition(Phase src, Phase dst) {
            this.src = src;
            this.dst = dst;
        }

        private static final Map<Phase,Map<Phase,Transition>> m = new EnumMap<Phase, Map<Phase, Transition>>(Phase.class);

        static {
            for (Phase p : Phase.values()) {
                m.put(p,new EnumMap<Phase, Transition>(Phase.class));
            }
            for (Transition trans : Transition.values()) {
                m.get(trans.src).put(trans.dst,trans);
            }
        }

        public static Transition from (Phase src, Phase dst) {
            return m.get(src).get(dst);
        }
    }
}
34、用接口模拟可伸缩枚举
public interface Operation {

    double apply(double x, double y);
}

public enum ExtendedOperation implements Operation {

    EXP("^") {
        @Override
        public double apply(double x, double y) {
            return Math.pow(x, y);
        }
    };

    private final String symbol;

    ExtendedOperation(String symbol) {
        this.symbol = symbol;
    }

    @Override
    public String toString() {
        return symbol;
    }
}

测试

public static void main(String[] args) {
        double x = Double.parseDouble("1.0");
        double y = Double.parseDouble("2.0");

        test(Arrays.asList(ExtendedOperation.values()),x,y);
    }
    
    private static void test(Collection<? extends Operation> opSet,double x, double y) {
        for (Operation op : opSet) {
            System.out.printf("%f %s %f =%f%n", x, op, y, op.apply(x,y));
        }
    }

枚举可以继承接口实现接口方法

35、注解优先于命名模式

优先使用注解

36、坚持使用Override注解

1、标识方法是继承父类的
2、防止覆盖方法书写错误

37、用标记接口定义类型

标记接口时没有包含方法声明的接口,例如,考虑Serializable接口,通过这个接口,类表明它的实例可以被写到ObjectOutStream。

六、方法

38、检查参数有效性

非公用的方法,应使用断言检查他们的参数

39、必要时进行保护性拷贝

类具有从客户端得到或者客户端的可变组件,类就必须保护性的拷贝这些组件。

40、谨慎设计方法签名

1、谨慎的选择方法名称
保证同一包下其他名称风格一致
2、不要过于追求提供便利的方法
3、避免过长参数列表(目标时四个参数,或者更少)

- 一、把方法分解成多个方法,每个方法只需要这些参数的一个子集。
- 二、创建辅助类,用来保存参数的分组
- 三、从对象创建到方法调用都采用Builder模式

4、对于参数类型,要优先使用接口而不是类,例如传递参数使用Map而不是HashMap
5、对于Boolean,要优先使用两个元素的枚举类型

41、慎用重载

尽量不要重载,特别是参数数目相同时,会引起错误

42、慎用可变参数

在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是他们不应该被过渡滥用。

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

返回类型位数组或者集合的方法,没有理由返回null

44、为所有导出的API元素编写文档注释

接口、方法增加注释,在程序调用的时候是很方便的。
为泛型或者方法编写文档时,确保要在文档中说明所有的类型参数。
为枚举写文档时,要确保在文档中说明常量。
为注解编写问你当时,确保在文档中说明所有成员,以及类型本身。

七、通用程序设计

45、将局部变量的作用域最小化

1、最有力的方法就是在第一次使用它的地方声明。

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

比起普通的for循环,它还稍有些性能优势,因为它对数组索引的边界值只计算一次。
三种情况无法使用for-each循环:

1、过滤:如果需要遍历集合,并删除选定的元素,就需要使用显示的迭代器,以便可以调用它的remove方法
2、转换:如果需要遍历列表或者数组,并取代它部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素值
3、平行迭代:如果需要并行地遍历多个集合,就需要显示的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移
47、了解和使用类库

产生位于0和某个上界之间的随机整数,那么常见的方式就是Random,但是它存在三个缺点:

一、如果比较小的2的乘方,经过一段相当短得周期之后,它产生得随机数序列将会重复。
二、如果n不是2得城防,那么平均起来,有些数会比其他得数出现得更为频繁,如果n比较大,这个缺点就会非常明显
三、极少情况下,它会返回一个落在指定范围之外的数。
Random.nextInt(int);//无风险的方法

合理使用类库避免重复造轮子。

48、如果要精确的答案,请避免使用float和double

float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数值范围上提供较为精确的快速近似计算而精心设计的,因此float和double类型尤其不适合用于货币计算
解决方式:使用BigDecimal、int或者long进行货币计算。
使用int或者long类型,计算钱的时候可以以分欸单位进行计算而不是以元为单位
如果数值范围没有超过9位十进制数字,就可以使用int;如果不超过18位数字,就可以使用long。如果数值超过18位数字,就必须使用BigDecimal

49、基于类型优先于装箱基本类型

基本类型和装箱基本类型之间有三个主要区别:

1、基本类型只有值,而装箱基本类型则具有于他们的值不同的同一性。
2、基本类型有功能完备的值,而每个装箱类型除了它对基本类型的所有功能之外,还有个非功能值null
3、基本类型通常比装箱基本类型更节省时间和空间。
50、如果其他类型更合适,则尽量避免使用字符串
1、字符串不适合代替其他的值类型
2、字符串不适合代替枚举类型
3、字符串不适合代替聚集类型,拼接类型
51、当心字符串连接的性能

当连接的循环的数量级非常大时,String字符串拼接更消耗性能,为了获取更好的性能,可以使用StringBuilder替代String

52、通过接口引用对象

建议使用接口而不是类作为参数的类型,如果不存在接口需要使用类时,应该使用基类作为参数,而不是实现类(子类)

53、接口优先于反射机制

反射的缺点:

1、丧失了编译时类型检查的好处。
2、执行反射访问所许哟啊的代码非常笨拙和冗长
3、性能损失

反射机制是一种功能强大的机制,对于特定的复杂系统编程任务,它是非常必要的。

54、谨慎使用本地方法(native)

使用本地方法来提供性能的做法不值得提倡。

55、谨慎的进行优化
1、努力避免那些限制性能的设计决策
2、要考虑API设计决策的性能后果
56、遵守普遍接受的命名惯例

包名:通常是一个名词或者名词短语命名
接口:以"-able"或"-ible"结尾的形容词来命名
注解:名词、动词、介词和形容词
执行动作的方法:动词或者动词短语
返回boolean值的方法:往往以的单词is开头,很少用has,后面跟名词或名词短语,或者任何具有形容词功能的单词或短语
转换类型方法:通常被称为toType

八、异常

57、只针对异常的情况才使用异常

异常应该只用于异常的情况下,他们永远不应该用于正常的控制流。

58、对可恢复的情况使用受检异常,对编程错误使用运行时异常
59、避免不必要地使用受检的异常

”把受检的异常编程未受检的异常“的一种方法是,把这个抛出异常的方法分成两个方法,其中第一个方法返回Boolean,表明是否应该抛出异常,简单理解就是,尽量避免try-catch

60、优先使用标准的异常

常见异常

异常使用场合
IllegalArgumentException非null的参数值不正确
IllegalStateException对方法调用而言,对象状态不合适
NullPointerException在禁止使用null的情况下参数值为null
IndexOutOfBoundsException下标参数越界
ConcurrentModificationException在禁止并发修改的情况下,检测到对象的并发修改
UnsupportedOperationException对象不支持用户请求的方法
61、抛出与抽象对应的异常

低层次的实现抛出低层次的异常;处理低层异常的最好做法是,在调用低层方法之前确保他们会成功执行,从而避免他们抛出异常。

62、每个方法抛出的异常都要有文档

为你编写的每个方法所能抛出的每个异常建立文档。

63、在细节消息中包含捕获失败的信息

异常的细节消息应该捕获住失败

64、努力使失败保持原子性

一般而言,失败的方法调用应该使对象保持在被调用之前的状态,具有这种属性的方法被称为具有失败原子性
实现方式:

1、设计一个不可变的对象
2、可变对象,获得失败原子性最常见的办法是,在执行操作之前检查参数的有效性,这样可以使得在对象的状态被修改之前,先抛出适当的异常。
3、编写一段恢复代码
4、在对象的一份临时拷贝上执行操作,当操作完之后再用临时拷贝中的结果代替对象的内容
65.不要忽略异常

不要使用空的catch块忽略异常,正常捕获方便错误调试

九、并发

66、同步访问共享的可变数据

当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。可以使用Synchronized、volatile可以实现一种可以接受的同步形式

67、避免过度同步

为了避免死锁和数据破坏,千万不要从同步区域内部调用外来方法。简单的讲,要尽量限制同步区域内部的工作量。

68、executor和task优先于线程

timer只用一个线程来执行任务,这在面对长期运行的任务时,会影响到定时的准确性。如果timer唯一的线程抛出未被捕获的异常,timer就会停止执行。被调度的线程池executor支持多个线程并且优雅地从抛出未受检异常的任务中恢复。

69、并发工具优先于wait和notify

高级的并发工具,在使用中很少使用wait和notify

1、应该优先使用ConcurrentHashMap,而不是使用Collections.synchronizedMap或Hashtable。
2、BlockingQueue扩展了Queue接口,并添加了包括take在内的几个方法,它从队列中删除并返回了头元素,如果队列为空,就等待。大多数ExecutorService(包括ThreadPoolExecutor)都使用BlockingQueue。
3、同步器是一些线程能够等待另一个线程的对象,允许它们协助动作。常见的同步器是CountDownLatch和Semaphore,较不常用的CyclicBarrier和Exchanger。CountDownLatch是一次性的障碍,允许一个或者多个线程等待一个或者多个其他线程来做某些事情。CountDownLatch的唯一构造器带有一个int类型的参数,这个int参数是指允许所有在等待的线程被处理之前,必须在锁存器上调用countDown方法的次数。
70、线程安全性的文档化

注释中出现synchronized关键字也不足以说明,都是线程安全的
线程安全性的几种级别:

1、不可变的:这个类的实例是不可变的。所以不需要同步。包括String、Long和BigInteger
2、无条件的线程安全:这个类实例时可变的,但是这个类有足够的内部同步,所以,它的实例可以被并发使用,无需任何外部同步。例如Random和ConcurrentHashMap
3、有条件的线程安全:除了有些方法为进行安全的并发使用而需要外部同步之外,这种线程安全级别与无条件的线程安全相同。包括Collections.synchronized包装返回的集合,它们的迭代器要求外部同步。
4、非线程安全:这个类的实例是可变的。为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法调用。这样的例子包括通用的集合实现,例如ArrayList和HashMap
5、线程对立的:这个类不能安全的被多个线程并发使用,即使所有的方法调用都被外部同步包围。Java平台类库中,线程对立的类或者方法非常少。
71、慎用延迟初始化

延迟初始化是延迟到需要域的值时才将它初始化的这种行为。这种方法既适用于静态域,也适用于实例域。
当有多个线程时,延迟初始化时需要技巧的。如果两个或者多个线程共享一个延迟初始化的域,采用某种形式的同步时很重要的,否则可能造成严重的Bug
大多数的域应该正常的进行初始化,而不是延迟初始化。

72、不要依赖于线程调度器

任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的。
简而言之,不要让应用程序的正确性依赖于线程调度器。

73、避免使用线程组

除了线程、锁和监视器之外,线程系统还提供了一个基本的抽象,即线程组。线程组的初衷是作为一种隔离applet(小程序)的机制,当然是出于安全的考虑。但是它们从来没有真正履行这个承诺,它们的安全价值已经差到根本不在Java安全模型的标准工作中提及的地步。
线程组提供的功能:它们允许你同时把Thread的某些基本功能应用到一组线程上。其中有一些基本功能已经被废弃,剩下的很少使用。可以忽略

十、序列化

74、谨慎地实现Serializable接口

将一个对象编码成一个字节流称作将该对象序列化相反的处理被称作反序列化
一旦对象被序列化后,它的编码就可以从一台正在运行的虚拟机被传递到另外一台虚拟机上,或者被存储到磁盘上,供以后反序列化时用。
序列化缺点:

1、实现Serializable接口而付出的最大代价是,一旦一个类被发布,就大大降低了”改变这个类的实现”的灵活性。
2、实现Serializable的第二个代价是,它增加了出现Bug和安全漏洞的可能性
3、实现Serializable的第三个代价是,随着类发行新的版本,相关的测试负担也增加了
4、内部类不应该实现Serializable。

注意:
序列化时,一定提供一个UID,否则系统就会自动地根据这个类来调用一个复杂地运算过程,从而在运行时产生该标识号,开销大。
千万不要认为实现Serializable接口会很容易。除非一个类在用了一段时间之后就会被抛弃,否则,实现Serializable接口就是个很严肃地承诺,必须认证对待。

75、考虑使用自定义地序列化

如果一个对象的物理表示法等同于它的逻辑内容,可能就适合于使用默认的序列化形式。即便确定了默认的序列化形式是合适的,通常还必须提供一个readObject方法以保证约束关系和安全性。
当一个对象物理表示法与它的逻辑数据内容有实质性的区别时,使用默认序列化形式会有以下4个缺点:

1、它使这个类的导出API永远地束缚在该类的内部表示法上。
2、它会消耗过多的空间。
3、它会消耗过多的时间。
4、它会引起栈溢出。
76、保护性地编写readObject方法

有助于编写更加健壮地readObject方法:

1、对于对象引用域必须保持为私有地类,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别
2、对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后
3、如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口
4、无论是直接方式还是间接方式,都不要调用类中任何可以被覆盖的方法
77、对于实例控制,枚举类型优先于readResolve

如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient的。
总而言之,应该尽可能地使用枚举类型来实施实例控制的约束条件。如果做不到,同时又需要一个既可序列化又是实例受控的类,就必须提供一个readResolver方法,并确保该类的所有实例域都为基本类型,或者是transient的。

78、考虑用序列化代理代替序列化实例

为可序列化的类设计一个私有的静态嵌套类,精确地表示外围类的实例的逻辑状态。这个嵌套类被称作序列化代理。
每当发现自己必须在一个不能被客户端扩展的类上编写readObject或者writeObject方法的时候,就应该考虑使用序列化代理模式。要想稳健地将带有重要约束条件的对象序列化,这种模式可能是最容易的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值