2021-10-23

[读书笔记]Effictive Java 第2章 创建和销毁对象

1:用静态工厂方法代替构造器 Consider static factory methods instead of constructors

先看JDK中的如下几个静态工厂方法:

// see line 149 in Boolean of Java8
public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

// see line 50 in Boolean of Java8
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);


// see line 693 in BigInteger of Java8
// 用这个静态工厂函数我们就知道拿到的一定是个素数,而构造函数却无法判定
public static BigInteger probablePrime(int bitLength, Random rnd) {
    if (bitLength < 2)
        throw new ArithmeticException("bitLength < 2");
    return (bitLength < SMALL_PRIME_THRESHOLD ?
            smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) :
            largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd));
}

// see line 109 in EnumSet of Java8
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType);
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");
    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}

// see line 205 in DriverManager of Java8
public static Connection getConnection(String url,
    java.util.Properties info) throws SQLException {
    return (getConnection(url, info, Reflection.getCallerClass()));
}

静态工厂方法与设计模式中的工厂方法模式不是一回事
静态工厂方法的优势:
● 和构造器不同的是,静态工厂方法有名称
● 和构造器不同的是,静态工厂方法不必在每次调用它们的时候都创建一个新对象,这可以使不可变类可以使用预先构建好的实例,或将构建好的实例缓存起来进行重复利用,如Boolean.valueOf(boolean),它从来不创建对象
● 和构造器不同的是,它可以返回原返回类型的任何子类型对象
● 所返回的对象的类可以随着每次调用而发生变化,取决于静态工厂方法的参数值。如上的EnumSet,它没有公有的构造器,只有静态工厂方法。它返回两个子类之一的一个实例,具体取决于底层枚举类型的大小:若元素为64个或更少,就像大多数枚举类型一样,静态工厂方法返回一个RegularEnumSet实例,用单个long进行支持;若枚举类型有65个或者更多元素,工厂就返回JumboEnumSet实例,用一个long数组进行支持。
● 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在。如上的DriverManager.getConnection(String, Properties)方法,它返回的是个接口,服务提供方实现其接口即可
静态工厂方法的劣势:
● 类如果不含公有的或者受保护的构造器,就不能被子类化
● API文档中不被注明则很难知道有相关的静态工厂方法

2:遇到多个构造器参数时要考虑使用构建器 Consider a builder when faced with many constructor parameters

静态工厂和构造器有共同的局限性:它们都不能很好地扩展到大量的可选参数。当一个有大量可选参数需要的时候,类的创建代码可以有三类:
● 重叠构造器(telescoping constructor)–代码冗余复杂可读性可用性差
● JavaBeans模式,先调用一个无参构造器来创建对象,然后再调用setter方法来设置每个必要的参数,以及每个相关的可选参数–在构造过程中JavaBean可能处于不一致状态,线程安全问题
● 建造者模式,既能保证像重叠构造器模式那样的安全性,也能保证像JavaBeans模式那么好的可读性。它不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象,然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。

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;

    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;

        // Required parameters by constructor
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        
        // Optional parameters by build
        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);
        }
    }
    
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}


NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
        .calories(100).sodium(35).build();

Builder模式模拟了具名的可选参数,也适用于类层次结构,总之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是一种不错的选择。

3:用私有构造器或者枚举类型强化Singleton属性 Enforce the singleton property with a private constructor or an enum type

4:通过私有构造器强化不可实例化的能力 Enforce noninstantiability with a private constructor

工具类不希望被实例化,因为实例化对它没有任何意义,然而在缺少显示构造器的情况下,编译器会自动提供一个公有的、无参的缺省构造器,由于只有当类不包含显示的构造器时,编译器才会生成缺省的构造器,因此只要让这个类包含一个私有构造器,它就不能被实例化,如Arrays这个工具类:

public class Arrays {
    …………

    // Suppresses default constructor, ensuring non-instantiability.
    private Arrays() {}
}

读书的时候想了一个白痴问题,但值得记录下,我就想:既然Arrays都没有被实例化,那我们是怎么用到里面的方法的呢?比如怎么调的sort?
这很简单啊。。。因为实例化是把类的对象实例化到堆中,这才叫实例化,你类本来就在方法区中,当然可以访问里面的静态方法了。。。sort是static的啊

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

不要用Singleton和静态工具类来实现依赖一个或多个底层资源的类,而应该将这些资源或者工厂传给构造器(或者静态工厂,或者构建器),通过它们来创建类,这种方式称为依赖注入。
想想spring编写依赖注入时,其实不就是把资源作为一个字段向类中进行了注入,也就是把资源传给了构造器。

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

极端反例,每被执行一次就会创建一个对象:

String s = new String("bikini");

正确方式,只会存在一个String实例:

String s = "bikini";

如下一个实验:

public class Test {
    public static void main(String[] args) {
        final long a = System.currentTimeMillis();
        Solution.sumQuick();
        final long b = System.currentTimeMillis();
        Solution.sumSlow();
        final long c = System.currentTimeMillis();
        System.out.println("quick: " + (b - a));
        System.out.println("slow: " + (c - b));
    }
}

class Solution {
    public static long sumSlow() {
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++) {
            sum += i;
        }
        return sum;
    }

    public static long sumQuick() {
        long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++) {
            sum += i;
        }
        return sum;
    }
}

输出如下:

quick: 941
slow: 5627

用时5.6秒的函数把sum声明成了Long而不是long,意味着程序构造了大约个232多余的Long对象,所以要优先使用基本类型而不是装箱基本类型,当心无意识的自动装箱

9:try-with-resources优先与try-finally

看两段代码

// try-finally - No longer the best way to close resources
static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}

// try-with-resources - the best way to close resources
static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}
// try-finally is ugly when userd with more than one resource
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[1024];
            int n;
            while ((n = in.read(buf)) >= 0) {
                out.write(buf, 0, n);
            }
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

// try-with-resources on multiple resources - short and sweet
static void copy(String src, String dst) throws IOException {
    try (InputStream in = new FileInputStream(src);
         OutputStream out = new FileOutputStream(dst)) {
        byte[] buf = new byte[1024];
        int n;
        while ((n = in.read(buf)) >= 0) {
            out.write(buf, 0, n);
        }
    }
}

何为资源?
实现了Closeable和AutoCloseable的类即为资源

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值