Effective Java精髓

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_32250495/article/details/82501163

Effective Java 

第一章:创建和销毁对象

1. 使用静态工厂方法

——优点:1. 可以提供多种方式的方法签名。

                  2. 不必每次都返回一个新的实例。 

                  3. 可以返回本类的一个子类实例。

                  4. 使用静态类型推导:public static <k,v> HashMap<k,v> get();

——缺点:1. 如果类只含私有构造器将不能被子类化。

                  2. 与其他静态方法没有任何区别。无法查明如何实例化一个类。

2. 遇到多个构造器参数时要考虑使用构建器  (建造者模式,重叠构造器)

3. 用私有构造器或者枚举类型强化Singleton属性  

————使用枚举类型构建绝对单例。 参考ProfectSingleton.class

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

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

————通过静态构造初始化可重复使用对象 使用享元模式FlyWeight 注意自动装箱

6. 消除过期的对象引用 

————不再使用的引用注意设置为null而不是等待被覆盖,结果被遗忘。

7. 避免使用终结方法finalizer 

————终结方法中的异常将没有任何提示。执行时间不确定。

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

8. 覆盖equals遵守通用约定  

——自反性、对称性、传递性、一致性、非空性。 equals用来比较值相等,而不是对象实例相等。

9. 覆盖equals时总要覆盖hashCode

10. 始终要覆盖toString方法  

——Object中toString方法返回类在jvm中的名称

11. 谨慎的覆盖clone  

——深拷贝、浅拷贝。 

——浅拷贝获得一个类的新实例,复制常量和不变量。直接使用被克隆对象的引用域。

——深拷贝注意要重新new所有域引用对象,不然会调用被克隆对象的引用。

12. 考虑实现Comparable接口  

——对于值类,实现comparable接口提供内部排序功能。

第三章:类和接口

13. 使类和成员的可访问性最小

——信息隐藏,使得类的功能都内聚在一个类模块中,从而降低与其他类的耦合,最终在修改一个类的时候,其所影响的范围最小。

14. 在公有类中使用访问方法而非公有域

15. 使可变性最小 ——不可变类在实例化之后就不能再改变,类的所有信息在实例化时候就全部确定。 如String,常量类等。确保对于可变组件的互斥访问,确保安全。

16. 复合优先于继承

——继承打破了封装性。例:子类依赖超类中特定功能的实现细节,超类的实现随着发型版本的改变而改变。这样子类可能就会遭到破坏,即使代码完全没有改变。

——只有当A真正是B的子类型的时候才适合继承。在抽象过程中两个类没有确定的继承关系,推荐使用复合模式。

——继承将类的API缺陷传播到其子类中,而复合则可以设计新的API来隐藏这些缺陷。

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

——一个类可继承必须要有文档说明覆盖其方法所带来的影响。

——一个类提空适当的钩子(protected可见性)以便能够进入到它的内部工作流程中。

——编写3个子类来测试可扩展的类,在类真正发布之前。

——构造器不能调用可被覆盖的方法,因为可能被子类覆盖,超类初始化的时候,子类尚未初始化,此时调用子类方法将可能出异常。

——父类实现Cloneable和Serializable会给继承扩展带来困难。clone和readObject都不能调用可覆盖的方法,其都在子类执行之前执行,意思同上一条。

——父类实现clone和readObject一定要声明为protected。子类clone先调父类clone然后再复制域。

18. 接口优于抽象类

——现有的类可以很容易被更新(增强),通过实现新的接口。

    ——接口是mixin的理想选择。   如:给类增加一个比较的功能。

    ——接口允许构造非层次结构的类型框架。

    ——通过对导出的接口都提供一个抽象的骨架实现。骨架是为了继承实现,提供默认实现和提取公共代码的功能。

    ——抽象类的演变比接口的演变要容易。比如在抽象类中增加公共的新方法。

    ——设计公有接口要十分谨慎,因为一旦发布将不可修改。接口发布需要多个不同的实现来测试接口的缺陷。

    

19. 接口只用于定义类型

——接口不应该用来导出常量。子类可以直接(或通过接口名.)使用接口中的常量,但是不能使用非常量(必须被初始化)。

20. 类层次优于标签类

——标签类中充斥了重复代码。

21. 用函数对象表示策略    定义接口表示某种策略,通过匿名类或者,静态内部类来实现。

22. 优先考虑静态成员类 

——静态成员类是外围类的一个静态成员,它可以访问外围类的所有静态成员,包括声明为私有的。

——非静态成员类的实例都与一个外围类的实例相关联(在外部类调用内部类的构造器时候)。在非静态成员类的内部可以通过this关键字访问外部类的内容。

——如果声明为成员类,但是不要求访问外围实例就应该添加static关键字。

第四章 泛型

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

——通过泛型让类型错误在编译器就暴露出来

——java为了兼容原生类型,在实际实现泛型的时候并没有占位符,编译器已经编译成实际的类型,然后将泛型信息擦除。后来为了兼容反射在字节码的方法表中添加了描述。

——无限制的通配符类型 '?' 匹配任何类型的参数。List<?>匹配任何类型的list,但是只允许null添加到list中。

24. 消除非受检警告

——如果无法消除警告,同时可以证明引起警告的代码是类型安全的,可以用@SuppressWarnings("unchecked")来消除警告

25. 列表优先于数组

——数组是协变的:如果一个实例的类型String是Object的子类型,那么String[]也是Object[]子类型,一个String变量放入Object[]数组中将不会在编译器报错

——List<Object> 永远不能放入String类型的实例。在编译期就会出错,所以泛型不能协变。

——泛型不能创建数组。

——使用数组类型要手动在代码中进行Object和实际类型的转换,无法做到通用的方法。

26. 优先考虑泛型

——使用泛型使得类型安全问题在编译器就暴露出来,由于数组的协变性将导致在运行期才发现缺陷。

27. 优先考虑泛型方法

——public <E> Set<E> union(Set<E> s1, Set<E> s2) 编译器能在编译时检查出类型错误。

编译器通过检查方法类型参数为String类型的,则可推断出E为String。这个过程称为类型推导。调用泛型构造器必须指定类型,不具有类型推导。

<T extends Comparable<T>>> T max(List<T> list) T表示所有可以自身比较的类型,即:实现了Comparable接口的类型T。

28. 利用有限制的通配符类提升API的灵活性

——Collection<? extend E> 生产者类型 泛型可接受E的子类型

——Collection<? super E> 消费者类型  泛型可接受    E的超类型

——由于泛型是不可变的,不具有像数组的协变能力,但是可以通过有限制通配符表示某个泛型可以接受一个类型的子类型,或者超类型。

29. 优先考虑类型安全的异构容器

  ——Map<Class<?>, Object>每个键都可以有一个不同的参数化类型,Class<String>、Class<Integer>等,所有称为异构的容器,同时请求String不会返回Integer所以是类型安全的

  ——cast对象检查对象是否是目标对象,如果不是则抛出异常。

  ——List<String>.class 是一个错误的反射,而List.class正确。

  ——无限制通配符只用作键,依然可以向其中添加元素。

第五章 枚举和注解

30. 用enum代替int常量

——枚举常量所有的功能在编译期就已经确定,很难发生运行时的异常,所以常称枚举为线程安全的。

——枚举类型中可以定义构造器,抽象方法等,其可以像一个类一样运作。

——枚举使用场景:当需要一组固定的常量的时候。枚举使编程变得更加容易,可以像类一样操作功能强大,其底层实现依然是int型常量。

——策略枚举:在枚举类中定义内部枚举表示枚举策略。

31. 用实例域代替序数

32. 用EnumSet代替位域

使用Set<>集合可以传递一组枚举常量。

33. 用EnumMap代替序数索引

  EnumMap<Enum,Set<>>通过枚举常量做键,可以让程序更简洁,安全。

34. 用接口模拟可伸缩的枚举

——在枚举类型中实现接口方法,来扩展。

35. 注解优先于命名模式

使用注解标注测试方法,可以方便解决命名模式的困境(命名不规范,字符错误等)

36. 坚持使用Override注解

对要覆盖的超类方法中,使用Override注解,防止进行重载

37. 用标记接口定义类型

——没有包含方法声明的接口,只是指明一个类实现了具有某种属性的接口。

——标记接口定义的类型是由被标记类的示例实现的;标记注解没有定义这样的类型。

——标记接口可以更加精确的进行锁定,注解要进行扫描。

——标记注解可以通过默认的方式添加一个或者多个注解类型元素。

第六章 方法

38. 检查参数的有效性

——对参数进行限制,并撰写详细的文档说明。

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

——对于可以随意改变值的类,在参数校验之前进行保护性拷贝。

40. 谨慎设计方法签名

——让类方便的使用,学习,测试和维护

——1. 谨慎的选择方法签名

——2. 不要过于追求提供便利的方法

——3. 避免过长的参数列表

——builder模式了解下

41. 慎用重载

——外观类型,运行时类型。方法重载,方法覆盖的单分派,多分派,很容易造成重载方法调用错误。

42. 慎用可变参数

——(int... args)创建了一个数组,大小为传入的参数数量,运行时确定。

——可变参数将导致一次数组的分配和初始化

——参数个数确定,可以通过多方法重载来替代。

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

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

第七章 通用程序设计

45. 将局部变量的作用于最小化

——在第一次使用他的时候声明

——每个局部变量的声明都应该包含一个初始化表达式

——将方法操作拆散,使局部变量最小

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

——需要remove(),转换和控制索引变量的时候,无法使用foreach。

47. 了解和使用类库

——底层标准类库经过大量测试,不必浪费时间在这底层开发工作上。

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

——使用float和double,总是会出现许多小数点导致计算不精确,从而导致计算错误。

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

——进行表达式运算,装箱类型会自动拆箱。基本类型赋值给装箱类型,会自动装箱。

50. 如果其他类型更合适,则尽量避免使用字符串

——字符串不适合代替其他的值类型

——字符串不适合代替枚举类型

——字符串不适合代替聚集类型

——字符串不适合代替能力表

——如果可以使用更加合适的数据类型,或者编写更加合适的数据类型,就应该避免使用字符串来表示对象。

51. 当心字符串连接的性能

——慎用'+'

52. 通过接口引用对象

——除非在调用构造器的时候,否则应该用接口引用对象。当使用接口引用的时候,程序将更加灵活,更换实现了只需要替换实现类即可。

——如果没有合适的接口存在,才可以用类来引用对象。

——如果可以通过基类引用对象,则可以使用基类对象。

53. 接口优先于反射机制

——反射散失编译时类型检查的好处

——执行反射访问所需要的代码非常笨拙和冗长

——性能损失    ——通常普通应用程序在运行时不应该以反射方式访问对象。

建议:如果非要使用反射,仅仅在创建类的时候使用反射,而在真正调用的时候,使用编译时已知的接口或者超类。

54. 谨慎地使用本地方法

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

55. 谨慎地进行优化

——不要去计较效率上的一些小小得失,在97%的情况下,不成熟的优化才是一切问题的根源。

——在没有遇到性能瓶颈,没有绝对清晰的优化方案之前,请不要进行优化

——不要因为性能就牺牲合理的结构,要努力编写好的程序而不是快的程序。

——努力避免那些限制性能的设计决策。

——努力避免那些限制性能的设计决策

——要考虑API设计决策的性能后果

——为了获得好的性能而对API进行包装,这是一种非常不好的想法

——在每次试图做优化之前后优化之后,要对性能进行测量

——努力设计好的程序,而不是快的程序。

56. 遵守普遍接受的命名惯例

第八章 异常

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

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

——异常模式的性能非常慢,而且可能掩盖系统中其他不相关部分的bug

58. 对可恢复的情况使用受检异常,对编程错误使用运行时异常

——通过在方法上抛出受检异常,强迫调用者必须捕获。 受检异常在调用者在编译期就必须捕获,否则不能通过编译。

——未受检的异常包括:运行时异常和错误,他们未被捕获将终止程序

59. 避免不必要的使用受检的异常

60. 优先使用标准的异常

——IllegalArgumentException IllegalStateException NullPointerException IndexOutofBoundsException ConcurrentModificationException UnsupportedOperationException

61. 抛出与抽象相应的异常

——高层实现,应该捕获底层的异常,同时抛出可以按照高层抽象进行解释的异常。这种被称为异常转义

——最好在调用底层方法之前进行参数校验,保证底层方法执行成功,从而防止异常发生。

——用某种全局的方式将异常记录下来。这样有助于管理员调查问题,同时又将客户端代码和最终用户问题隔离开。

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

——始终要单独地声明受检的异常,并且利用@throws标记,准确地记录下抛出去每个异常的条件。

——在文档中记录方法可能抛出的未受检异常

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

——异常的细节信息应该包含所有对该异常有贡献的参数和域的值

64. 努力使失败保持原子性(一个对象调用方法失败后,对象中各可变域的值未被修改)

——失败的方法调用应该使对象保持在被调用之前的状态。例如不变量,失败后才改变状态等,编写恢复代码回滚,临时性拷贝一份。

65. 不要忽略异常

第九章 并发

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

67. 避免过度同步

——一个同步区域内,尽量不要调用外部方法,如果外部方法也有同步,则容易产生死锁。

68. executor和task优于线程

——使用精细化的框架可以完成大多数需求。

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

——优先使用concurrent包

——优先使用notifyAll()

70. 线程安全性的文档化

71. 慎用延迟初始化

——两次空判定是最好的初始化方法。(双重检查模式)

——对于可以重复实例化的域可以接受单检查模式

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

——任何依赖线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的。

——Thread.yield毫无意义

——调整线程优先级将使得程序不可移植

73. 避免使用线程组

第十章 序列化

74. 谨慎地实现Serializable接口

——一旦一个类被发布,就大大降低了改变这个类的实现的灵活性。(任何私有域都成了导出API的一部分,破坏了信息隐藏)

——增加了出现bug和安全漏洞的可能性,反序列化构建器可以改变对象的内部信息

——增加测试负担

——为了继承而设计的类,应该尽可能少去实现序列化接口。

75. 考虑使用自定义的序列化形式

——当一个对象的物理表示法与它的逻辑数据内容有实质性的区别时,使用自定义的序列化形式。

——当使用一个自定义的序列化形式,所有实例域应标记为transient,表示该域不会被序列化到流中,该变量在序列化后无法访问。

——静态变量无法被序列化。

——提供一个序列化版本。

76. 保护性的编写readObject方法

——对于可变域进行保护性拷贝,防止被篡改(伪造的字节码),从而导致安全问题。

——readObject相当于一个公用的构造器。

77. 对于实例控制,枚举类型优先于readResolve

——ReadResolve防止反序列化出现多个实例,但是可能出现安全性问题。

——使用readResolve应尽量保证所有域都是基本变量,或者是瞬时的。

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

单例:

package EffectiveJava;

// 详解各种单例模式创建方法。

/**
 * 枚举类型其实都是静态不变量实现的,虚拟机加载的时候会确保静态量被正确的同步加载。
 * 序列化的时候只将 instance 这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型。因此反序列化后的实例也会和之前被序列化的对象实例相同。
 * @author jane
 *
 */
public enum ProfectSingleton {
	instance; // 绝对获取一个实例。多线程安全。
	private int code;
	private ProfectSingleton() {code=10;}
	public int getCode() {return code;}
}


/**
 * 饿汉式。 类加载的时候就被实例化。多线程亲和。无法确定序列化过程唯一。
 * @author jane
 *
 */
class Singleton2{
	private final static Singleton2 singleton=new Singleton2();
	
	private Singleton2() {}
	
	public Singleton2 getSingleton() {
		return singleton;
	}
}

/**
 * 饱汉式,也称懒汉式。 当需要的时候才会被实例化。无法确保多线程安全。
 * @author jane
 *
 */
class Singleton1{  // 包可见
	private  static Singleton1 singleton=null;
	
	private Singleton1() {}
	
	public Singleton1 getSingleton() {
		if(singleton==null) {
			singleton=new Singleton1();
		}
		return singleton;
	}
}

/**
 * 多线程安全懒汉式。
 * @author jane
 *
 */
class Singleton3{
	private static Singleton3 singleton=null;
	
	private Singleton3() {}
	
	public Singleton3 getSingleton() {
		if(singleton==null) {
			synchronized(Singleton3.class) {
				if(singleton==null)
					singleton=new Singleton3();
			}
		}
		return singleton;
	}
}



内部类:

package EffectiveJava;

public class TestEffectiveJava implements TestEffectiveInterface{
    
	public int field1=0;
	private static String field2="hello";
	private NestedEffective2 effective2=new NestedEffective2();
	
	@Override
	public void sayHello() {
		// TODO Auto-generated method stub
		System.out.println(this.effective2.field5);
		
		NestedEffective.sayHello2();
		effective2.sayHello3();
	}
	
	// 静态内部类可以直接访问外部类的静态成员 私有的也可以。
	public static class NestedEffective{
		public boolean field3=true;
		private boolean field4=false;
		public static void sayHello2() {
			System.out.println(field2);
		}
		
	}
	// 非静态内部类可以直接访问外部类的成员。通过一个修饰过的this引用
	public class NestedEffective2{
		public boolean field5=false;
		public boolean field6=false;
		
		public void sayHello3() {
			System.out.println(field2);
		}
	}

}

猜你喜欢

转载自blog.csdn.net/qq_32250495/article/details/82501163