创建和销毁对象(一)

1.用静态工厂方法代替构造器

为了获得一个类的实例,可以使用公有构造器,也可以提供一个公有的静态工厂方法,他只是一个返回类的实例的静态方法
public static Boolean valueOf(boolean b) { //Boolean是bool的一个Java装箱类
    return b ? Boolean.TRUE : Boolean.FALSE;
}

 

静态工厂方法的优点:
  1. 他是有名称的,阅读代码更加直观,例如Big Integer(int,int ,Random) 返回的BigInteger
  2. 不必在每次调用他们的时候创建一个新对象,他可以重复利用,Boolean.valueOf(boolean)不会创建对象
    1. 能够为重复的调用返回相同的对象,极大的提升性能
  3. 可以返回原返回类型的任何子类型的对象
  4. 返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值设置
  5. 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
静态工厂方法的缺点:
  1. 类如果不含公有的或者受保护的构造器,就不能被子类化
  2. 程序员很难发现他们
 
传统的方式是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);
        }
}

 

posted @ 2024-11-04 00:11  Heinrich♣  阅读(3)  评论(0编辑  收藏  举报