创建和销毁对象(一)
1.用静态工厂方法代替构造器
为了获得一个类的实例,可以使用公有构造器,也可以提供一个公有的静态工厂方法,他只是一个返回类的实例的静态方法
public static Boolean valueOf(boolean b) { //Boolean是bool的一个Java装箱类 return b ? Boolean.TRUE : Boolean.FALSE; }
静态工厂方法的优点:
- 他是有名称的,阅读代码更加直观,例如Big Integer(int,int ,Random) 返回的BigInteger
-
不必在每次调用他们的时候创建一个新对象,他可以重复利用,Boolean.valueOf(boolean)不会创建对象
- 能够为重复的调用返回相同的对象,极大的提升性能
- 可以返回原返回类型的任何子类型的对象
- 返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值设置
- 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
静态工厂方法的缺点:
- 类如果不含公有的或者受保护的构造器,就不能被子类化
- 程序员很难发现他们
传统的方式是new
class People{ String name; int age; int weight; } People People=new People();
静态工厂方法可以在类中添加一个公有静态方法来返回一个实例:
class People{ String name; int age; int weight; public static People getPeople(){ return new People(); } } People firstPeople=People.getPeople();
2.遇到多个构造器参数时要考虑使用构建器
静态工厂和构造器的缺点都是不能很好的扩展大量的可选参数,大部分程序员对于这样的情况使用重叠构造器。
public class Person { private final int age; private final int height; private final int weight; private final int sex; public Person (int age) { this(age,0); } public Person (int age, int height) { this(age,height,0); } public Person (int age, int height, int weight, int sex) { this.age = age; this.height = height; this.weight = weight; this.sex = sex; } }
遇到许多可选的构造器参数的时候,还有第二种代替方法,javaBean方式,先调用一个无参构造器来创建对象,再调用setter方法设置每个必要的参数,以及每个相关的可选参数
public class Person { private int age = 1; private int height = 2; private int weight = 3; private int sex = 1; public Person() {} //Setters public void setAge(int val) {age=val;} public void setHeight(int val){height=val;} public void setWeight(int val){weight=val;} public void setSex(int val){sex=val;} } Person person = new Person(); person.setAge(2); person.setHeight(100); person.setWeight(100); person.setSex(1);
但是JavaBean模式存在严重的缺点,构造过程被分散到几个调用中,在构造过程中JavaBean可能处于不一致的状态。
public class Person { private final int age; private final int height; private final int weight; private final int sex; // 私有构造函数,只能通过Builder来访问 private Person(Builder builder) { this.age = builder.age; this.height = builder.height; this.weight = builder.weight; this.sex = builder.sex; } // Builder类 public static class Builder { private final int age; private int height; private int weight; private int sex; // Builder的构造函数 public Builder(int age) { this.age=age; } // 链式设置方法 public Builder setAge(int age) { this.age = age; return this; } public Builder setHeight(int height) { this.height = height; return this; } public Builder setWeight(int weight) { this.weight = weight; return this; } public Builder setSex(int sex) { this.sex = sex; return this; } // 构建Person对象 public Person build() { return new Person(this); } } private Person(Builder builder){ age = builder.age; height = builder.height; weight = builder.weight; sex = builder.sex; } } // 使用Builder模式创建Person对象 Person person = new Person.Builder() .setAge(2) .setHeight(100) .setWeight(100) .setSex(1) .build();
如果类的构造器或者静态工厂中有多个参数,设计者种类,Builder模式就是一种不错的选择
3.用私有构造器或者枚举类型强化Singleton属性
Singleton是指仅仅一条被实例化一次的类,用来代表一个无状态的对象。使类称为Singleton会使他的客户端测试变得十分困难,因为不可能给Singleton替换模拟实现。
常见的实现Singleton方法
public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis(){...} public void leaveTheBuilding(){...} }
在实现Singleton的第二种方法中,公有的成员是一个静态工厂方法
public class Elvis { private static final Elvis INSTANCE = new Elvis(); private Elvis(){} public static Elvis getInstance(){return INSTRANCE;} public void leaveTheBuilding(){...} }
第三种方法
public enum Elvis { INSTANACE; public void leaveTheBuilding(){...} }
4.优先考虑依赖注入引入资源
public class SpellChecker { private static final Lexicon dictionary = ...; private SpellChecker() {} public static boolean isValid(String word) {...} public static List<String> suggestions(String typo) {...} }
静态工具类和Singleton类不适合需要引用底层资源类型的类
当创建实例的时候,就将该资源传到构造器中,这是依赖注入
public class SpellChecker { private final Lexion dictionary; public SpellChecker(Lexion dictionary) { this.dictionary = Objects.requireNonNull(dictionary); } public boolean isValid(String word){...} public List<String> suggestions(String typo) {...} }
5.避免创建不必要的对象
最好能重用单个对象,而不是在每次需要的时候创建也给相同功能的新对象。
String s=new String("bikini"); //bad example String s = "bikini"; //good example
New String每次都会重新创建一个实例。
第二个版本只用了一个String实例,而不是每次执行的时候都创建一个新的实例
static boolean isRomanNumeral(String s) { return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"+"(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$"); }
这个实现的问题在于它依赖String.matches方法,虽然String.matches方法最易于查看一个字符串是否正与正则表达式相匹配,但并不适合在注重性能的情形中重复使用
public class RonamNumerals { 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 isRomanNumeral(String s) { return ROMAN.matcher(s).matches(); } }
改进后的isRomanNumeral方法如果频繁的调用,会显示出明显的性能优势。
另一种创建多于对象的方式是自动装箱,允许程序员将基本类型和装箱基本类型混用
private static long sum() { LONG sum=0L; for(long i =0;i<=Integer.MAX_VALUE;i++) sum+=i; return sum; }
这段代码sum的声明从Long改为long之后会更快
6. 消除过期的对象引用
Java中的垃圾回收机制
public class Stack { private Object[] elements; private int size=0; private static final intg DEFAULT_INITIAL_CAPACITY=16; public Stack() { elements = new Object[DEFAULT_INITIAL_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); }
这段程序中会有内存泄露的问题,当一个栈先增长,再收缩,那么栈中弹出来的对象将不会被垃圾回收,这是因为栈内部维护着对这些对象的过期引用。
修复方法
public Object pop() { if(size==0) throw new EmptyStackException(); Object result = elements[--size]; elements[size]=null; return result; }
只要一个元素被弹出栈,指向它的引用就过期了。
内存泄漏的另一个常见的来源是缓存,一旦你把对象引用放到缓存中,他就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存
第三个常见的来源是监听器和其他回调,如果实现了一个api,客户端在这个api中注册回调,却没有显示的取消注册
7. try-with-resources优先于try-finally
Java类中有很多需要调用close方法来手动关闭的资源。例如InputStream,outputStream等
一般使用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语句正确的关闭了资源,他也存在着不足。当try中包try的时候非常麻烦
那么使用try with resources是一个很好的方法
static String firstLineOfFile(String path) throws IOException { try(BufferedReader br = new BufferedReader){ new FileReader(path){ return br.readLine(); } } } statric 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); } }