一.静态工厂方法来取代构造器
例子来自Boolean这个类,这个valueOf方法返回了一个Boolean实例,但是它并不是Boolean的构造方法,这样做有几个好处:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
- 有名称,可以自己定义
- 不用每次都新建一个对象,比如上面这个方法就不是每次都新建一个,是在类的成员变量“缓存”了两个:
- 更灵活,可以返回原本类型的任意一个子类对象,如果用构造器只能返回原本类型对象
- 每次调用,返回对象都可以变化,根据不同参数返回不同对象,而不是像构造器一样,每次调都返回一样的对象,Java.util.EnumSet就是一个例子
- 方法返回的对象所属的类,在编写构造方法的类时,可以不存在,这就是一种服务提供者
框架,参考Java.util.ServiceLoader
看到这发现平时也经常用啊,比如Collections.emptyList()这种
二.建造者模式来取代一般的构造器,应对参数过多的问题
问题:
一般的构造器,可能会遇到有很多参数的情况,比如下面这种,很多参数我不想要,但是必须得set,就很不方便
解决:采用建造者Builder模式
它不直接构造对象,而是先得到一个Builder对象,然后在这个对象上调用类似于Setter的方法
结果是这样的,是一个流式调用的API
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();
其实现是,得有一个静态内部类叫做Builder
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
//1.静态内部类Builder
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
//2.Builder的这些set方法,返回的对象得是Builder,这样才能穿起来
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 Builder sodium(int val)
{ sodium = val; return this; }
public Builder carbohydrate(int val)
{ carbohydrate = val; return this; }
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
//3.还得有一个返回目标对象的构造方法,参数是Builder
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
三.用私有构造器和枚举强化类的单例属性(Singleton)
单例的意思就是只被实例化一次,通常那些无状态的类需要是单例的,因为创建多个对象没有必要!
第一种做法:静态成员是Final类型的
// Singleton with public final field (Page 17)
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
第二种做法:静态工厂方法
跟上面的几乎一样,就是多了个getInstance方法
// Singleton with static factory (Page 17)
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.getInstance();
elvis.leaveTheBuilding();
}
}
第三种做法:枚举
// Enum singleton - the preferred approach (Page 18)
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
四.用私有构造器和枚举强化类的单例属性(Singleton)
有的类不需要构造器,只需要用它的静态方法和静态变量,比如Collections,Arrays这种工具类。这种类不需要被实例化,但是如果不写的话,编译器会自动提供一个公有的default constructor,所以需要写一个私有的构造器
//java util包的Collections就是这样写的
public class Collections {
// Suppresses default constructor, ensuring non-instantiability.
private Collections() {
}
xxxxxxxx
}
五.依赖注入
如果一个类依赖了底层资源,需要通过依赖注入的方式来编写类,也就是说构造器,需要有一个参数,根据不同参数set不同的值,就是这么简单。
六.避免重复创建对象
比如多用静态工厂方法Boolean.valueOf而不是每次new 一个boolean() ,但是有时候非常隐蔽,你不看底层代码看不出来它有没有创建对象,比如书里给了一个正则匹配的例子:
第一种做法,这种不好,因为String.matches会在里面创建Pattern实例
public class RomanNumerals {
// Performance can be greatly improved! (Page 22)
static boolean isRomanNumeralSlow(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
第二种做法好,它把Pattern编译成final的了
// Reusing expensive object for improved performance (Page 23)
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeralFast(String s) {
return ROMAN.matcher(s).matches();
}
还有就是类似于Long这种包装类,会自动装箱,所以要多用基本类型。
七.消除过期的对象引用
- 自己管理内存的类,比如Stack栈
- 缓存可能有内存泄漏
- 监听器和回调
总的来说 看的不是特别明白
八.避免使用终结方法和清除方法
终结方法就是finalize() 清除方法我也没用过。。。
九.关闭资源要用try-with-resources
这样更简洁,而且异常信息更有价值,这个我也没怎么用过,前提条件是资源类必须实现AutoCloseable接口,比如Stream
十.equals方法覆盖时需要注意
一致性、自反性、对称性、传递性等性质,比较麻烦一般也不会有问题,文章给了几个建议:
- 先用==判断参数是否为对象的引用,能节省性能
- 用instanceof来判断类的类型是否和要比较的一致,如果不是直接返回false.
还有警告⚠️
- 方法的参数必须是Object!
- 覆盖equals时一定要覆盖hashcode
- 不要让equals过于智能(复杂)
十一.equals覆盖,就得覆盖hashcode
不然hashMap和hashSet不好使了,很可能让很多对象都 映射到同一个桶中,这样hashmap就退化变成链表了
十二.始终覆盖toString()
这个一般人都知道吧 跳过了
十三.谨慎的覆盖clone
平时不用,跳过了
十四.Comparable接口
public interface Comparable<T> {
public int compareTo(T o);
}
compareTo方法的解释:
将当前对象和目标对象进行比较,如果当对象小于目标对象,返回负整数;如果二者相等返回0,如果对象大于目标对象,返回正整数,如果因为类型导致无法比较,抛出异常
想要先比较某个属性,再比较某个属性,java8有新的API
// Comparable with comparator construction methods (page 70)
private static final Comparator<PhoneNumber> COMPARATOR =
comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
总结:每个对排序敏感的类,都需要关注Comparable接口,比较的时候,不要使用<和>,应该在装箱基本类型使用静态方法比如Integer.compare,或者在Comparator使用比较器构造方法
十五.降低程序元素的可访问性
jdk1.9提供了新的隐式访问控制,模块可以通过模块声明导出一部分包来控制访问。
其他的就是private那些,一般也没人闲得没事攻击这个⑧。。。
十八.复合优先于继承
文章中举了一个继承HashSet的类,表示继承这个操作,需要了解原本类的具体实现,否则有可能造成不正确的结果,导致得到的子类非常脆弱。
- 复合:就是新的类其中包括现有类的一个实例,新的类自己的方法调用现有类的方法,这被称为转发,这种方式十分稳固
// Reusable forwarding class (Page 90) 这是新类
public class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) { this.s = s; }
public void clear() { s.clear(); }
public boolean contains(Object o) { return s.contains(o); }
public boolean isEmpty() { return s.isEmpty(); }
public int size() { return s.size(); }
public Iterator<E> iterator() { return s.iterator(); }
public boolean add(E e) { return s.add(e); }
// Wrapper class - uses composition in place of inheritance (Page 90) 封装的类
public class InstrumentedSet<E> extends ForwardingSet<E> {
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
@Override public boolean add(E e) { //用的时候只要转发就行了,实际调用的是forwadingset的方法,实际上是set的方法
addCount++;
return super.add(e);
}
@Override public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
}
同时,因为把每一个实例都包装起来了,这个InstrumentedSet类被称为包装类(wrapperclass)。 这也正是 Decorator(修饰者)模式的应用。
只有当B是A的子类的时候,就是说B is a A,这时候才能用继承
反面例子:stack栈并不是vector,但是他继承了vector这就是不对的
十九.谨慎继承,如果要继承一定得有详细文档
二十.接口优于抽象类
这个感觉正常人都知道吧
二十一.为后代设计接口
java8之后,接口增加了缺省方法,就是为了给现有的接口加方法,在之前,如果这么改会报错。加这个东西主要是为了Lambda的应用,书里的意思是,即便有缺省方法,也得好好设计接口。
二十二.接口只用于定义类型
有一种接口只有定义了一些静态的常量,private static final xxx = 。。这种作者不建议用,它会让实现了接口的类的使用者很迷惑
二十三.类层次优于标签类
标签类就是那种,里面有enum枚举,又有属性的类,这样不如写子类好
二十四.静态成员类优于非静态成员类
因为非静态的成员类,可以访问外部类的属性;所以如果定义的这个成员类,无需访问外部对象实例,那么就得加static
二十五.不要在一个文件中定义两个class
跟标题一样,就是不要在一个.java文件中,写两个class
二十六.泛型----不要用原生类型
//不要用这种,如果这个里面放入了其他类型,那么运行阶段才会报错
private final Collection dogs;
//最好用下面这种
private final Collection<Animal> dogs;
如果用原生的类型,就失去了泛型在安全性和描述性方面的优势,如果不知道集合的类型,可以用?这种无限制通配符,也就是List<?> list
下面是术语:
二十七.泛型----消除unchecked警告
java在类型转换时如果没有进行类型校验,会报Uncheck cast的警告,如果确定对象能转成功一般我们是不会加校验,但警告一直存在。可以在当前方法上加上@SuppressWarnings(“unchecked”)注解关闭校验。这一节就是在告诉你,要尽可能消除这些影响。实在不行的话用@SuppressWarnings(“unchecked”),但我发现现在我们用的idea都没有了 是版本问题吗?
二十八.列表优于数组
- 列表,比如List 这种东西有泛型擦除,这样更灵活可以随意使用;而数组在运行期这个类型也是确定的
- 泛型类型是可变的
数组和泛型一般不可以很好的混合使用,(现在的程序员一般都不会用数组吧)。