• EffectiveJava笔记1.创建和销毁对象 fight


    核心内容

    创建、销毁对象主要包含以下4个方面的内容:

    • 何时以及如何创建对象
    • 何时以及如何避免创建对象
    • 如何却奥他们能够适时地销毁
    • 如何管理对象销毁之前必须进行的各种清理动作

    解决方案及编码原则

    第1条:使用 静态工厂方法 代替 构造器

    类提供一个公有的 静态工厂方法,例如我们看下 Boolean类的示例

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

    注:静态工厂方法 不等价于 设计模式中的工厂模式
    优势:

    1. 第一大优势:相比于构造器,他们有方法名,可以更加准确清晰的描述返回的对象。可读性更高,构造器的参数必须不同,限制更严格,所以此方式更灵活
    2. 第二大优势:相比于构造器,不必在每次调用他们的时候都创建一个新对象。静态方法返回的对象,内部实现可以缓存起来,比如:Boolean.valueOf(boolean)
    3. 第三大优势:相比于构造器,可以返回类型的任何子类型对象。通过方法,返回值可以设置为 接口 类型,返回其所有子类型

    灵活的静态工厂方法构成了 服务提供者框架(Service Provider Framework),例如:JDBA(java数据库连接,Java Database Connectivity)API.

    服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。
    包含三个重要的组件(1、2、3)和一个可选组件(4):

    1. 服务接口(Service Interface):有提供者实现的
    2. 提供者注册API(Provider Registration API):系统用来注册实现,让客户端访问他们的
    3. 服务访问API(Service Access API):客户端用来获取服务实例的。
    4. 服务提供者接口(Service Provider Interface):如果没有接口,那么实现就需要按照类名称注册,然后通过反射方式进行实例化。

    例如:对于JDBC来说:

    • Connection是它的服务接口
    • DriverManager.registerDriver是提供者注册API,
    • DriverManager.getConnection是服务访问API,
    • Driver是服务提供者接口

    对于 服务访问API 可以利用适配器(Adapter)模式,返回比提供者需要的更丰富的服务接口。给一个具体的例子:

    //Service Interface
    public interface Service{
    	...//方法定义
    }
    //Service Provider Interface
    public interface Provider{
    	Service newService();
    }
    //Noninstantiable class for service registration and access
    public class Services{
    	//阻止实例化
    	private Services(){}
    	
    	//创建服务接口的映射关系
    	private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
    	
    	public static final String DEFAULT_PROVIDER_NAME = "<def>";
    	
    	//Provider registration API ,提供者注册API
    	public static void registerDefaultProvder(Provider p){
    		registerProvider(DEFAULT_PROVIDER_NAME, p);
    	}
    	public static void registerProvider(String name, Provder p){
    		providers.put(name, p);
    	}
    	
    	//Service access API
    	public static Service newInstance(){
    		return newInstance(DEFAULT_PROVIDER_NAME);
    	}
    	
    	public static Service newInstance(String name){
    		Provider p = providers.get(name);
    		if(p == null){
    			throw new IllegalArgumentException("No provder registered with name:" + name);
    		}
    		reutrn p.newService();
    	}
    }
    
    

    缺点:

    1. 类如果不含有工友或者受保护的构造器,就不能被子类化
    2. 它们于其他的静态方法没有任何区别,辨识度不高

    静态工厂方法的一些惯用名称:

    1. valueOf
    2. of
    3. getInstance
    4. newInstance
    5. getType
    6. newType

    第2条:遇到多个构造器参数时需要考虑用 构建器(Builder)

    多个参数,大家一般会想到两种方式:

    • 多个构造器组成,维护成本高
    • 使用 pojo,先new对象,再set,数据可能出于不一致的状态,线程不安全
      在这种时候,我们尽量使用 Builder 模式,可以达到以上两种的优点,示例代码如下:
    //Bulder模式
    public class Test {
        //必选参数
        private int test1;
        private int test2;
        
        //可选参数
        private int test3;
        private int test4;
    
        public static class Builder{
            private int test1;
            private int test2;
            
            private int test3 = 0;
            private int test4 = 0;
            
            public Builder(int test1, int test2){
                this.test1 = test1;
                this.test2 = test2;
            }
            
            public Builder test3(int test3){
                this.test3 = test3;
                return this;
            }
            
            public Builder test4(int test4){
                this.test4 = test4;
                return this;
            }
            
            public Test build(){
                return new Test(this);
            }
        }
        
        public Test(Builder builder) {
            this.test1 = builder.test1;
            this.test2 = builder.test2;
            this.test3 = builder.test3;
            this.test4 = builder.test4;
        }
    
    
        public static void main(String[] args) {
            Test test = new Test.Builder(1, 2)
                    .test3(3).test4(4)
                    .build();
        }
    }
    

    当大多数参数都是可选的时候,使用Bulder模式,更易于阅读和编写,同时也比JavaBean更加安全。

    第3条:用私有构造器*或者枚举类型强化 Singleton 属性

    Singleton 指仅被实例化一次的类。
    第一种方式:构造器保持私有,到处公有的静态成员。

    public class TestSingleton {
        public static final TestSingleton INSTANCE = new TestSingleton();
        
        private TestSingleton(){
            ///...
        }
    }
    

    上边这种方式,可以通过反射来获取,可以在构造器中进行判断,在创建实例时抛出异常。

    public class TestSingleton {
        public static final TestSingleton INSTANCE = new TestSingleton();
        
        private TestSingleton(){
            throw new AssertionError();
        }
    }
    

    为了用一种更优化的方式,解决上边的问题,就有了下边第二种方式
    第二种方式:提供 静态工厂方法 到处共享实例

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

    如果上边的类,要变成可序列化的

    • 仅仅声明 "implement Serializable" 是不够的。
    • 声明所有实例域都是瞬时(transient)的
    • 同时提供一个 readResolve方法。
      否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。

    在java 1.5发行版本起,可以使用枚举的方式来实现:

    public enum EnumSingleton {
        INSTANCE;
    }
    

    效果一样的前提下,代码更简洁、并且无偿提供了序列化机制,达到绝对防止多次实例化。

    第4条:避免创建不必要的对象

    通过前边提到的 3 种方法,去实现避免创建不必要的对象,会极大的提升性能。
    延迟初始化是不建议的方式,复杂度增加
    对象池的方式也不是一种好的方式,除非是对象是非常重量级,比如:数据库连接池。
    注:因重用对象而付出的代价要远远大于因创建重复对象而付出的代价。

    第5条:消除过期的对象引用

    public class TestStack {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INTIAL_CAPACITY = 16;
    
        public TestStack() {
            elements = new Object[DEFAULT_INTIAL_CAPACITY];
        }
    
        public void push(Object element) {
            ensureCapaticy();
            elements[size++] = element;
        }
        
        public Object pop(){
            if (size == 0){
                throw new EmptyStackException();
            }
            return elements[--size];
        } 
    
        private void ensureCapaticy() {
            if (elements.length == size){
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    }
    

    在上边的代码中,pop方法里边只是返回了数组中的数据,这些数据即使没人用了,也不会被回收,从而导致了内存泄漏。这类内存泄漏
    通常是"无意识的对象保持"。
    如何解决这类问题,只需要在引用已经过期后,只需要清空这些引用即可,对于pop的微调一下:

    public Object pop(){
        if (size == 0){
            throw new EmptyStackException();
        }
        Object result = elements[--size];
        elements[size] = null;
        return result;
    }
    

    针对上边的改造方式,会让大家以后过于谨慎,其实没必要,本身这种方式也是一种例外,不是一种规范行为。
    最好的方法是:让包含该引用的变量结束其生命周期。
    通过上边的例子可以看出:只要类是自己管理内存,大家就应该警惕内存泄漏问题。一旦元素被释放掉,就应该将其清空。

    内存泄漏另一个常见来源是缓存,为了保证垃圾正常被回收,使用 WeakHashMap来保存其引用。
    还有另外一个常见来源监听器回调,最佳方法同上,只保存他们的弱引用(weak reference)。

    第6条:避免使用终结(finalizer)方法

    终结方法(finalizer)方法通常是不可预测的,也比较危险。会导致行为不稳定,性能下降,可移植性差。
    缺点:

    • 不能保证会被及时的执行。比如:用终结方法来关闭已经打开的文件,是严重的错误用法,而是try finally替代
    • 同时也不能保证它们会被执行
      注意:不应该依赖终结(finalizer)方法来更新重要的持久状态
    • 有非常严重的性能损失
      如果类的对象中封装的资源(例如:文件、线程)确实需要终止,只需提供一个显示的终止方法。
    • 显示的终止方法有以下要求:
      1、每个实例不再有用的时候调用
      2、该实例必须在自己的私有域,记录下自己是否已经被终止,以便其他方法在调用前抛出 IllegalStateException 异常
      典型的例子:InputStream、OutputStream、java.util.Connection上的 close 方法。

    通常情况下,显示的终止方法try-finally结构 结合起来使用,以确保及时终止。在 finally 子句内部调用显示的终止方法,保证在异常情况下,也会被执行。

    Foo foo = new Foo(...);
    try{
        ...
    } finally{
       foo.terminate();
    }
    

    带来的好处:

    • 充当"安全网",任何情况下都会被执行
    • 与对象的本地对等体(natice peer)有关。
      本地对等体:一个本地对象(native object),普通对象通过本地方法(native method),委托给一个本地对象。所以,当 它的java对等体被回收时,它(本地对象)不会被回收,这种情况下,就可以使用终止方法来执行回收。

    还有一个需要注意的点是“终结方法链”,它并不会被自动执行。如果类有终结方法,并且子类覆盖了终结方法,子类的终结方法必须手动调用超累的终结方法。
    在子类调用的代码中,也还是应该结合 try-finally 结构来调用,以保证父类的终结方法被正常调用。

    最后如果需要把终结方法和公有非final类关联起来,请考虑使用 结方法守卫者,以确保即使子类的终结方法未能调用super.finalizer,该终结方法也被执行。

  • 相关阅读:
    如何强制360浏览器以极速模式打开页面
    如何解决Android SDK无法下载Package的问题(.net)
    Xamarin 安装步骤
    使用require.js和backbone实现简单单页应用实践
    HBuilder之初体验
    ClassLoader&双亲委派&类初始化过程
    List remove ConcurrentModificationException源码分析
    二十九、简谈设计模式
    二十八、Java基础--------正则表达式
    二十七、JDK1.5新特性---Annotation
  • 原文地址:https://www.cnblogs.com/qxynotebook/p/16183419.html
Copyright © 2020-2023  润新知