《Effective Java》读后感java
静态工厂方法优势:程序员
静态工厂方法与构造器(构造方法)不一样的第一大优点在于,它们有名称。见名知意,突出区别。编程
静态工厂方法与构造器不一样的第二大优点在于,没必要在每次调用它们的时候都建立一个新对象。数组
静态工厂方法与构造器不一样的第三大优点在于,它们能够返回原返回类型的任何子类型的对象。缓存
静态工厂方法与构造器不一样的第四大优点在于,在建立参数化类型实例的时候,它们使代码变得更加简洁。安全
例如:Map<String,List<String>> map=newHashMap<String,List<String>>();并发
假设HashMap提供了静态工厂方法:(目前是没有该静态方法)框架
public static <K,V> HashMap<K,V> newInstance(){dom
return newHashMap<K,V>();ide
}
上面的代码就能够简写为:Map<String,List<String>> map= HashMap.newInstance();
静态工厂方法缺点:
主要缺点在于,类若是不含公有的或者受保护的构造器,就不能被子类化;
第二个缺点在于,它们与其余的静态方法实际上没有任何区别。
假如咱们的一个实体类有不少的属性值,可是这些属性值又是可选的。若是咱们遇到这样的是类,如何设计出方便的实体类呢?
一般解决办法一:重叠构造器
复制代码
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());
}
例如: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秒附近。
结论很明显:优先使用基本类型而不是装箱基本类型,要小心无心识的自动装箱。
内存泄漏:若是一个栈先是增加,而后再收缩,那么从栈中弹出来的对象将不会被当作垃圾回收,即便使用栈的程序再也不引用这些对象,它们也不会被回收。由于,栈内部维护着对这些对象的过时引用(obsolete reference)。所谓的过时引用,是指永远也不会再被解除引用。
出现内存泄漏的三种须要注意的状况:
一:类是本身管理内存;
二:缓存; 把对象放入缓存中,很容易被遗忘。
三:监听器和其余回调。
尽管Object是一个具体类,可是设计它主要是为了扩展。它的全部非final方法(equals、hashCode、toString、clone和finalize)都用明确的通用约定,由于它们被设计成要被覆盖(override)的。
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
引用java API,关于Object中hashCode方法的一段话:
返回该对象的哈希码值。支持此方法是为了提升哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
hashCode 的常规协定是:
在 Java 应用程序执行期间,在对同一对象屡次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另外一次执行,该整数无需保持一致。
若是根据equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每一个对象调用 hashCode 方法都必须生成相同的整数结果。
若是根据equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求必定生成不一样的整数结果。可是,程序员应该意识到,为不相等的对象生成不一样整数结果能够提升哈希表的性能。
对于成员(域、方法、嵌套类和嵌套接口)有四种可访问级别:下面根据可访问性递增顺序说明:
私有的(private):只有在声明该成员的顶层类内部才能够访问这个成员;
包级私有(package-private):声明该成员的包内部的任何类均可以访问这个成员。从技术上讲,它被称为“缺省访问级别”;
受保护的(protected):声明该成员的类的子类能够访问这个成员(可是有一些限制[JLS,6.6.2])。
公有的(public):在任何地方均可以访问。
为了使类称为不可变,要遵循下面五条规则:
1, 不要提供任何会修改对象状态的方法;
2, 保证类不会被扩展;
3, 使全部的域都是final的;
4, 使全部的域都称为私有的;
5, 确保对于任何可变组件的互斥访问
现有的类能够很容易被更新,以实现新的接口;
接口是定义mixin(混合类型)的理想选择;
接口容许咱们构造非层次结构的类型框架。
例如:比较器函数表明一种为元素排序的策略。
谨慎地选择方法的名称;见名知意
不要过于追求提供便利的方法;
避免过长的参数列表;
没法使用for-each循环的三种状况:
1,过滤:若是须要遍历集合,并删除选定的元素,就须要使用显式迭代器,以即可以调用它的remove方法;
2,转换:若是须要遍历列表或者数组,并取代它部分或者所有的元素值,就须要列表迭代器或者数组索引,以便设定元素的值
3,平行迭代:若是须要并行的遍历多个集合,须要显式的控制迭代器或者索引变量,以便全部迭代器或者索引变量均可以获得同步前移
以上状况,须要使用普通for循环。
精确计算,推荐使用BigDecimal(性能会相对慢一些)
字符串不适合代替其余的值类型;
字符串不适合代替枚举类型;
字符串不适合代替汇集类型;
字符串不适合代替能力表;
为了得到能够接收的性能,请使用StringBuilder或StringBuffer代替String进行字符串的拼接。
使用反射机制的缺点:
1,丧失了编译时类型检查的好处,包括异常检查;
2,执行反射访问所须要的代码很是笨拙和冗长。
2,性能损失。反射方法调用比普通方法调用慢了不少。
不要去计较效率上的一些小小的得失,在97%的状况下,不成熟的优化才是一切问题的根源。
在优化方面,咱们应该遵照两条规则:
1, 不要进行优化;
2, 仍是不要进行优化;也就是说在你没有绝对清晰的优化方案以前,请不要进行优化。
总结:不要费力编写快速的程序,应该努力编写好的程序,速度天然随之而来。
缺点是:
1, 类实现该接口以后,一旦一个类被发布,就大大下降了“改变该类的实现”的灵活性。
2, 它增长了出现bug和安全漏铜的可能性;
3,随着类发行新的版本,相关的测试负担也增长了。
Mutableperiod***。
编写更加健壮的readObject方法:
对于对象引用域必须保持为私有的类,要保护性的拷贝这些域中的每一个对象。不可变类的可变组件就属于这一类别;
对于任何约束条件,若是检查失败,则抛出一个InvalidObjectException异常。这些检查动做应该跟在全部的保护性拷贝以后。
若是整个对象图在被反序列化以后必须进行验证,就应该使用ObjectInputValidation接口
不管是直接仍是间接方式,都不用调用类中任何可被覆盖的方法
若是依赖readResolve进行实例控制,带有对象引用类型的全部实例域则都必须声明为tansient的。不然,会被Mutableperiod***。