Java创建和销毁对象的一些技巧和注意事项

总括

1.使用静态工厂方法代替构造方法
2.当构造函数有很多参数时考虑使用构建器
3.用私有构造器或枚举类型实现单例模式
4.在想让一个工具类不被实例化的时候使用私有构造函数
5.优先考虑使用依赖注入来引用资源
6.避免创建不必要的对象
7.消除不必要的对象引用
8.避免使用finalize和clear方法
9.try-with-resources优先于try-finally

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

如将boolean转成Boolean对象的方法:

public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;
}

静态工厂方法的优点:
(1)这个方法有名称,更易用,代码更易读
(2)与构造方法不同,它不会在每次调用的时候都创建一个对象,也就意味着它能够为重复的调用返回相同的对象
(3)它可以返回原返回类型的任意子类对象
(4)静态方法返回对象的类型会随着参数的不同而不同
(5)方法返回对象所属的类,在编写该包含静态工厂方法的类时可以不存在
一个类最好只有一个带有指定签名的构造函数,这样使得在使用的时候不会出现错误和混淆。我们也看到在Java swing中大量使用了静态工厂方法。

缺点:
(1)如该类没有公有构造函数,则它不能被继承
(2)javadoc不会像对待构造函数那样明确标出静态工厂方法,使之不易被发现

2.当构造函数有很多参数时考虑使用构建器

通常在实例化对象的时候我们有两种方式来满足个性化的实例化:使用重叠构造器和JavaBeans模式。前者在不同的构造器之间使用this相互引用,但当参数数量较多时使得代码较难编写且难以阅读;后者通过无参构造先创建对象,再通过setter进行初始化,这使得创建对象变得容易,但JavaBeans可能处于不一致状态,影响到类的不可变性。

于是就有了第三种方法:构建器(Builder)。它的做法是这样:考虑到构造函数中有必要的参数和可选的参数,它让客户端先利用必要参数调用构造方法,之后在builder对象上调用类似setter的方法来初始化可选参数。通常把Builder类作为该类的静态成员类。
例:

public class NutritionFacts{
    //必要参数
    private final int servings;
    //可选参数
    private final int calories;
    private final int fat;
    private final int sodium;

    public static class Builder{
        //必要参数
        private final int servings;
        //可选参数
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        
        public Builder(int servings){ 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 NutritionFacts(Builder builder){
            servings = builder.servings;
            calories = builder.calories;
            fat = builder.fat;
            sodium = builder.sodium;
        }
    }
}

则客户端使用构建器创建对象的代码为:

NutritionFacts Cola = new NutritionFacts.Builder(240).calories(100).sodium(30).build();

这样既可以实现按需求进行初始化,又使得代码变得易于阅读。

3.用私有构造器或枚举类型实现单例模式

方法一:

public class Single{
    private static final Single INSTANCE = new Single();
    private Single(){}
    public static Single getInstance(){ return INSTANCE; }
    public void whateverMethod(){//...}
}

方法二:使用枚举,适于需要序列化的时候。

public enum Singleton { 
    INSTANCE; 
    public void whateverMethod() { } 
}

方法三:较常用的方法

public class Singleton { 
    private static Singleton instance = new Singleton(); 
    private Singleton (){} 
    public static Singleton getInstance() { 
        return instance; 
    } 
}

4.在想让一个工具类不被实例化的时候使用私有构造函数

我们有时候会有一些提供静态方法的工具类来进行面向过程编程,因为是静态方法,他们是属于类的,虽然可以被继承,但是不存在多态,有时候我们希望这样的类不被实例化,于是可以在该类中定义私有的构造方法,因为若不这样做,编译器会自动生成无参构造,私有构造方法是的不能在类的外部进行实例化。
例:

public class UtilityClass{
    //压制使用默认构造函数
    private UtilityClass(){
        throw new AssertionError(); //用于防止对象的空初始化,防止在类内调用该构造函数
    }
}

5.优先考虑使用依赖注入来引用资源

当创建一个实例时,就将该资源传入构造方法中。例:

public class SpellChecker{
    private final Lexicon dict;

    public SpellChecker(Lexicon dict){
        this.dict = Objects.requireNonNull(dict);
    }

    //...
}

此外,静态工具类和单例模式不适合用于需要引用资源的类。
当依赖较多时,管理各种依赖变得十分困难,这通常由Spring这样的依赖注入框架来解决。

6.避免创建不必要的对象

当创建对象的代价很大时(比如大对象),考虑对象的重复使用。
比如使用正则表达式匹配的String.matches方法时,由于每次调用该方法都会创建一个Pattern实例,所以我们可以将正则表达式编译缓存起来重复使用,如判断是否是罗马数字:

public class RomanNumerals{
    private static final Pattern ROMAN = Pattern.compile(
        "此处是正则表达式,省略"
    );

    static boolean isRomanNumerals(String s){
        return ROMAN.matcher(s).matches();
    }
}

此外在无必要时尽可能使用基本数据类型而非其包裹类型,这样可减少开销。
维护自己的对象池通常不是好的做法,除非对象非常之大,如数据库连接池,因为数据库连接的开销很大。轻量的对象就不必考虑复用了,因为JVM会帮我们进行优化。

7.消除不必要的对象引用

JVM将程序员从内存管理的痛苦中解放出来,但这不意味着我们不需要做内存管理了,看一下实现栈这一数据结构中弹栈的代码:

@SuppressWarnings("unchecked")
public T pop(){
    if(!isEmpty()){
        return (T)elements[top--];
    }else{
        System.out.println("Stack is empty");
        return null;
    }
}

当栈先增长,再缩小,则那些弹出栈的对象不会被垃圾回收。即使栈的程序不再引用这些对象,它们也不会被回收,因为栈内维护着这些对象的过期引用,虽然它们逻辑上不存在了,但是实际上并没有被回收,造成内存泄漏。
避免内存泄漏的做法是对这些不使用的对象进行解除引用,如下:

@SuppressWarnings("unchecked")
public T pop(){
    if(!isEmpty()){
                    T result = (T)elements[--top];
        elements[top] = null;
        return result;
    }else{
        System.out.println("Stack is empty");
        return null;
    }
}

清空对象引用应该是一种例外,而不是一种规范,当数组元素变成了非活动的一部分(逻辑上不存在),需要手动进行清空。

8.避免使用finalize和clear方法

上述方法的缺点:不能保证被调用;存在性能损失和安全问题。

9.try-with-resources优先于try-finally

例:

try(BufferedReader br = new BufferedReader(new FileReader(path))){
    return br.readLine();
}catch(IOException e){
    //...
}

这用于避免多重try-finally嵌套造成的调试困难。

参考文献:《Effective Java》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值