Effective Java

来源:互联网 发布:东莞广电网络多少兆 编辑:程序博客网 时间:2024/06/07 02:05
泛型


如果无法消除警告,同时可以证明引起警告的代码是类型安全的,可以用一个@SuppressWarnings("unchecked")
注解来禁止这条警告。
SuppressWarnings注解可以用在任何粒度的级别中,从单独的局部变量到整个类都可以。应该始终在尽可能小的范围
中使用SuppressWarnings注解。永远不要在整个类上使用SuppressWarnings,这么做可能会掩盖了重要的警告。


总而言之,非受检警告很重要,不要忽略它们。每一条警告都表示可能在运行时抛出ClassCastException异常。
如果无法消除非受检警告,同时可以证明引起警告的代码是类型安全的,就可以在尽可能小的范围中,

用@SuppressWarnings("unchecked")注解禁止该警告。要用注释把禁止该警告的原因记录下来。


创建和销毁对象


考虑用静态工厂方法代替构造器
静态工厂方法与构造器的第一大优势在于,它们有名称
静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象
静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象


单元素的枚举类型已经成为实现Singleton的最佳方法
要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱


通过创建附加的对象,提升程序的清晰性、简洁性和功能性,这通常是件好事。
反之,通过维护自己的对象池来避免创建对象并不是一种好的做法,除非池中的对象是
非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。建立数据库连接的代价
是非常昂贵的,因此重用这些对象非常有意义。
但是,一般而言,维护自己的对象池必定会把代码弄得很乱,同时增加内存占用,并且还会损害性能。
现代的JVM实现具有高度优化的垃圾回收器,其性能很容易就会超过轻量级对象池的性能。

工具类通常要求客户端要用类名来修饰这些常量名。如果大量利用工具类导出的常量,可以通过利用静态导入
static import 机制,避免用类名来修饰常量名
import static com.effectivejava.science.PhysicalConstants.*;
接口应该只被用来定义类型,它们不应该被用来导出常量。


优先考虑静态成员类
嵌套类是指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为它的外围类提供服务。如果嵌套类将来
可能会用于其他的某个环境中,它就应该是顶层类。嵌套类有四种:静态成员类,非静态成员类,
匿名类和局部类。
静态成员类是最简单的一种嵌套类。最好把它看作是普通的类,只是碰巧被声明在另一个类的内部而已,
它可以访问外围类的所有成员,包括那些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的
静态成员一样,也遵守同样的可访问性规则。如果它被声明为私有的,它就只能在外围类的内部才可以被访问。
如果嵌套类的实例可以在它外围类的实例之外独立存在,这个嵌套类就必须是静态成员类,在没有外围实例的
情况下,要想创建非静态成员类的实例是不可能的。


私有的静态成员类的一种常见用法是用来代表外围类所代表的对象的组件。例如,考虑一个Map实例,它把键
和值关联起来。许多Map实现的内部都有一个Entry对象,对应于Map中的每个键-值对。虽然每个entry都与一个Map
关联,但是entry上的方法并不需要访问该Map。因此,使用非静态成员类来表示entry是很浪费的:私有的静态成员类
是最佳的选择。如果不小心漏掉了entry声明中的static修饰符,该Map仍然可以工作,但是每个entry中将会包含一个
指向该Map的引用,这样就浪费了空间和时间。


并发
虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚刚被写入的值
问题在于,增量操作符(++)不是原子的。它在nextSerialNumber域中执行两项操作:首先它读取值,然后写回一个
新值,相当于原来的值再加上1。
private static final AtomicLong nextSeriaNum = new AtomicLong();
//AtomicLong 确保了操作的原子性
public static long generateSerialNumber(){
return nextSerialNum.getAndIncrement();
}


避免本条目中所讨论到的问题的最佳办法是不共享可变的数据。要么共享不可变的数据,要么压根不共享。换句话说,
将可变数据限制在单个线程中。
当多个线程共享可变数据的时候,每个读或者写数据的线程都必须执行同步。如果没有同步,就无法保证一个线程所做
的修改可以被另一个线程获知。如果只需要线程之间的交互通信,而不需要互斥,volatile修饰符就是一种可以接受的
同步形式,但要正确地使用它可能需要一些技巧。


executor和task优先于线程


java.util.concurrent.Executors类包含了静态工厂,能为你提供所需的大多数executor.然而,如果
你想来点特别的,可以直接使用ThreadPoolExecutor类,这个类允许你控制线程池操作的几乎每个方面。
在大负载的产品服务中,最好使用Executors.newFixedThreadPool,它为你提供了一个包含固定
线程数目的线程池,或者为了最大限度地控制它,就直接使用ThreadPoolExecutor类


你不仅应该尽量不要编写自己的工作队列,而且还应该尽量不直接使用线程。现在关键的抽象不再
是Thread了,它以前可是既充当工作单元,又是执行机制。现在工作单元和执行机制是分开的。
现在关键的抽象是工作单元,称作任务(task)。任务有两种:Runnable及其近亲Callable(它与
Runnable类似,但它会返回值)。执行任务的通用机制是executor service。


Executor Framework 也有一个可以代替java.util.Timer的东西,即ScheduledThreadPoolExecutor。
虽然timer使用起来更加容易,但是被调度的线程池executor更加灵活,timer只用一个线程来执行任务,
这在面对长期运行的任务时,会影响到定时的准确性。如果timer唯一的线程跑出未被捕获的异常,timer
就会停止执行。被调度的线程池executor支持多个线程,并且优雅地从抛出未受检异常的任务中恢复。


并发工具优先于wait和notify


自从java1.5发行版本开始,java平台就提供了更高级的并发工具,它们可以完成以前必须在wait和notify上
手写代码来完成的各项工作。既然正确地使用wait和notify比较困难,就应该用更高级的并发工具来代替。
java.uti.concurrent中更高级的工具分为三类:Executor Framework,并发集合(Concurrent Collection)
以及同步器(Synchronizer)


并发集合为标准的集合接口(如List、Queue和Map)提供了高性能的并发实现。为了提供高并发性,这些实现
在内部自己管理同步。因此,并发集合中不可能排除并发活动,将它锁定没有什么作用,只会使程序的速度变慢。
应该优先使用ConcurrentHashMap,而不是使用Collections.synchronizedMap或者HashTable。只要用并发Map
替换老式的同步Map,就可以极大地提升并发应用程序的性能。应该优先使用并发集合,而不是使用外部同步的
集合。


同步器(Synchronizer)是一些使线程能够等待另一个线程的对象,允许它们协调动作。最常用的同步器是
CountDownLatch和Semaphore。较不常用的是CyclicBarrier和Exchanger。


不可变的:这个类的实例是不变的。所以,不需要外部的同步。这样的例子包括String、Long、BigInteger


无条件的线程安全:这个类的实例是可变的,但是这个类有着足够的内部同步,所以,它的实例可以被并发
使用,无需任何外部同步。其例子包括Random和ConcurrentHashMap。


有条件的线程安全:除了有些方法为进行安全的并发使用而需要外部同步之外,这种线程安全级别与无条件的线程
安全相同。这样的例子包括Collections.synchronized包装返回的集合,它们的迭代器(iterator)要求外部同步


非线程安全:这个类的实例是可变的。为了并发地使用它们,客户必须利用自己选择的外部同步包围每个方法调用,
这样的例子包括通用的集合实现,例如ArrayList和HashMap


序列化
将一个对象编码成一个字节流,称作对该对象序列化;相反的处理过程被称作反序列化。
一旦对象被序列化后,它的编码就可以从一台正在运行的虚拟机传递到另一台虚拟机上,或者
被存储到磁盘上,供以后反序列化时用。序列化技术为远程通信提供了标准的线路级对象表示法,
也为JavaBeans组件结构提供了标准的持久化数据格式。


序列化会使类的演变收到限制,这种限制的一个例子与流的唯一标识符有关,通常它被称为序列版本
UID。每个可序列化的类都有一个唯一的标识符与它相关联。


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


在为了继承而设计的类中,真正实现了Serializeble接口的有Throwable类、Component和HttpServlet
抽象类。因为Throwable类实现了Serializable接口,所以RMI的异常可以从服务器端传到客户端。
Component实现了Serializable接口,因此GUI可以被发送、保存和恢复。HttpServlet实现了
Serializable接口,因此会话状态可以被缓存。