首页 > 代码库 > Effective Java读后感
Effective Java读后感
《Effective Java》读后感
1 创建和销毁对象
1.1 考虑用静态工厂方法代替构造器
静态工厂方法优点:
静态工厂方法与构造器(构造方法)不同的第一大优势在于,它们有名称。见名知意,突出区别。
静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象。
静态工厂方法与构造器不同的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。
静态工厂方法与构造器不同的第四大优势在于,在创建参数化类型实例的时候,它们使代码变得更加简洁。
例如:Map<String,List<String>> map=newHashMap<String,List<String>>();
假设HashMap提供了静态工厂方法:(目前是没有该静态方法)
public static <K,V> HashMap<K,V> newInstance(){
return newHashMap<K,V>();
}
上面的代码就可以简写为:Map<String,List<String>> map= HashMap.newInstance();
静态工厂方法缺点:
主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化;
第二个缺点在于,它们与其他的静态方法实际上没有任何区别。
1.2 遇到多个构造器参数时要考虑用构建器
假如我们的一个实体类有很多的属性值,但是这些属性值又是可选的。如果我们遇到这样的是类,如何设计出方便的实体类呢?
通常解决办法一:重叠构造器
复制代码
public class User {
private String id; // id(必填)
private String name; // 用户名(必填)
private String email; // 邮箱(可选)
private int age; // 年龄(可选)
private String phoneNumber; // 电话(可选)
private String address; // 地址(可选)
public User(String id, String name) {
this(id, name, "qq.com", 0, "120", "广州");
}
public User(String id, String name, String email) {
this(id, name, email, 0, "120", "广州");
}
public User(String id, String name, String email, int age) {
this(id, name, email, age, "120", "广州");
}
public User(String id, String name, String email, int age, StringphoneNumber) {
this(id, name, email, age, phoneNumber, "广州");
}
public User(String id, String name, String email, int age, StringphoneNumber, String address) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
this.phoneNumber = phoneNumber;
this.address = address;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
}
注:许多你不想设置的参数,但是还不得不为他们传递值
通常解决办法二: JavaBean模式
调用一个无参构造器来创造对象,然后调用setter方法来设置每个必须的参数,以及每个相关的可选参数
复制代码
public class User {
private String id; // id(必填)
private String name; // 用户名(必填)
private String email; // 邮箱(可选)
private int age; // 年龄(可选)
private String phoneNumber; // 电话(可选)
private String address; // 地址(可选)publicUser() {
super();
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
public void setAge(int age) {
this.age = age;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void setAddress(String address) {
this.address = address;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ",email=" + email + ", age=" + age + ", phoneNumber="
+ phoneNumber + ",address=" + address + "]";
}
}
注:JavaBeans模式自身有着很严重的缺点。因为构造过程被分到几个调用中,在构造过程中JavaBean可能处于非一致的状态。JavaBeans模式阻止了把类做成不可变的可能,这就需要确保他的线程安全。
解决办法三:构建器
复制代码
public class User {
private String id; // id(必填)
private String name; // 用户名(必填)
private String email; // 邮箱(可选)
private int age; // 年龄(可选)
private String phoneNumber; //电话(可选)
private String address; //地址(可选)
public static class Builder{
private String id; // id(必填)
private String name; // 用户名(必填)
private String email; // 邮箱(可选)
private int age; // 年龄(可选)
private String phoneNumber; //电话(可选)
private String address; //地址(可选)
public Builder(String id, String name) {
super();
this.id = id;
this.name = name;
}
public Builder email(String email){
this.email = email;
return this;
}
public Builder age(int age){
this.age = age;
return this;
}
public Builder phoneNumber(String phoneNumber){
this.phoneNumber = phoneNumber;
return this;
}
public Builder address(String address){
this.address = address;
return this;
}
public User builder(){
return new User(this);
}
}
private User(Builder builder){
this.id = builder.id;
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phoneNumber = builder.phoneNumber;
this.address = builder.address;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ",email=" + email + ", age=" + age + ", phoneNumber="
+ phoneNumber + ",address=" + address + "]";
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public int getAge() {
return age;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getAddress() {
return address;
}
}
注:不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。
测试代码
public static void main( String[] args )
{
User user = new User.Builder(UUID.randomUUID().toString(),"parry").address("广州").builder();
System.out.println(user.toString());
}
1.3 用私有构造器或者枚举类型强化Singleton属性
1.4 通过私有构造器强化不可实例化的能力
1.5 避免创建不必要的对象
例如:Strings=new String(“abc”); // Don’t do this
该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作全都是不必要的。传递给String构造器的参数(”abc”),本身就是一个String实例,功能方面等同于构造器创建的所有对象。如果这个方法在一个循环中,就会创建成千上万的不必要的String实例。
正确的写法:String s=”abc”;
在Java1.5版本的新特性中,有一种创建多与对象的新方法:自动装箱!
例如:
Long sum=0L;
for(long i=0; i<Integer.MAX_VALUE; i++){
sum+= i;
}
system.out.println(sum);
执行以上代码,耗时大约43秒;
这段程序算出的答案是正确的,但是比实际情况要更慢一些,只因为变量sum使用的Long类型,而不是long,这意味着程序构造了大约2的31次方个多余的Long实例。如果将Long改为基本类型long。程序运行时长为6.8秒附近。
结论很明显:优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱。
1.6 消除过期的对象引用
内存泄漏:如果一个栈先是增长,然后再收缩,那么从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。因为,栈内部维护着对这些对象的过期引用(obsolete reference)。所谓的过期引用,是指永远也不会再被解除引用。
出现内存泄漏的三种需要注意的情况:
一:类是自己管理内存;
二:缓存; 把对象放入缓存中,很容易被遗忘。
三:监听器和其他回调。
1.7 避免使用终结方法
2 对于所有对象都通用的方法
尽管Object是一个具体类,但是设计它主要是为了扩展。它的所有非final方法(equals、hashCode、toString、clone和finalize)都用明确的通用约定,因为它们被设计成要被覆盖(override)的。
2.1 覆盖equals时请遵守通用约定
equals方法实现了等价关系:
自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true
对程序(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true
传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,且y.equals(z)返回true,那么x.equals(z)必须返回true
一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作再对象中所有的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致的返回false
非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false
覆盖equals时总要覆盖hashCode
引用java API,关于Object中hashCode方法的一段话:
返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是:
在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
如果根据equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
始终要覆盖toString
谨慎覆盖clone
考虑实现Comparable接口
3 类和接口
3.1 使类和成员的可访问性最小化
对于成员(域、方法、嵌套类和嵌套接口)有四种可访问级别:下面根据可访问性递增顺序说明:
私有的(private):只有在声明该成员的顶层类内部才可以访问这个成员;
包级私有(package-private):声明该成员的包内部的任何类都可以访问这个成员。从技术上讲,它被称为“缺省访问级别”;
受保护的(protected):声明该成员的类的子类可以访问这个成员(但是有一些限制[JLS,6.6.2])。
公有的(public):在任何地方都可以访问。
在公有类中使用访问方法而非公有域
使可变性最小化
为了使类称为不可变,要遵循下面五条规则:
1, 不要提供任何会修改对象状态的方法;
2, 保证类不会被扩展;
3, 使所有的域都是final的;
4, 使所有的域都称为私有的;
5, 确保对于任何可变组件的互斥访问
3.4 复合优先于继承
3.5 要么为继承而设计,提供说明文档,要么就禁止继承
3.6 接口优于抽象类
现有的类可以很容易被更新,以实现新的接口;
接口是定义mixin(混合类型)的理想选择;
接口允许我们构造非层次结构的类型框架。
3.7 接口只用于定义类型
3.8 类层次优于标签类
3.9 用函数对象表示策略
例如:比较器函数代表一种为元素排序的策略。
3.10 优先考虑静态成员类
4 泛型
4.1 请不要在新代码中使用原生态类型
4.2 消除非受检警告
4.3 列表优先于数组
4.4 优先考虑泛型和泛型方法
4.5 利用有限制通配符来提升API的灵活性
4.6 优先考虑类型安全的异构容器
5 枚举和注解
5.1 用enum代替int常量
5.2 用实例域代替序数
5.3 用EnumSet代替位域
5.4 用EnumMap代替序数索引
5.5 注解优先于命名模式
5.6 用标记接口定义类型
6 方法
6.1 检查参数的有效性
6.2 必要时进行保护性拷贝
6.3 谨慎设计方法签名
谨慎地选择方法的名称;见名知意
不要过于追求提供便利的方法;
避免过长的参数列表;
6.4 慎用重载
6.5 慎用可变参数
6.6 返回零长度的数组或者集合,而不是null
6.7 为所有导出的API元素编写文档注释
7 通用程序设计
7.1 将局部变量的作用域最小化
7.2 for-each循环优先于for循环
无法使用for-each循环的三种情况:
1,过滤:如果需要遍历集合,并删除选定的元素,就需要使用显式迭代器,以便可以调用它的remove方法;
2,转换:如果需要遍历列表或者数组,并取代它部分或者全部的元素值,就需要列表迭代器或者数组索引,以便设定元素的值
3,平行迭代:如果需要并行的遍历多个集合,需要显式的控制迭代器或者索引变量,以便所有迭代器或者索引变量都可以得到同步前移
以上情况,需要使用普通for循环。
7.3 了解和使用类库
7.4 精确的答案,避免使用float和double
精确计算,推荐使用BigDecimal(性能会相对慢一些)
7.5 基本类型优先于装箱基本类型
7.6 如果使用其他类型更合适,避免使用string类型
字符串不适合代替其他的值类型;
字符串不适合代替枚举类型;
字符串不适合代替聚集类型;
字符串不适合代替能力表;
7.7 当心字符串连接的性能
为了获得可以接收的性能,请使用StringBuilder或StringBuffer代替String进行字符串的拼接。
7.8 通过接口引用对象
7.9 接口优先于反射机制
使用反射机制的缺点:
1,丧失了编译时类型检查的好处,包括异常检查;
2,执行反射访问所需要的代码非常笨拙和冗长。
2,性能损失。反射方法调用比普通方法调用慢了很多。
7.10 谨慎的调用本地方法
7.11 谨慎的进行优化
不要去计较效率上的一些小小的得失,在97%的情况下,不成熟的优化才是一切问题的根源。
在优化方面,我们应该遵守两条规则:
1, 不要进行优化;
2, 还是不要进行优化;也就是说在你没有绝对清晰的优化方案之前,请不要进行优化。
总结:不要费力编写快速的程序,应该努力编写好的程序,速度自然随之而来。
7.12 遵守普遍接受的命名惯例
8 异常
8.1 只针对异常的情况才使用异常
8.2 对可恢复的情况使用受检异常,对编程错误使用运行时异常
8.3 避免不必要的使用受检异常
8.4 优先使用标准的异常
8.5 抛出与抽象相对应的异常
8.6 每个方法抛出的异常都要有文档或注释
8.7 在细节消息中包含能捕获失败的信息
8.8 努力让失败保持原子性
8.9 不要忽略异常
9 并发
9.1 同步访问共享的可变数据
9.2 避免过度同步
9.3 executor和task优先于线程
9.4 并发工具优先于wait和notify
9.5 线程安全性的文档化
9.6 慎用延迟初始化
9.7 不要依赖线程调度器
9.8 避免使用线程组
10 序列化
10.1 谨慎实现Serializable接口
缺点是:
1, 类实现该接口之后,一旦一个类被发布,就大大降低了“改变该类的实现”的灵活性。
2, 它增加了出现bug和安全漏铜的可能性;
3,随着类发行新的版本,相关的测试负担也增加了。
10.2 考虑使用自定义的序列化形式
10.3 保护性地编写readObject方法
Mutableperiod攻击。
编写更加健壮的readObject方法:
对于对象引用域必须保持为私有的类,要保护性的拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别;
对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后。
如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口
无论是直接还是间接方式,都不用调用类中任何可被覆盖的方法
对于实例控制,枚举类型优先于readResolve
如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域则都必须声明为tansient的。否则,会被Mutableperiod攻击。
10.5 考虑用序列化代理代替序列化实例
本文出自 “IT技术解决方案” 博客,请务必保留此出处http://wyait.blog.51cto.com/12674066/1923969
Effective Java读后感