1. 创建和销毁对象
1.1 用静态工厂方法代替构造器
public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); }
优点:
静态工厂方法有名字,代码更便于阅读
静态工厂方法不必每次都创建出新的对象,可以使用预创建好的对象,重复利用,有利于实例受控
静态工厂方法可以返回原类型的子类对象
静态工厂方法可以根据入参返回不同的对象
静态工厂方法返回的对象所属的类在编写静态方法时可以不存在,更加灵活,便于解耦
缺点:
类如果不含公有的或者受保护的构造器,就不能被子类化
1.2 遇到多个构造器参数时要考虑使用构建器
1.使用重叠构造器模式
缺点:客户端代码编写麻烦,容易出错
2.使用javabean的模式
缺点:构造过程被分到几个调用中时,有可能导致不一致的状态,容易出问题
3.使用builder模式
优点:客户端代码易于编写,易于阅读,十分灵活
缺点:由于需要在创建对象之前先创建builder,在注重性能的情况下可能会有开销问题
适用场景:存在较多参数
1.3 用私有构造器或枚举类强化Singleton属性
推荐使用枚举类型实现单例模式,静态工厂方法需要考虑序列化问题,需要声明中加上implements Serializable ,并提供一个readReslove方法保证每次反序列化实例时返回同一个对象
private Object readResolve(){
return INSTANCE;
}
1.4 通过私有构造器强化不可实例化的能力
有时候我们需要编写只包含静态方法和静态域的类作为工具类,这种类我们不需要实例化,可以在类里面添加一个私有的构造器,但是这样做会导致不能被子类化
1.5 优先考虑依赖注入来引用资源
1.6 避免创建不必要的对象
1. 最好能重用对象,而不是每次都创建一个新的对象
2. 使用静态工厂代替构造方法可以避免过多的创建对象。
3. 除了重用不可变对象外,还可以重用那些已知不会被修改的可变对象,可以使用static代码块创建对象
4. 优先使用基本类型,避免出现无意识的自动装箱,导致创建过多的对象
1.7 消除过期的对象引用
会导致内存泄漏,需要手动消除过期的对象引用
public class Stack {
pprivate Object[] elements;
private int size = 0;
private static final int DEFAULT_INITAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if(size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
private void ensureCapacity() {
if(elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
1.8 try-with-resources优先于try-finally
try-finally 关闭单个资源
static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
try-finally 关闭多个资源示例
static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
try-with-resources 关闭单个资源
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
try-with-resources 关闭多个资源
static void copy(String src, String dst) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
try-with-resources代码更加简洁,异常处理更加方便
2. 对于所有对象都通用的方法
2.1 覆盖equals时请遵守通用约定
1.可以不覆盖equals方法的条件:
- 代表实体而不是值的类
- 没有必要提供逻辑相等的功能
- 超类已经重写过equals,同样适用于这个子类
- 类私有,或包级私有
2.需要重写equals 条件:
- 需要用来进行值比较
- 超类未重写equals方法
- 枚举类除外,每个值最多只存在一个对象。
3. 重写equals时通用约定
- 自反性,非空的情况下,自身必须等于自身
- 对称性,非空的情况下,a.equals(b) b.equals(a)
- 传递性,非空的情况下,a.equals(b) b.equals(c) --> a.equals(c)
- 一致性,非空的情况下,不做修改的情况下,相等的一直相等
- 非空性,所有非空a.equals(null)都应该返回false
2.2 重写equals方法时必须重写hashCode方法
如果不这么做,可能会导致在一些基于散列值实现的集合类中出现问题
1.Object中hashCode方法的规范约定
- 当在一个应用程序执行过程中,如果在equals方法比较中没有修改任何信息,在一个对象上重复调用hashCode方法时,它必须始终返回相同的值。从一个应用程序到另一个应用程序的每一次执行返回的值可以是不一致的。
- 如果两个对象根据equals(Object)方法比较是相等的,那么在两个对象上调用hashCode就必须产生的结果是相同的整数。
- 如果两个对象根据equals(Object)方法比较并不相等,则不要求在每个对象上调用hashCode都必须产生不同的结果。 但是,程序员应该意识到,为不相等的对象生成不同的结果可能会提高散列表(hash tables)的性能。
2.不要通过排除重要属性来提高计算hashCode的性能
2.3 始终重写toString()方法
提供合适的输出
2.4 谨慎重写clone方法
使用super.clone()时,基础类型会复制,但是对象,数组这种会复制引用,会导致修改复制出来的对象的属性对象,原始的对象属性也会被修改,解决办法就是深克隆
2.5 考虑实现Comparable接口
public interface Comparable<T> {
public int compareTo(T o);
}
compareTo方法:将这个对象和指定对象进行对比,当该对象小于,等于或大于指定对象时,分别返回一个负整数,零或正整数,如果由于指定对象的类型而无法比较时则抛出异常
jdk1.7之后的版本compareTo方法中不建议使用<或>进行比较,建议使用装箱基础类型的类中的静态compare方法
多个属性需要进行比较时,从最关键的属性开始比较,当出现非零的结果结束整个比较操作,当一个属性比较结果相等时,依次进行下个属性的比较
public int compareTo(PhoneNumber pn){
int result = Short.compare(areaCode,pn.areaCode);
if(result == 0){
result = Short.compare(prefix,pn.prefix);
if (result == 0){
result = Short.compare(lineNum,pn.lineNum);
}
}
return result;
}
也可以使用Comparator提供的比较器构造方法
private static final Comparator<PhoneNumber> COMPARATOR =
comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn){
return COMPARATOR.compare(this,pn);
}
不要使用计算差值的方式,容易造成整数溢出,推荐使用装箱类的compare方法或者Comparator提供的比较构造器方法
3 类和接口
3.1 使类和成员的可访问性最小化
尽可能地使每个类或者成员不被外界访问
3.2 要在公有类中使用访问方法而非公有域
域私有化,提供公共的访问方法
3.3 使可变性最小化
不可变对象本质上是线程安全的,可以被自由的共享,对于频繁使用的值,我们可以提供公有的静态final常量
3.4 复合优先于继承
继承打破了封装性,子类依赖于父类中特定功能的实现细节,父类发生变化时,子类可能遭到破坏
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
public InstrumentedHashSet() {}
public InstrumentedHashSet(int initCap, float loadFactor) {
super(initCap, loadFactor);
}
@Override
public boolean add(E e) {
addCount ++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
解决办法:在新的类中增加一个私有域,引用现有类的一个实例,这种方法也被叫做复合
只有当子类是父类的子类型时,才适合使用继承
public class InstrumentedSet<E> extends ForwardingSet<E>{
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount ++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
//Reusable forwarding class
class ForwardingSet<E> implements Set<E> {
private final Set<E> s;
public ForwardingSet(Set<E> s) {this.s = s;}
@Override
public int size() {return s.size();}
@Override
public boolean isEmpty() {return s.isEmpty();}
@Override
public boolean contains(Object o) {return s.contains(o);}
@Override
public Iterator<E> iterator() {return s.iterator();}
@Override
public Object[] toArray() {return s.toArray();}
@Override
public <T> T[] toArray(T[] a) {return s.toArray(a);}
@Override
public boolean add(E e) {return s.add(e);}
@Override
public boolean remove(Object o) {return s.remove(o);}
@Override
public boolean containsAll(Collection<?> c) {return s.containsAll(c);}
@Override
public boolean addAll(Collection<? extends E> c) {return s.addAll(c);}
@Override
public boolean retainAll(Collection<?> c) {return s.retainAll(c);}
@Override
public boolean removeAll(Collection<?> c) {return s.retainAll(c);}
@Override
public void clear() {s.clear();}
}
3.5 要么设计继承并提供文档说明,要么禁止继承
禁止继承的方法,将类声明为final,或者将构造器私有化,提供静态方法创建对象
3.6 接口优于抽象类
1.现有的类可以很容易被更新,以实现新的接口
2.接口是定义 mixin (混合类型)的理想选择
如果一个类已经有了一个主要的超类型,那么通过实现一个接口,这个类可以拥有另一个次要的超类型
3.接口允许构造非层次结构的类型框架
public interface Singer{
AudioClip sing(Song s);
}
public interface Songwriter{
Song compose(int chatPosition);
}
public interface SingerSongwriter extends Singer, Songwriter{
Audi oClip strum();
void actSensitive();
}
4.通过抽象的骨架实现可以结合抽象类和接口的优势
public interface InterfaceDefineCompute{
int operate(int a, int b); // 运算符号定义
int plus(int a, int b) // 加法运算
int subtraction(int a, int b); // 减法运算
}
public abstract class AbstractDefineCompute implements InterfaceDefineCompute{
@Override
int plus(int a, int b){
return operate(a, b);
}
@Override
int subtraction(int a, int b){
b = b * -1;
return operate(a, b);
}
}
public class DefineCompute extends AbstractDefineCompute{
@Override
int operate(int a, int b){
return a + b;
}
}
public class Test{
public static void main(String[] args){
DefineCompute defineCompute = new DefineCompute();
int a=1,b=2;
defineCompute.plus(a,b);
defineCompute.subtraction(a,b);
}
}
3.7 为后代设计接口
java8之前,为接口添加一个方法,还得去破坏所有的子类代码才可以,因为你接口添加了方法,子类得去实现这个方法,不实现就会编译不通过,但是在java8之后增加了缺省方法,可以在接口中添加方法了。
public interface A {
public void A();
//接口中default修饰的方法是缺省方法
default void B() {
System.out.println("default");
}
}
不建议使用
3.8 类层次优于标签类
class Figure {
enum Shape { RECTANGLE , CIRCLE }
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
//Constructor for rectangle
Figure(double length , double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE :
return length * width;
case CIRCLE :
return Math.PI = (radius * radius);
default :
throw new AssertionError();
}
}
}
解决办法:子类型化
// Class hierarchy replacement for a tagged class
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
double area() { return Math.PI = (radius * radius); }
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double area() { return length = width; }
}
3.9 静态成员类优于非静态成员类
嵌套类是指在另一个类中定义的内部类。嵌套类应该只存在于为其外围类提供服务。如果嵌套类在某些其他上下文中有用,那么它应该是顶层类。有四种嵌套类:静态成员类,非静态成员类,匿名类和局部类。除第一种之外的其他类型都被称为内部类
非静态成员类的每个实例都与其外围类的实例隐式关联。在非静态成员类的方法中,可以调用外围类实例的方法,或者使用限定的this构造获得对外围实例的引用。如果嵌套类的实例可以在其外围类的实例之外独立存在,则嵌套类必须是静态成员类:如果没有外围类的实例,则无法创建非静态成员类的实例
如果声明一个不需要访问外围类实例的成员类,则始终将static修饰符放在其声明中,使其成为静态成员类而不是非静态成员类,外围类存储此引用需要时间和空间